IO模型分为四种,同步阻塞IO、同步非阻塞IO、IO多路复用,异步IO。采用select/ poll/ epoll都可以实现I/O多路复用模型,I/O多路复用就是指多个客户端的连接socket可以通过一个管理器来处理其读写等操作,一般配合同步非阻塞IO来使用。异步IO模型在Windows下可以通过iocp来实现。
1、select
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
nfds:windows下第一个参数被忽略,linux下为监听的套接字中最大的那个加1。
readfds:为监听读套接字集合,如果关心套接字的未决读事件则将套接字加入到这个集合中。
writefds:为监听写套接字集合,如果关心套接字的未决写事件则将套接字加入到这个集合中。
exceptfds:为监听异常套接字集合,如果关心套接字的未决异常事件则将套接字加入到这个集合中。
fd_set为套接字集类型,可以将一个套接字加入到这个集合中,当select返回的时候如果该套接字上没有发生相应的事件则会将该套接字从这个集合中移除。
timeout:超时时间,NULL为一直等待事件发生。
添加到fd_set结构的套接字数量是有限制的,通常是FD_SETSIZE。
windows下FD_SETSIZE在winsock2.h中定义为64,如果想要增加监听的套接字数量则可以自定义FD_SETSIZE的值(必须在包含winsock2,h之前定义),不过自定义的值也不能超过底层的最大支持(通常是1024)。 linux下FD_SETSIZE定义为1024,如果要修改其大小的话必须重新编译内核。
以下为Windows下select模型示例代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <stdio.h> #include <winsock2.h> #include <Ws2tcpip.h> #include <windows.h> #pragma comment(lib, "WS2_32") // 链接到WS2_32.lib int main() { WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 2); if (::WSAStartup(sockVersion, &wsaData) != 0) { int iErrno = WSAGetLastError(); printf("WSAStartup() failed with errno: %d ", iErrno); return -1; } USHORT nPort = 4567; // 此服务器监听的端口号 SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sListen == INVALID_SOCKET) { int iErrno = WSAGetLastError(); printf("socket() failed: %d ", iErrno); goto end; } unsigned long ul = 1; if(ioctlsocket(sListen, FIONBIO, &ul)) { int iErrno = WSAGetLastError(); printf("ioctlsocket() for listenSocket failed: %d ", iErrno); goto end; } sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(nPort); sin.sin_addr.S_un.S_addr = INADDR_ANY;//绑定本机所有IP if (::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) { int iErrno = WSAGetLastError(); printf(" Failed bind() :%d ", iErrno); goto end; } if (::listen(sListen, SOMAXCONN)) { int iErrno = WSAGetLastError(); printf(" Failed listen() :%d ", iErrno); goto end; } // select模型处理过程 // 1)初始化一个套节字集合fdSocket,用于存储监听套接字和所有的连接套接字 fd_set fdSocket; FD_ZERO(&fdSocket); FD_SET(sListen, &fdSocket);//添加监听套节字句柄到fdSocket套接字集合 fd_set fdRead; sockaddr_in addrRemote; int nAddrLen = sizeof(addrRemote); while (TRUE) { // 2)将fdSocket的一个拷贝fdRead传递给select函数的第二个参数,说明我们只关心这些套接字是否可读(监听套接字上有连接到来则监听套接字变为可读), //当fdRead中的套接字上有未决读事件发生时,select()返回,并将fdRead中没有未决I/O事件的套节字移除。 fdRead = fdSocket; int nRet = ::select(0, &fdRead, NULL, NULL, NULL); if (nRet > 0) { // 3)select()返回后,检查套接字是否还在fdRead中,在的话说明该套接字上有未读事件发生或有连接到来,然后进一步处理这些未决I/O。 for (int i = 0; i<(int)fdSocket.fd_count; i++) { if (FD_ISSET(fdSocket.fd_array[i], &fdRead)) { if (fdSocket.fd_array[i] == sListen) { if (fdSocket.fd_count < FD_SETSIZE) { memset(&addrRemote, 0, sizeof(addrRemote)); SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen); if (sNew == INVALID_SOCKET) { int iErrno = WSAGetLastError(); if (iErrno == WSAEWOULDBLOCK) { continue; } else { printf("accept() error: %d ", iErrno); for (int j = 0; j < (int)fdSocket.fd_count; j++) { ::closesocket(fdSocket.fd_array[j]); } goto end; } } else { unsigned long arg = 1; if (ioctlsocket(sNew, FIONBIO, &arg)) { int iErrno = WSAGetLastError(); printf("ioctlsocket() for clientSocket failed: %d ", iErrno); } FD_SET(sNew, &fdSocket); char ipBuf[64] = { 0 }; printf("accepted a connection(%s: %d) ", inet_ntop(AF_INET, (VOID*)&addrRemote.sin_addr, ipBuf, sizeof(ipBuf)), ntohs(addrRemote.sin_port)); } } else { printf(" Too much connections, can not accept ! "); continue; } } else { char szText[4096]; int nRecv = ::recv(fdSocket.fd_array[i], szText, sizeof(szText), 0); if (nRecv > 0) { szText[nRecv] = '