使用Socket套接字绑定函数bind的一个细节

    只要稍微接触Socket套接字进行过网络编程的人,对Socket客户端调用流程或服务端调用流程都会很熟悉,传输层协议采用TCP也好,或UDP也罢。但是要写好这套“固化”的流程代码,如果稍不小心,或缺乏经历,还是很容易犯错误的,尤其当项目程序在迭代开发过程中,功能越来越多,也越来越复杂的情况下。

    就在前几天,我碰到了这样一个细节问题。

    协议栈Demo程序中有个设备校时的功能,采用NTP协议进行设备间的时钟同步。Demo程序在我和一位同事的PC (操作系统为MS XP)上测试时钟同步,怎么测,都没问题。但是拿到Windows 7系统下,却可能会发生问题,后来我在Windows Server 2003系统下,也碰到这个问题。

    Demo A要向 Demo B发送登录请求,登出......并发送时钟同步请求(A向B登录请求时创建的套接字和A向B时钟同步请求创建的套接字不同),A 也要向Demo C登录;B向C登录。在Windows 7系统下,若打开A和B,A直接向B时钟同步请求,结果成功;若先打开A和C,A向C登录成功后,再打开B,A向B时钟同步请求,则失败。但该问题在开发环境PC XP系统下不会发生,后来在和另一位同事的共同努力下,最终找到了问题所在,并改掉了此Bug,不亦乐乎!

    下边我写了一个简易的测试程序,再现了造成时钟同步请求可能会失败的关键所在。

 

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "WS2_32")	// 链接到WS2_32.lib

/*

1,测试设置

	套接字sock1绑定地址信息:IP = 127.0.0.1,Port = 4567
	套接字sock2绑定地址信息:IP = INADDR_ANY,Port = 4567

2,测试结果:

	下列代码编译程序udp_bind.exe(Release)在XP系统下运行,只有sock1套接字发送数据成功,而sock2套接字在绑定时失败
	将udp_bind.exe在MS Server 2003系统下运行,则sock2和sock2均发送成功

3,测试总结:
	在使用UDP协议创建套接字,绑定本地地址时,本地地址IP应该使用同种模式,即INADDR_ANY,否则不同的套接字可能绑定到相同的端口

*/


SOCKET CreateSock(char *psLocalIP, unsigned short usLocalPort)
{
	//创建套节字
	SOCKET sock = ::socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if( INVALID_SOCKET == sock )
	{
		printf("Failed socket() %d \n", ::WSAGetLastError());
		return 0;
	}

	//绑定本地地址
	sockaddr_in addrLocal;
	memset(&addrLocal, 0, sizeof(addrLocal));
	addrLocal.sin_family = AF_INET;
	if ( '\0' == psLocalIP[0] )
	{
		addrLocal.sin_addr.s_addr = htonl(INADDR_ANY);
	}
	else
	{
		addrLocal.sin_addr.s_addr = inet_addr(psLocalIP);
	}
	addrLocal.sin_port = htons(usLocalPort);
	if ( 0 != bind(sock, (struct sockaddr*)&addrLocal, sizeof(addrLocal)) )
	{
		printf("Failed bind() %d \n", ::WSAGetLastError());
		closesocket(sock);
		return 0;
	}

	return sock;
}

int main(int argc, char* argv[])
{
	// 初始化WS2_32.dll
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2, 2);
	if(::WSAStartup(sockVersion, &wsaData) != 0)
	{
		exit(0);
	}

	SOCKET sock1 = CreateSock("127.0.0.1", 4567);
	if ( 0 == sock1 )
	{
		printf("Failed create sock1 \n");
	}

	//int iOn = 1;
	int iRet = 0;
	//iRet = setsockopt(sock1,SOL_SOCKET,SO_REUSEADDR,(char*)&iOn,sizeof(iOn));

	SOCKET sock2 = CreateSock("", 4567);
	if ( 0 == sock2 )
	{
		printf("Failed create sock2 \n");
	}

	//填写远程地址信息
	sockaddr_in addrRemote; 
	addrRemote.sin_family = AF_INET;
	addrRemote.sin_port = htons(5678);
	addrRemote.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");


	char szText[] = " Hello UDP! \r\n";
	iRet = ::sendto(sock1, szText, strlen(szText), 0, (sockaddr*)&addrRemote, sizeof(addrRemote));
	if ( SOCKET_ERROR == iRet )
	{
		printf("Failed sendto() sock1 %d \n", ::WSAGetLastError());
		closesocket(sock1);
	}
	else
	{
		printf("Successed sendto sock1 = %d, data lenth = %d \n", sock1, iRet);
	}

	iRet = ::sendto(sock2, szText, strlen(szText), 0, (sockaddr*)&addrRemote, sizeof(addrRemote));
	if ( SOCKET_ERROR == iRet )
	{
		printf("Failed sendto() sock2 %d \n", ::WSAGetLastError());
		closesocket(sock2);
	}
	else
	{
		printf("Successed sendto sock2 = %d, data lenth = %d \n", sock2, iRet);
	}
	system("pause");

	closesocket(sock1);
	closesocket(sock2);
	::WSACleanup();	
	return 0;
}

 

    下面是udp_bind.exe在XP系统下的运行结果截图

 

    下面是在Server 2003系统下的截图

 

    相同的代码在不同的系统下跑出不同的结果,令人有些费解!将上述测试代码中对应地方改为如下,就OK了。

   

	//if ( '\0' == psLocalIP[0] )
	//{
		addrLocal.sin_addr.s_addr = htonl(INADDR_ANY);
	//}
	//else
	//{
	//	addrLocal.sin_addr.s_addr = inet_addr(psLocalIP);
	//}


    看到这里,您也应该明白我们Demo程序中时钟同步的问题所在了吧?

    俗话说,细节决定成败。

    不论做产品,做项目,还是开发网络程序,都会面对很多细节问题,要重视你碰到的细节问题!

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来灵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值