C++中I/O模型之select模型实例

青禾
青禾
发布时间:2026-10-14 | 5人浏览过
原创

本文通过一个具体的C++实例,详细解析了在网络编程中如何使用select模型来实现I/O多路复用。内容涵盖了从创建监听套接字到进入事件循环的完整流程,重点展示了如何通过fd_set集合监控多个Socket状态,分别处理客户端连接请求和数据读取,并包含必要的资源清理与错误处理。

Select模型核心原理

Select模型是Windows Socket (WinSock) API提供的一种多路I/O复用机制。它允许程序同时监控多个套接字(Socket)的状态变化(如是否有数据可读、是否可写或有错误发生)。其基本工作流程如下:

  1. 初始化集合:定义 fd_set 结构体,并使用 FD_ZERO 清空后,使用 FD_SET 将需要监控的Socket加入集合。
  2. 调用Select:将集合副本传递给 select 函数。该函数会阻塞直到集合中至少有一个Socket就绪,或者超时。
  3. 检查就绪情况:select 返回后,原集合会被修改,仅保留就绪的Socket。通过 FD_ISSET 遍历原始集合,判断哪些Socket触发了事件。
  4. 执行相应操作:根据触发的事件类型(如可读或可接受连接),执行相应的读写或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++网络编程的资料请关注本站其它相关文章!

相关标签:

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表