网络编程(51)—— Windows下使用select进行IO复用

        本文主要介绍在Windows系统下使用select搭建回声服务端的方法。在之前的《网络编程(16)—— IO复用技术之select》一文中我们介绍了在Linux使用Select进行IO复用的方法。本文对其原理不再详述,旨在通过对比使用加强对select的理解和应用。整个Windows版的select服务端的代码如下,稍后我们将对关键代码进行解释。

// SelectServ.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include "stdio.h"
#include "winsock2.h"

#pragma comment(lib,"ws2_32.lib")
#define BUF_SIZE 30
#define SOCK_SIZE 20

void ErrorHandler(const char* message);

int _tmain(int argc, _TCHAR* argv[])
{
	SOCKET servSock,clntSock;
	SOCKADDR_IN servAddr,clntAddr;
	int clntAddrSz;
	int strLen;
	char buf[BUF_SIZE];

	fd_set	fds,copyRead;
	SOCKET socks[SOCK_SIZE];
	TIMEVAL tm;
	int sockNum = 0;
	unsigned long ul=1; 

	WSADATA	wsaData;
	if(WSAStartup(MAKEWORD(2,2),&wsaData)==SOCKET_ERROR)
	{
		ErrorHandler("WSAStartUp Error");
	}
	servSock=socket(AF_INET,SOCK_STREAM,0);
	if(servSock==INVALID_SOCKET)
		ErrorHandler("socket error");
	//将socket设置成非阻塞模式
	//ioctlsocket(servSock,FIONBIO,&ul);

	memset(&servAddr,0,sizeof(servAddr));
	servAddr.sin_family=AF_INET;
	servAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	servAddr.sin_port=htons(atoi("8888"));

	if(bind(servSock,(const sockaddr*)&servAddr,sizeof(servAddr))==SOCKET_ERROR)
	{
		ErrorHandler("bind error");
	}

	if(listen(servSock,SOCK_SIZE)==SOCKET_ERROR)
	{
		ErrorHandler("listen error");
	}
	//遍历socks,将所有的元素置于无效的socket
	for(int i=0;i<SOCK_SIZE;i++)
		socks[i] = INVALID_SOCKET;
	sockNum += 1;
	
	socks[0]=servSock;
	FD_ZERO(&fds);
	FD_SET(servSock,&fds);
	tm.tv_sec=100000;
	tm.tv_usec=0;
	
	while(1)
	{
		copyRead=fds;
		int selResult = select(sockNum,&copyRead,0,0,&tm);
		printf("select return...\n");
		if(selResult==-1)
			puts("select error");
		else if(selResult==0)
			puts("timeout!");
		else
		{
			//先判断是否是有新的客户端连接
			if(FD_ISSET(socks[0],&copyRead))
			{
				clntAddrSz = sizeof(clntAddr);
				clntSock = accept(servSock,(SOCKADDR*)&clntAddr,&clntAddrSz);
				//将socket设置成非阻塞模式
				ioctlsocket(clntSock,FIONBIO,&ul);
				for(int i=0;i<SOCK_SIZE;i++)
				{
					//遍历socks,在元素为无效的socket处插入客户端的socket
					if(socks[i] == INVALID_SOCKET)
					{
						FD_SET(clntSock,&fds);
						socks[i]=clntSock;
						sockNum++;
						break;
					}
				}
			}
			//遍历所有的客户端socket,0的位置为服务端的socket,所以从1开始
			for (int i=1;i<SOCK_SIZE;i++)
			{
				//如果是无效的socket 不必处理
				if(socks[i]==INVALID_SOCKET) continue;
				if(FD_ISSET(socks[i],&copyRead))
				{
					strLen=recv(socks[i],buf,BUF_SIZE,0);
					if(strLen <= 0)//客户端断开了连接
					{
						closesocket(socks[i]);
						//从fds删除客户端socket
						FD_CLR(socks[i],&fds);
						//将对应的位置再次置为无效socket
						socks[i] == INVALID_SOCKET;
						sockNum--;
					}
					else if(strLen > 0)
					{
						send(socks[i],buf,strLen,0);
					}
				}
			}
		}
	}
	closesocket(servSock);
	return 0;
}

void ErrorHandler(const char* message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

第22~26行,声明使用select相关的变量。包括存放socket的集合fd_set和它的副本copyRead;声明超时tm,以及用来进行socket计数的sockNum;ul在后面设置非阻塞socket时将会用到。
第53~55行,遍历数组socks,将每个数组元素都置为无效的socket,为的是后面的循环判断哪个socket有需要接受的数据时,遇到无效的socket直接跳过,可参照第98行代码。
第56行,因为已经创建了一个服务端的socket,所以sockNum要加1。
第58~62行,将服务端的socket代码放到socks的第一个元素的位置,然后利用宏对fds进行清空,并将servSock注册到fds中,并设置超时时间。
第66行,复制fds至copyRead。每次select之后,select监视的socket集合都会发生状态的变化,且不能复原,因此都需要我们在select之前传递一个集合的副本,以防止传递集合时数据被破坏。
第67行,使用select返回接收到数据的socket
第76行,因为我们将servSock放到了socks中的第一个元素,所以先判断是不是socks[0]发生状态变化,也就是说先判断是不是有新的客户端连接。
第81行,将客户端的socket设置成非阻塞模式,这点很重要,否则在101行读取客户端的数据时就会被阻塞,无法同时和多个客户端进行通信。
第82~91行,遍历socks,只在包含无效socket的位置插入socket。同时,每接收到一个新的客户端的连接,sockNum都要加1.
第95~114行,遍历socks,找到接收到数据的客户端的socket,并进行相应的读写。因为socks[0]存放的是servSock,我们之前已经处理过,所以这里的下标i从1开始。



Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL51



  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值