C++中I/O模型之select模型实例
本文通过一个具体的C++实例,详细解析了在网络编程中如何使用select模型来实现I/O多路复用。内容涵盖了从创建监听套接字到进入事件循环的完整流程,重点展示了如何通过fd_set集合监控多个Socket状态,分别处理客户端连接请求和数据读取,并包含必要的资源清理与错误处理。
Select模型核心原理
Select模型是Windows Socket (WinSock) API提供的一种多路I/O复用机制。它允许程序同时监控多个套接字(Socket)的状态变化(如是否有数据可读、是否可写或有错误发生)。其基本工作流程如下:
- 初始化集合:定义
fd_set结构体,并使用FD_ZERO清空后,使用FD_SET将需要监控的Socket加入集合。 - 调用Select:将集合副本传递给
select函数。该函数会阻塞直到集合中至少有一个Socket就绪,或者超时。 - 检查就绪情况:
select返回后,原集合会被修改,仅保留就绪的Socket。通过FD_ISSET遍历原始集合,判断哪些Socket触发了事件。 - 执行相应操作:根据触发的事件类型(如可读或可接受连接),执行相应的读写或Accept操作。
实现步骤与代码详解
第1步:初始化监听Socket
首先需要创建一个TCP Socket,绑定到指定端口并设置为监听状态。这是所有连接的入口。
void main()
{
// 假设 CInitSock 用于初始化Winsock库
CInitSock initSock;
USHORT nPort = 9999; // 监听的端口
SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sListen == INVALID_SOCKET)
{
printf("socket error...");
return;
}
sockaddr_in servAddr = {0};
servAddr.sin_family = AF_INET;
servAddr.sin_port = ::htons(nPort);
servAddr.sin_addr.S_un.S_addr = INADDR_ANY;
if (SOCKET_ERROR == ::bind(sListen, (sockaddr*)&servAddr, sizeof(servAddr)))
{
int nError = ::GetLastError();
printf("connect error..");
return;
}
::listen(sListen, 5);
此段代码完成了基本的服务端搭建。注意 CInitSock 通常用于自动管理 Winsock 的 WSAStartup 和 WSACleanup,确保环境就绪。
第2步:配置Select监控集合
在开始主循环前,需要将监听套接字加入文件描述符集合。由于 Select 模型中,每次循环都需要重新设置集合,因此通常保持一个“全量”集合和每次传递前的“拷贝”集合。
//select模型处理过程
//1.初始化套接字集合,添加监听socket到这个集合
fd_set fdSocket;
FD_ZERO(&fdSocket);
FD_SET(sListen, &fdSocket);
第3步:进入主循环处理事件
这是Select模型的核心。在无限循环中,我们将当前维护的Socket集合复制一份传给 select。当有事件发生时,select 会修改这份副本,返回就绪的Socket数量。
while (TRUE)
{
//2. 将集合的一个拷贝传递给select函数
//当有事件发生时,select移除未决的socket,然后返回
fd_set fdRead = fdSocket;
int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
if (nRet > 0)
{
//3. 遍历原始集合,找出哪些Socket在fdRead中
for (UINT i=0;i<fdSocket.fd_count;i++)
{
if (FD_ISSET(fdSocket.fd_array[i], &fdRead))
{
// ... 后续处理逻辑 ...
}
}
}
else
{
printf("nRet litter 0, error...");
return;
}
}
第4步:区分连接请求与数据接收
当 select 指示某个Socket可读时,可能有两种情况:一是监听Socket有新连接接入;二是已连接的Socket有数据到达。需要通过判断Socket是否为监听Socket来区分。
处理新连接
如果触发的是 sListen,则调用 accept 接受连接。注意Select模型对最大连接数有限制(FD_SETSIZE,默认为64)。
if (fdSocket.fd_array[i] == sListen)
{
if (fdSocket.fd_count < FD_SETSIZE)
{
sockaddr_in addrRemote = {0};
int nAddrLen = sizeof(addrRemote);
SOCKET sNew = ::accept(sListen, (sockaddr*)&addrRemote, &nAddrLen);
FD_SET(sNew, &fdSocket); // 将新连接的Socket加入监控集合
}
else
{
printf("too many connection...error");
continue;
}
}
处理数据读取
如果是其他Socket触发,说明有数据可读。调用 recv 接收数据。若接收失败或关闭,需从集合中移除并关闭Socket。
else
{
char szContent[256]={0};
int nRecv = ::recv(fdSocket.fd_array[i], szContent, sizeof(szContent), 0);
if (nRecv > 0)
{
szContent[nRecv] = '\0';
printf("recv data:%s", szContent);
}
else
{
::closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i], &fdSocket); // 从监控集合中移除
}
}
第5步:资源清理
程序结束或异常退出时,应关闭监听Socket并释放资源。
::closesocket(sListen);
printf("*******************************");
getchar();
}
以上就是C++中I/O模型之select模型实例的详细内容,更多关于C++网络编程的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













