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; }