1. 程式人生 > >Windows上如何玩非阻塞的connect?---讓程式設計師自定義connect函式的超時時間

Windows上如何玩非阻塞的connect?---讓程式設計師自定義connect函式的超時時間

        我們知道, 對於阻塞的socket而言, connect函式也是阻塞的, 我在Windows上測試過, 對於阻塞的socket而言, connect的阻塞時間約為25s(linux上是75s吧, 各個平臺都不一樣).  也就是說, 很多時候, 客戶端需要等25s才繼續往下執行。 我們想象一下, 使用者肯定會不滿意啊, 得罪了使用者, 那就糟糕了。 那能不能搞個自己設定超時時間的connect函式呢? 完全可以! 在本文中, 我們來學習一下非阻塞connect函式的實現---讓程式設計師自定義connect函式的超時時間。

       說明: 兩年後, 當我再次審視這些程式的時候, 我發現, select後, 強烈建議做FD_ISSET檢查。

       直接上客戶端的程式碼:

#include <stdio.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
	// 網路初始化
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 2);
	WSAStartup( wVersionRequested, &wsaData );


	// 建立客戶端socket(預設為是阻塞socket)
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);


	// 設定為非阻塞的socket
	int iMode = 1;
	ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); 


	// 定義服務端
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(8888);


	// 超時時間
	struct timeval tm;
	tm.tv_sec  = 5;
	tm.tv_usec = 0;
	int ret = -1;
	

	// 嘗試去連線服務端
	if (-1 != connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)))
	{
		ret = 1; // 連線成功
	}
	else
	{
		fd_set set;
		FD_ZERO(&set);
		FD_SET(sockClient, &set);

		if (select(-1, NULL, &set, NULL, &tm) <= 0)
		{
			ret = -1; // 有錯誤(select錯誤或者超時)
		}
		else
		{
			int error = -1;
			int optLen = sizeof(int);
			getsockopt(sockClient, SOL_SOCKET, SO_ERROR, (char*)&error, &optLen); 
			
			// 之所以下面的程式不寫成三目運算子的形式, 是為了更直觀, 便於註釋
			if (0 != error)
			{
				ret = -1; // 有錯誤
			}
			else
			{
				ret = 1;  // 無錯誤
			}
		}
	}


	// 設回為阻塞socket
	iMode = 0;
	ioctlsocket(sockClient, FIONBIO, (u_long FAR*)&iMode); //設定為阻塞模式


	// connect狀態
	printf("ret is %d\n", ret);


	// 傳送資料到服務端測試以下
	if(1 == ret)
	{
		send(sockClient, "hello world", strlen("hello world") + 1, 0);
	}


	// 釋放網路連線
	closesocket(sockClient);
	WSACleanup();

	return 0;
}


      我們先不管服務端, 直接執行上面的程式, 過5s後, 程式結果為:ret is -1

      好, 我們關掉當面的客戶端。 並啟用下面的服務端:

#include <stdio.h>
#include <winsock2.h> // winsock介面
#pragma comment(lib, "ws2_32.lib") // winsock實現

int main()
{
	WORD wVersionRequested;  // 雙位元組,winsock庫的版本
	WSADATA wsaData;         // winsock庫版本的相關資訊
	
	wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257
	

	// 載入winsock庫並確定winsock版本,系統會把資料填入wsaData中
	WSAStartup( wVersionRequested, &wsaData );
	

	// AF_INET 表示採用TCP/IP協議族
	// SOCK_STREAM 表示採用TCP協議
	// 0是通常的預設情況
	unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN addrSrv;

	addrSrv.sin_family = AF_INET; // TCP/IP協議族
	addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket對應的IP地址
	addrSrv.sin_port = htons(8888); // socket對應的埠

	// 將socket繫結到某個IP和埠(IP標識主機,埠標識通訊程序)
	bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	// 將socket設定為監聽模式,5表示等待連線佇列的最大長度
	listen(sockSrv, 5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);

	printf("To receive...\n");
	char recvBuf[100] = {0};
	recv(sockConn, recvBuf, 100, 0); // 接收客戶端資料,最後一個引數一般設定為0
	printf("recv is %s\n", recvBuf);

	while(1);

	closesocket(sockConn);	
	closesocket(sockSrv);
	WSACleanup();
	
	return 0;
}

     然後呢, 我們再啟動客戶端, 發現客戶端立即出現:ret is 1, 服務端對應的結果為:

To receive...
recv is hello world

       由此可見, 上面的客戶端程式實現了非阻塞的connect, 也就是用, 程式設計師可以自定義超時時間。 ok, 先這樣。