1. 程式人生 > >使用Socket套接字繫結函式bind的一個細節

使用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程式中時鐘同步的問題所在了吧?

    俗話說,細節決定成敗。

    不論做產品,做專案,還是開發網路程式,都會面對很多細節問題,要重視你碰到的細節問題!