socket執行accept函數時沒有進入阻塞狀態,而是陷入了無限循環


接着前兩天繼續看《VC深入詳解》的網絡編程部分,這次我快速看了遍書上的函數以及套接字C-S模型,然后自己從0開始寫了個簡單的服務端,結果發現一直在輸出

而明明我還沒有寫客戶端程序,由於打印的代碼只有一處,在如下的while循環里

	while (true)
	{
 		/* 5. 接收客戶端發送的連接請求 */
 		SOCKET sockConnect = accept(sockServer, (SOCKADDR*)&addrClient, &len);
 		/* 6. [發送/接收]數據 */
		char sendBuf[BUFSIZ];
		char ipBuf[INET_ADDRSTRLEN];
		sprintf(sendBuf, "Welcome [IP: %s] to Server",
			inet_ntop(AF_INET, &addrClient.sin_addr, ipBuf, sizeof(ipBuf)));
 		send(sockConnect, sendBuf, sizeof(sendBuf), 0);

		char recvBuf[BUFSIZ];
		recv(sockConnect, recvBuf, BUFSIZ, 0);
		printf("Receive Data: %s\n", recvBuf);
		/* 7. 斷開連接,關閉套接字 */
		closesocket(sockConnect);
	}

引用《UNIX網絡編程》:accept函數由TCP服務器調用,用於從完成連接隊列頭返回下一個已完成連接。如果已完成連接隊列為空,那么進程被投入睡眠(假定套接字為默認的阻塞方式)。

調試發現,每一次accept函數都成功完成並執行后續代碼,所以才會有無限循環打印的現象。仔細對比書上代碼和說明,我的accept函數也沒有用錯,於是頭疼了很久。

How often have I said to you that when you have eliminated the impossible, whatever remains, however improbable, must be the truth?

既然我沒有得到程序錯誤的真相,那么說明我沒有排除掉所有的錯誤。回顧之前代碼,我跟示例代碼的區別就在於我把綁定IP和端口的代碼封裝起來了。

    /* 3. 綁定套接字到本地的地址和端口 */
    SOCKADDR_IN addrServer = SockAddr(6000, "127.0.0.1");
    /* 4. 將套接字設為監聽模式, 准備接收連接請求 */
    listen(sockServer, SOMAXCONN);
inline SOCKADDR_IN SockAddr(u_short port, PCSTR ip, int af = AF_INET)
{
	SOCKADDR_IN sockAddr_In;
	sockAddr_In.sin_family = af;
	sockAddr_In.sin_port = htons(port);
	auto& addr = sockAddr_In.sin_addr.S_un.S_addr;
	int err = inet_pton(af, ip, &addr);
	if (err == 0)
	{
		throw std::runtime_error("IP地址字節序從網絡轉換到主機出錯!");
	}
	else if (err == -1)
	{
		throw std::runtime_error("IP地址輸入格式無效!");
	}
	return sockAddr_In;
}

但是錯誤並沒有出在我的SockAddr函數,而是在於我沒有綁定!漏掉了關鍵的一行代碼

bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));

我僅僅只是創建了一個包含協議家族、IP地址、端口號的結構體SOCKADDR_IN,雖然也輸入了IP和端口信息,但是卻沒有把SOCKADDR_IN當做SOCKADDR類型(套接字的地址信息)和SOCKET(套接字本身)進行綁定,而沒有綁定的后果呢?

繼續引用《UNIX網絡編程》:如果一個TCP客戶或服務器未曾調用bind捆綁一個端口,當調用connect或listen時,內核就要為套接字選擇一個臨時端口。讓內核選擇臨時端口對於TCP服務器來說非常罕見,因為服務器是通過它們的眾所周知端口被大家認識。

調試發現sockConnect(也就是accept的返回值)是4294967295。稍微敏感的就會發現,這是32位無符號整型的上限值,也就是2^32-1,而SOCKET類型實際上就是UINT_PTR(unsigned int表示的指針,也就是32位地址)

typedef UINT_PTR        SOCKET;

再仔細看看《UNIX網絡編程》上關於accept函數的解釋

若出錯則為-1,UNIX上的accept是返回int,而windows上則是相當於返回unsigned int,-1的二進制表示就是每一位都為1,對應unsigned int的上限值(UINT_MAX)。所以剛才並不是每次都連接成功,而是每次都連接出錯(因為試圖用一個未綁定地址的服務器socket來接收客戶的連接請求),返回錯誤碼-1。如果accept正常,則是有客戶連接則返回一個表示連接的socket,沒有客戶連接則使進程睡眠直到有客戶連接(即阻塞)。

總結下來,根本原因還是socket沒有配置好就調用accept函數了,同樣,如果注釋掉listen那行,也會出現同樣的現象。

----------------------------------------------------------粗心的分割線----------------------------------------------------------

最后附上一點感悟,急於求成反而會浪費更多的時間,但也有個好處,如果照着書上代碼敲一遍,運行正確,然后再看看每個函數代表什么,也許當時就清楚了,但是后來出錯時可能就不知道到底掉到那個坑了。有出錯經驗也不錯。《UNIX網絡編程》確實是本不錯的書,對套接字API講得很詳細,像htnol還有inet_addr等轉換函數還是看這本書講得更清楚。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM