在linux上,当使用C执行与端口0(选择随机端口)的套接字绑定时,我得到错误98,地址已在使用中.怎么可能?

Jef*_*y K 5 c sockets linux bind

因此,我们有一个长期存在的商业产品,这已经很成熟,我以前从未见过这类问题.我们使用客户端程序将数据发送到服务器.有时,由于客户环境中的防火墙,我们允许最终用户指定要绑定的出站端口范围,但是,在我看到的这个特定问题中,我们没有这样做,并且正在使用端口0来执行绑定.从我读过的所有内容来看,这意味着选择一个随机端口.但我无法找到的是,这对内核/操作系统意味着什么.如果我要求一个随机端口,那么它是如何使用的呢?严格来说,只有src ip/src端口和dst ip/port的唯一配对才能使连接唯一.我相信可以使用相同的端口,如果与另一个目标IP通信,但也许这与此无关.

此外,这不会发生在所有客户的系统上,只有一些.因此,这可能是某种形式的负载相关问题.据我所知,系统相当繁忙.

这是我们正在使用的代码.我遗漏了一些用于windows的ifdef代码,并在绑定后省略了我们的操作.

    _SocketCreateClient(Socket_pwtP sock, SocketInfoP sInfo )
{
int nRetries;                       /* number of times to try connect()  */
unsigned short port;
BOOL success = FALSE;
BOOL gotaddr = FALSE;
char buf[INET6_ADDRSTRLEN] ="";
int connectsuccess =1;
int ipv6compat =0;

#ifdef SOCKET_SEND_TIMEOUT
struct timeval time;
#endif /* SOCKET_SEND_TIMEOUT */

nRetries = sInfo->si_nRetries;
sock->s_hostName = strdup(sInfo->si_hostName);

#ifdef DEBUG_SOCKET
LogWrite(LogF,LOG_WARNING,"Socket create client");
LogWrite(LogF,LOG_WARNING,"Number of retries = %d", nRetries);
#endif

ipv6compat = GetIPVer();
if (ipv6compat == -1) /* ipv6 not supported */
    gotaddr = GetINAddr(sInfo->si_hostName, &sock->s_sAddr.sin_addr);
else
    gotaddr = GetINAddr6(sInfo->si_hostName, &sock->s_sAddr6.sin6_addr);

/* translate supplied host name to an internet address */
if (!gotaddr) {
                        /* print this message only once */
                        if ( sInfo->si_logInfo && ( sInfo->si_nRetries == 1 ) )
                        {
                           LogWrite(LogF, LOG_ERR,
           "unable to resolve ip address for host '%s'", sInfo->si_hostName);
                        }
                        sock = _SocketDestroy(sock);
}

else {

    if (ipv6compat == 1) /* ipv6 supported */
    {
            /* try to print the address in sock->s_sAddr6.sin6_addr to make sure it's good.  from call above */
            LogWrite(LogF, LOG_DEBUG2, "Before call to inet_ntop");
            inet_ntop(AF_INET6, &sock->s_sAddr6.sin6_addr, buf, sizeof(buf));
            LogWrite (LogF, LOG_DEBUG2, "Value of sock->s_sAddr6.sin6_addr from GetINAddr6: %s", buf);


            LogWrite (LogF, LOG_DEBUG2, "Value of sock->s_sAddr6.sin6_scope_id from if_nametoindex: %d", sock->s_sAddr6.sin6_scope_id);

            LogWrite (LogF, LOG_DEBUG2, "Value of sock->s_type: %d", sock->s_type);
    }


    /* try to create the socket nRetries times */
    while (sock && sock->s_id == INVALID_SOCKET) {
        int socketsuccess = FALSE;

        /* create the actual socket */

        if (ipv6compat == -1) /* ipv6 not supported */
            socketsuccess = sock->s_id = socket(AF_INET, sock->s_type, 0);
        else
            socketsuccess = sock->s_id = socket(AF_INET6, sock->s_type, 0);

        if ((socketsuccess) == INVALID_SOCKET) {
            GETLASTERROR;
            LogWrite(LogF, LOG_ERR, "unable to create socket: Error %d: %s", errno,
            strerror(errno) );
            sock = _SocketDestroy(sock);
        }
        else
        {

             /* cycle through outbound port range for firewall support */
            port = sInfo->si_startPortRange;
         while ( !success && port <= sInfo->si_endPortRange ) {
                    int bindsuccess = 1;

             /* bind to outbound port number */
                    if ( ipv6compat == -1) /* ipv6 not supported */
                    {
                            sock->s_sourceAddr.sin_port   = htons(port);
                            bindsuccess = bind(sock->s_id, (struct sockaddr *) &sock->s_sourceAddr,
                                             sizeof(sock->s_sourceAddr));

                    }

                    else {
                            sock->s_sourceAddr6.sin6_port   = htons(port);
                            inet_ntop(AF_INET6, &sock->s_sourceAddr6.sin6_addr, buf, sizeof(buf));
                            LogWrite(LogF, LOG_DEBUG,
                                            "attempting bind to s_sourceAddr6 %s ", buf);

                            bindsuccess = bind(sock->s_id, (struct sockaddr *) &sock->s_sourceAddr6,
                                             sizeof(sock->s_sourceAddr6));
                    }

                     if (bindsuccess == -1) {
                            GETLASTERROR;
                            LogWrite(LogF, LOG_ERR,
                                    "unable to bind port %d to socket: Error %d: %s. Will attempt next port if protomgr port rules configured(EAV_PORTS).", port, errno, strerror(errno) );

                            /* if port in use, try next port number */
                          port++;
              }
              else {
                    /* only log if outbound port was specified */
                    if (port != 0)
                             {
                               if ( sInfo->si_sourcehostName ) {
                                  LogWrite(LogF, LOG_DEBUG,
                                       "bound outbound address %s:%d to socket",
                                             sInfo->si_sourcehostName, port);
                               }
                               else {
                                  LogWrite(LogF, LOG_DEBUG,
                                       "bound outbound port %d to socket", port);
                               }
                            }
                            success = TRUE;
              }


         }
        }
    }
}
return(sock);
}
Run Code Online (Sandbox Code Playgroud)

我们在日志文件中看到的错误如下所示.它正在进行2次尝试并且都失败了:

protomgr [628453]:错误:无法将端口0绑定到套接字:错误98:地址已在使用中.如果配置了protomgr端口规则(EAV_PORTS),将尝试下一个端口.

protomgr [628453]:错误:无法将端口绑定到套接字:错误98:地址已在使用中.如果此消息来自protomgr,请考虑增加EAV_PORTS的数量.

protomgr [628453]:错误:无法将端口0绑定到套接字:错误98:地址已在使用中.如果配置了protomgr端口规则(EAV_PORTS),将尝试下一个端口.

protomgr [628453]:错误:无法将端口绑定到套接字:错误98:地址已在使用中.如果此消息来自protomgr,请考虑增加EAV_PORTS的数量.

Jef*_*y K 5

因此,看起来这与系统耗尽可用端口有关,并且它被配置为只有大约9000个端口可用.

/etc/sysctl.conf中的此设置控制可用端口:net.ipv4.ip_local_port_range = 9000 65500

第一个数字是起始端口,第二个是最大数字.这个例子是从未经改动的Suse Enterprise Linux服务器11.0中提取的.报告此问题的我们的客户以这样的方式配置它在他们定义的范围内只有大约9000个端口可用,并且所有端口都在系统上使用.

希望这对未来的其他人有所帮助.