Select模型原理
利用select函数,判断套接字上是否存在数据,或者能否向一个套接字写入数据。目的是防止应用程序在套接字处于锁定模式时,调用recv(或send)从没有数据的套接字上接收数据,被迫进入阻塞状态。
select参数和返回值意义如下:
int select (
IN int nfds, //0,无意义
IN OUT fd_set* readfds, //检查可读性
IN OUT fd_set* writefds, //检查可写性
IN OUT fd_set* exceptfds, //例外数据
IN const struct timeval* timeout); //函数的返回时间
struct timeval {
long tv_sec; //秒
long tv_usec; //毫秒
};
select返回fd_set中可用的套接字个数。
fd_set是一个SOCKET队列,以下宏可以对该队列进行操作:
FD_CLR( s, *set) 从队列set删除句柄s;
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中;
FD_SET( s, *set )把句柄s添加到队列set中;
FD_ZERO( *set ) 把set队列初始化成空队列.
Select工作流程
1:用FD_ZERO宏来初始化我们感兴趣的fd_set。
也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
如果想要检查一个套接字是否有数据需要接收,可以用FD_SET宏把套接接字句柄加入可读性检查队列中
3:调用select函数。
如果该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,
4:用FD_ISSET对套接字句柄进行检查。
如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么说明马上可以进行相应的IO操 作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的fd_set里,那么说明当前数据已经来了, 马上可以读取成功而不会被阻塞。
#include <winsock2.h> #include <stdio.h> #define PORT 5150 #define MSGSIZE 1024 #pragma comment(lib, "ws2_32.lib") int g_iTotalConn1 = 0; int g_iTotalConn2 = 0; SOCKET g_CliSocketArr1[FD_SETSIZE]; SOCKET g_CliSocketArr2[FD_SETSIZE]; DWORD WINAPI WorkerThread1(LPVOID lpParam); int CALLBACK ConditionFunc1(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData); DWORD WINAPI WorkerThread2(LPVOID lpParam); int CALLBACK ConditionFunc2(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR * g,DWORD dwCallbackData); int main(int argc, char* argv[]) { WSADATA wsaData; SOCKET sListen, sClient; SOCKADDR_IN local, client; int iAddrSize = sizeof(SOCKADDR_IN); DWORD dwThreadId; // Initialize windows socket library WSAStartup(0x0202, &wsaData); // Create listening socket sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); // Bind local.sin_family = AF_INET; local.sin_addr.S_un.S_addr = htonl(INADDR_ANY); local.sin_port = htons(PORT); bind(sListen, (sockaddr*)&local, sizeof(SOCKADDR_IN)); // Listen listen(sListen, 3); // Create worker thread CreateThread(NULL, 0, WorkerThread1, NULL, 0, &dwThreadId); // CreateThread(NULL, 0, WorkerThread2, NULL, 0, &dwThreadId); while (TRUE) { sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc1, 0); printf("1:Accepted client:%s:%d:%d ", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn1); g_CliSocketArr1[g_iTotalConn1++] = sClient; /* sClient = WSAAccept(sListen, (sockaddr*)&client, &iAddrSize, ConditionFunc2, 0); printf("2:Accepted client:%s:%d:%d ", inet_ntoa(client.sin_addr), ntohs(client.sin_port), g_iTotalConn2); g_CliSocketArr2[g_iTotalConn2++] = sClient; */ } return 0; } DWORD WINAPI WorkerThread1(LPVOID lpParam) { int i; fd_set fdread; int ret; struct timeval tv = {1, 0}; char szMessage[MSGSIZE]; while (TRUE) { FD_ZERO(&fdread); //1清空队列 for (i = 0; i < g_iTotalConn1; i++) { FD_SET(g_CliSocketArr1[i], &fdread); //2将要检查的套接口加入队列 } // We only care read event ret = select(0, &fdread, NULL, NULL, &tv); //3查询满足要求的套接字,不满足要求,出队 if (ret == 0) { // Time expired continue; } for (i = 0; i < g_iTotalConn1; i++) { if (FD_ISSET(g_CliSocketArr1[i], &fdread)) //4.是否依然在队列 { // A read event happened on g_CliSocketArr ret = recv(g_CliSocketArr1[i], szMessage, MSGSIZE, 0); if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET)) { // Client socket closed printf("1:Client socket %d closed. ", g_CliSocketArr1[i]); closesocket(g_CliSocketArr1[i]); if (i < g_iTotalConn1-1) { g_CliSocketArr1[i--] = g_CliSocketArr1[--g_iTotalConn1]; } } else { // We reveived a message from client szMessage[ret] = '