关闭

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

标签: WINDOWS SELECTwindowsIO复用
825人阅读 评论(0) 收藏 举报
分类:

        本文主要介绍在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
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:306512次
    • 积分:5862
    • 等级:
    • 排名:第4344名
    • 原创:292篇
    • 转载:17篇
    • 译文:1篇
    • 评论:53条
    其它平台
    博客专栏