windows下多路io select模型编程

本文介绍Windows Sockets编程及Select模型的应用。通过一个服务端和客户端的例子展示了如何使用Select来处理多客户端连接,实现非阻塞I/O,提高网络应用的效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

select(
    _In_ int nfds,//为了兼容的参数,忽略填0
    _Inout_opt_ fd_set FAR * readfds,//可读套接字集合指针
    _Inout_opt_ fd_set FAR * writefds,//可写套接字集合指针
    _Inout_opt_ fd_set FAR * exceptfds,//错误套接字集合指针
    _In_opt_ const struct timeval FAR * timeout//select等待时间

    );


readfds数组将包括满足以下条件的套接字:

1有数据可读

2连接已经关闭、重设或终止

3正在请求建立连接的套接字(listfd),此时调用accept函数会直接成功,accept相当于非阻塞的

writefds数组包含满足下列条件的套接字:

1有数据可以发送,此时在此sockfd上调用send,可以向对方发送数据。

2调用connect函数,并连接成功的sockfd


windows sockets提供了下列宏,用来对fd_set进行一系列操作

FD_CLR(fd,*set);set集合中删除sockfd套接字

FD_ISSET(fd,*set);检查sockfd是否为set集合的成员

FD_SET(fd,*set);sockfd加入到set集合中

FD_ZERO(*set);set集合初始化为空集合


代码编程如下

server

#include <stdio.h>
#include<WinSock2.h>
#include <WS2tcpip.h>

#pragma comment(lib,"ws2_32.lib")	//加载ws2_32.lib库

int main()
{
	WSADATA wsadata;
	WORD SockVersion = MAKEWORD(2,2);//初始化socker的版本
	if (WSAStartup(SockVersion,&wsadata)!=0)//WSAStartup是绑定socket版本号,指定操作系统调用那个版本的方法
	{
		printf("WSAStartup fail\n");
		return -1;
	}

	//创建scoket
	SOCKET ScoketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (ScoketFd == INVALID_SOCKET)
	{
		printf("socket error !");
		return -1;
	}

	//绑定IP和端口
	struct sockaddr_in SockAddr;
	SockAddr.sin_family = AF_INET;
	SockAddr.sin_port = htons(1111);
	SockAddr.sin_addr.S_un.S_addr = INADDR_ANY;
	if (bind(ScoketFd, (SOCKADDR*)&SockAddr, sizeof(SOCKADDR)) == SOCKET_ERROR)
	{
		printf("bind error !\n");
		return -1;
	}

	//开始监听
	if (listen(ScoketFd, 10) == SOCKET_ERROR)
	{
		printf("listen error !\n");
		return -1;
	}
	
	// select模型  
	fd_set AllSocketSet;
	FD_ZERO(&AllSocketSet);
	FD_SET(ScoketFd,&AllSocketSet);//将ScoketFd加入sock集合
	int ret = 0;


	//接收数据
	SOCKET ClientConFd;
	struct sockaddr_in ClientAddr;
	int nSize = sizeof(ClientAddr);
	char RevData[1024] = { 0 };
	while (1)
	{
		//设置监听fd可读事件
		fd_set ReadFd;
		fd_set WriteFd;
		FD_ZERO(&ReadFd);
		FD_ZERO(&WriteFd);
		ReadFd = AllSocketSet;
		WriteFd = AllSocketSet;

		ret = select(0, &ReadFd, &WriteFd, NULL, NULL);//第一个参数nfds被忽略,是为了兼容版本
		if (ret == SOCKET_ERROR)
		{
			printf("select error\n");
			return -1;
		}
		
		if (FD_ISSET(ScoketFd, &ReadFd))	//检查ScoketFd是否在这个集合里面, 
		{					//select将更新这个集合,把其中不可读的套节字去掉 
							//只保留符合条件的套节字在这个集合里面 
											
			printf("start accept new connect\n");
			ClientConFd = accept(ScoketFd, (SOCKADDR*)&ClientAddr, &nSize);//等待连接,连接后会产生一个行的fd
			if (ClientConFd == INVALID_SOCKET)
			{
				printf("accept error !\n");
				return -1;
			}
			else
			{
				FD_SET(ClientConFd, &AllSocketSet);//将新产生的fd加入到原先的集合中
			}

		}

		for (u_int i = 1; i < AllSocketSet.fd_count; ++i)//第一个fd是监听连接的fd,第二个开始是连接上的fd
		{

			if (FD_ISSET(AllSocketSet.fd_array[i], &ReadFd))//通过轮询原来的集合,检查ScoketFd是否在ReadFd集合中
			{
				printf("接收一个连接:%s \n", inet_ntoa(ClientAddr.sin_addr));
				//接收数据
				ret = recv(AllSocketSet.fd_array[i], RevData, 1024, 0);
				if (ret == SOCKET_ERROR)
				{
					DWORD err = WSAGetLastError();
					if (err == WSAECONNRESET)// 客户端的socket没有被正常关闭,即没有调用closesocket
					{
						printf("Client is forced to close\n");
						closesocket(AllSocketSet.fd_array[i]);
						FD_CLR(AllSocketSet.fd_array[i], &AllSocketSet);//把AllSocketSet.fd_array[i]从AllSocketSet集合中删除
						break;
					}
					else
					{
						printf("recv fail\n");
					}
					
				}
				else if (ret == 0)//客户端的socket正常关闭
				{
					closesocket(AllSocketSet.fd_array[i]);
					FD_CLR(AllSocketSet.fd_array[i], &AllSocketSet);
					printf("Client closes normally");
				}
				else
				{
					printf("%s\n", RevData);//打印读取的数据
				}
				
			}

			if (FD_ISSET(AllSocketSet.fd_array[i], &WriteFd))
			{
				//发送数据
				char * SendData = "hello,TcpClient";
				send(AllSocketSet.fd_array[i], SendData, strlen(SendData), 0);
				
			}
		}	
		
	}

	for (u_int j = 0; j < AllSocketSet.fd_count; ++j)
	{
		SOCKET socket = AllSocketSet.fd_array[j];
		closesocket(socket);
	}
	WSACleanup();//终止 DLL 的使用
	
	return 0;
}

