单网卡多IP导致的socket connect 10060超时错误

问题:

接管别人代码时遗留的一个bug,在win7下,给一个网卡设置多个ip时,发现无法连接上服务器了。XP下就不会,这多个ip为192.168.1.127,172.1.1.13,10.0.0.1。

复现过程:

复现的过程中我的服务器ip为192.168.1.128,子网掩码255.255.255.0,默认网关为192.168.1.128(服务器不需要联网,所以就这么设置了)。

客户端按照bug说明设置了对应的3个IP。

发现无法复现,然后和测试沟通要求重现,发现他那边设置这样的IP是会出现的。因此比较了下他的服务器IP和我的服务器IP的区别,竟然是默认网关的问题,设置成192.168.1.1就能复现了。(后来发现设置成其他的也可以)。

解决过程:

首先恶补了一下IP、子网掩码、网关的知识。(都还给老师了)(socket技术也不是很熟悉)

分析的话还是从客户端IP开始,首先3个IP中肯定要存在的是192.168.1.127,因为只有这个IP和服务器同网段,其他两个都是不同网段的,并且刚好占据了A、B、C三类IP。于是试着去掉172网段的IP,发现也会复现。

OK,那基本确定就是因为存在一个不同网段的IP导致连接不上服务器了,于是去看源代码,跟踪发现是在调用getaddrinfo时,传入本主机名称,返回的IP列表中存在两个IP,但是代码里面默认就只使用第一个,这win7下第一个刚好是10.0.0.1,当然就无法和192.168.1.128的服务器通信了。测试了下XP下刚好第一个IP是和服务器同网段的,所以就能通信。(不过这里有个疑问就是服务器默认网关设置成192.168.1.128时仍然能通信,这是为什么?)

因为后面调用connect的地方和getaddrinfo的地方不在同一个层级,而且都已经经过了封装,想要将返回的IP列表全部返回到connect的地方会牵涉到比较大的改动,我比较担心引发新的BUG。所以决定在返回Ip给connect使用之前就过滤出和服务器IP在同一网段的IP。

这里是将服务器IP传进去作为一个参照对象。将返回的IP列表中的IP全部转换成unsigned long类型,然后和服务器IP做差,差值最小的就是最接近服务器IP的(认为是和服务器IP同网段的)。要注意的是,inet_addr函数返回的unsigned long是将高段的值放置在末尾的,所以这里调用了ntohl函数将unsigned long逆序一遍。

代码片段如下:

if ( SOCKET_ERROR != getaddrinfo(pstrHost, pstrService, &aiHints, &aiList) && ( aiList != 0 ))
    {
        // 优先匹配在同一网段的IP地址来使用
        ADDRINFO *aiUse = aiList;
        if(NULL != aiUse->ai_next && NULL != pszRemote && AF_INET == nFamily)    // 只支持ip4的
        {
            char szAddress[MAX_PATH] = { 0 };
            ADDRINFO* aiCrt = aiUse;
            unsigned long lulMinimumDiff = 0xffffffff;
            unsigned long llRemote = ntohl(inet_addr(pszRemote));
            unsigned long llCrt = 0;
            unsigned long llDiff = 0;
            while(NULL != aiCrt)
            {
                // 转换成数值,差值最小的就是ip最接近的
                const void* addr = &((const sockaddr_in*)aiCrt->ai_addr)->sin_addr;
                if (inet_ntop(nFamily, addr, szAddress, MAX_PATH) != NULL)
                {
                    llCrt = ntohl(inet_addr(szAddress));
                    if(llCrt > llRemote)
                        llDiff = llCrt - llRemote;
                    else 
                        llDiff = llRemote - llCrt;

                    if(llDiff < lulMinimumDiff)
                    {
                        lulMinimumDiff = llDiff;
                        aiUse = aiCrt;
                    }
                }

                aiCrt = aiCrt->ai_next;
            }
        }
        ADDRINFO ai = { 0 };
        ai.ai_addr = sockAddr;
        memcpy(ai.ai_addr, aiUse->ai_addr, aiUse->ai_addrlen);
        freeaddrinfo( aiList );
        return true;
}
原文地址:https://www.cnblogs.com/monotone/p/3988434.html