C++Socket編程—socket網絡模型之select模型


一、select模型是什么

select模型是Windows sockets中最常見的IO模型。它利用select函數實現IO 管理。通過對select函數的調用,應用程序可以判斷套接字是否存在數據、能否向該套接字寫入據。

 

二、為什么要使用select模型?

解決基本C/S模型中,accept()、recv()、send()阻塞的問題,以及C/S模型需要創建大量線程,客戶端過多就會增加服務器運行壓力

 

三、select模型與C/S模型的不同點    

    • C/S模型中accept()會阻塞一直傻等socket來鏈接
    • select模型只解決accept()傻等的問題,不解決recv(),send()執行阻塞問題
    其實select模型解決了實現多個客戶端鏈接,與多個客戶端分別通信
    兩個模型都存在recv(),send()執行阻塞問題
    • 由於服務器端,客戶端不需要(客戶端只有一個socket,可以通過加線
    程解決同時recv和send)


select函數決定一個或者多個套接字(socket)的狀態,如果需要的話,等待執行異步I/O。

 

四、select模型的API函數

int select(
              __in     int    nfds,
              __inout    fd_set *readfds,
              __inout  fd_set *writefds,
              __inout  fd_set *exceptfds,
              __int       const struct timeval *timeout
              );


參數:
 nfds:忽略。
 readnfds: 指向檢查可讀性的套接字集合的可選的指針。
 writefds: 指向檢查可寫性的套接字集合的可選的指針。
 exceptfds: 指向檢查錯誤的套接字集合的可選的指針。
 timeout: select函數需要等待的最長時間,需要以TIMEVAL結構體格式提供此參數,對於阻塞操作,此參數為null。
 
返回值:
       select函數返回那些准備好並且包含在fd_set結構體的套接字的總數,如果超時,則返回0;如果錯誤發生,返回SOCKET_ERROR。如果返回值為SOCKET_ERROR,可以通過WSAGetLastError函數檢索指定的錯誤碼。

五、代碼示例

select模型下的客戶端服務器簡單收發數據通信:

客戶端:

int main()
{
// 	1) 創建socket
	SOCKET sockServer = socket(
		AF_INET,
		SOCK_STREAM, //流式 
		IPPROTO_TCP);//tcp協議


	// 	2) 綁定端口
	sockaddr_in siServer;
	siServer.sin_family = AF_INET;
	siServer.sin_port = htons(9527);
	siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer));
	if (nRet == SOCKET_ERROR)
	{
		printf("綁定失敗 \r\n");
		return 0;
	}

	// 	3) 監聽
	nRet = listen(sockServer, SOMAXCONN);
	if (nRet == SOCKET_ERROR)
	{
		printf("監聽失敗 \r\n");
		return 0;
	}
	
	//創建線程, 檢測socket是否有數據可讀, 並且處理
	vector<SOCKET> vctClients;
	HANDLE hTread = CreateThread(NULL, 0, HandleClientsThread, (LPVOID)&vctClients, 0, NULL);
	CloseHandle(hTread);
		
	while (true)
	{
		// 	4) 接受連接
		sockaddr_in siClient;
		int nSize = sizeof(siClient);
		SOCKET sockClient = accept(sockServer, (sockaddr*)&siClient, &nSize);
		if (sockClient == SOCKET_ERROR)
		{
			printf("接受連接失敗 \r\n");
			return 0;
		}
		printf("IP:%s port:%d 連接到服務器. \r\n",
			inet_ntoa(siClient.sin_addr),
			ntohs(siClient.sin_port));

		g_lock.Lock();  //加上線程鎖
		vctClients.push_back(sockClient);
		g_lock.UnLock();
	}
    return 0;
}

 服務器:

bool HandleData(SOCKET sockClient)
{
	// 	5) 收發數據
	char aryBuff[MAXWORD] = { 0 };
	int nRet = recv(sockClient, aryBuff, sizeof(aryBuff), 0);
	if (nRet == 0 || nRet == SOCKET_ERROR)
	{
		printf("接受數據失敗 \r\n");
		return false;
	}
	printf("收到數據: %s \r\n", aryBuff);

	char szBuff[] = { "recv OK " };
	nRet = send(sockClient, szBuff, sizeof(szBuff), 0);
	if (nRet == SOCKET_ERROR)
	{
		printf("數據發送失敗 \r\n");
		return false;
	}

	return true;
}

//線程, 用來處理客戶端, 和客戶端進行數據的收發
DWORD WINAPI HandleClientsThread(LPVOID pParam)
{
	vector<SOCKET>& vctClients = *(vector<SOCKET>*)pParam;

	while (TRUE)
	{
		fd_set fdRead;
		FD_ZERO(&fdRead); //初始化

		//將所有的socket放入數組
		g_lock.Lock();
		for (auto sock:vctClients)
		{
			FD_SET(sock, &fdRead);
		}
		g_lock.UnLock();

		//檢測指定的socket
		timeval tv = {1, };
		int nRet = select(fdRead.fd_count, &fdRead, NULL, NULL, &tv);
		if (nRet == 0 || nRet == SOCKET_ERROR) 
		{
			continue;
		}

		//處理數據
		g_lock.Lock();
		for (auto itr = vctClients.begin(); itr != vctClients.end(); ++itr)
		{
			//判斷socket是否是可以讀數據了
			if (FD_ISSET(*itr, &fdRead))
			{
				if (!HandleData(*itr))
				{
					//連接斷開
					vctClients.erase(itr);
					break;
				}
			}
		}
		g_lock.UnLock();
	}

	return 0;
}

 


免責聲明!

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



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