client:测试的时候我用同样的客户端代码,生成多个客户端访问服务端

#include <WINSOCK2.H>
#include <STDIO.H>

#pragma  comment(lib, "ws2_32.lib")


int main()
{
	WSADATA wsadata;
	WORD SockVersion = MAKEWORD(2, 2);//初始化socker的版本
	if (WSAStartup(SockVersion, &wsadata) != 0)//WSAStartup是绑定socket版本号,指定操作系统调用那个版本的方法
	{
		printf("WSAStartup fail\n");
		return -1;
	}

	//创建scoket
	SOCKET ScoketFd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (ScoketFd == INVALID_SOCKET)
	{
		printf("socket error !");
		return -1;
	}

	//连接服务端
	struct sockaddr_in ServerAddr;
	ServerAddr.sin_family = AF_INET;
	ServerAddr.sin_port = htons(1111);
	ServerAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	if (connect(ScoketFd, (SOCKADDR *)&ServerAddr, sizeof(ServerAddr)) == SOCKET_ERROR)
	{
		printf("connect error !\n");
		closesocket(ScoketFd);
		return 0;
	}
	//发送数据到服务端
	char * sendData = "hello,Tcp Server,i'm client";
	send(ScoketFd, sendData, strlen(sendData), 0);

	//读取服务端数据
	char revData[1024] = {0};
	int ret = recv(ScoketFd, revData, 1024, 0);
	if (ret > 0)
	{
		printf("%s\n", revData);
	}
	getchar();
	closesocket(ScoketFd);
	WSACleanup();
	return 0;
}

结果如图




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值