问题:
接管别人代码时遗留的一个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;}