一、套接字模型
Winsock以两种模式执行I/O操作:阻塞和非阻塞。
在阻塞模式下,执行 I/O 的 Winsock 调用(如 send 和 recv)
直到操作完成才返回。在非阻塞模式下,Winsock 函数会立即返回。
1)阻塞模式
套接字创建时,默认工作在阻塞模式下。例如,对 recv 函数的调用会使程序进入等待状
态,直到接收到数据才返回。
阻塞套接字的好处是使用简单,但是当需要处理多个套接字连接时,就必须创建多个线
程,即典型的一个连接使用一个线程的问题,这给编程带来了许多不便。所以实际开发中使
用最多的还是下面要讲述的非阻塞模式。
2)非阻塞模式
非阻塞套接字使用起来比较复杂,但是却有许多优点。应用程序可以调用 ioctlsocket 函
数显式地让套接字工作在非阻塞模式下,如下代码所示。
u_long ul = 1;
SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
ioctlsocket(s, FIONBIO, (u_long *)&ul);
一旦套接字被置于非阻塞模式,处理发送和接收数据或者管理连接的 Winsock 调用将会
立即返回。
二、选择模型
select 模型是一个广泛在 Winsock 中使用的 I/O 模型。称它为 select 模型,是因为它主要是使用 select 函数来管理 I/O 的。这个模式的设计源于 UNIX 系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字
select 函数可以确定一个或者多个套接字的状态。如果套接字上没有网络事件发生,便
进入等待状态,以便执行同步 I/O。函数定义如下。
int select(
int nfds, // 忽略,仅是为了与Berkeley 套接字兼容
fd_set* readfds, // 指向一个套接字集合,用来检查其可读性
fd_set* writefds, // 指向一个套接字集合,用来检查其可写性
fd_set* exceptfds, // 指向一个套接字集合,用来检查错误
const struct timeval* timeout // 指定此函数等待的最长时间,如果为NULL,则最长时间为无限大
);
1.套接字集合
fd_set 结构可以把多个套接字连在一起,形成一个套接字集合。select 函数可以测试这个
集合中哪些套接字有事件发生。下面是这个结构在 WINSOCK2.h 中的定义。
typedef struct fd_set {
u_int fd_count; // 下面数组的大小
SOCKET fd_array[FD_SETSIZE]; // 套接字句柄数组
} fd_set;
下面是 WINSOCK 定义的 4 个操作 fd_set 套接字集合的宏。
FD_ZERO(*set) 初始化 set 为空集合。集合在使用前应该总是清空
FD_CLR(s, *set) 从 set 移除套接字 s
FD_ISSET(s, *set) 检查 s 是不是 set 的成员,如果是返回 TRUE
FD_SET(s, *set) 添加套接字到集合
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 CInitSock theSock; // 初始化Winsock库 2 int main() 3 { 4 USHORT nPort = 4567; // 此服务器监听的端口号 5 // 创建监听套接字 6 SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 7 sockaddr_in sin; 8 sin.sin_family = AF_INET; 9 sin.sin_port = htons(nPort); 10 sin.sin_addr.S_un.S_addr = INADDR_ANY; 11 // 绑定套接字到本地机器 12 if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) 13 { 14 printf(" Failed bind() "); 15 return -1; 16 } 17 // 进入监听模式 18 ::listen(sListen, 5); 19 // select 模型处理过程 20 // 1)初始化一个套接字集合fdSocket,添加监听套接字句柄到这个集合 21 22 fd_set fdSocket; // 所有可用套接字集合 23 FD_ZERO(&fdSocket); 24 FD_SET(sListen, &fdSocket); 25 while(TRUE) 26 { 27 // 2)将fdSocket集合的一个拷贝fdRead传递给select 函数, 28 // 当有事件发生时,select 函数移除fdRead集合中没有未决I/O操作的套接字句柄,然后返回。 29 fd_set fdRead = fdSocket; 30 int nRet = ::select(0, &fdRead, NULL, NULL, NULL); 31 if(nRet > 0) 32 { 33 34 // 3)通过将原来fdSocket 集合与select 处理过的fdRead集合比较, 35 // 确定都有哪些套接字有未决I/O,并进一步处理这些I/O。 36 37 for(int i=0; i<(int)fdSocket.fd_count; i++) 38 { 39 if(FD_ISSET(fdSocket.fd_array[i], &fdRead)) 40 { 41 if(fdSocket.fd_array[i] == sListen) // (1)监听套接字接收到新连接 42 { 43 if(fdSocket.fd_count < FD_SETSIZE) 44 { 45 sockaddr_in addrRemote; 46 int nAddrLen = sizeof(addrRemote); 47 SOCKET sNew = 48 ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen); 49 FD_SET(sNew, &fdSocket); 50 printf("接收到连接(%s) ", ::inet_ntoa(addrRemote.sin_addr)); 51 } 52 else 53 { 54 printf(" Too much connections! "); 55 continue; 56 } 57 } 58 else 59 { 60 char szText[256]; 61 int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0); 62 if(nRecv > 0) // (2)可读 63 { 64 szText[nRecv] = '