多路I/O转接服务器(一):select以及其两种实现方式

多路IO转接服务器也叫做多任务IO服务器。该类服务器实现的主旨思想是,不再由应用程序自己监视客户端连接。

通过三个例子,来举例:

例一例二是在windows平台下的,例三兼容多平台。

1、无select的socket编程

#define FD_SETSIZE 1024
#include <WinSock2.h>
#include <iostream>
#include <vector>


#define PORT 6666
#define IPSTR "127.0.0.1"
int main()
{
	int iRet = 0;
	WORD vr = (2, 2);
	WSADATA dta;
	iRet = WSAStartup(vr, &dta);
	/*错误处理*/
	SOCKET sock = INVALID_SOCKET;
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	/*错误处理*/
	sockaddr_in sin = {};
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	iRet = bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
	/*错误处理*/
	iRet = listen(sock, 5);
	/*错误处理*/
	SOCKET csock = INVALID_SOCKET;
	//普通连接
	sockaddr_in csin = {};
	int addrlen = sizeof(sockaddr_in);
	csock = accept(sock, (sockaddr*)&csin, &addrlen);
	while (1)
	{
		char buf[256];
		iRet = recv(csock, buf, sizeof(buf), 0);    //阻塞位置
		std::cout << buf << std::endl;
	}
	
	closesocket(sock);
	WSACleanup();
}

在普通连接中,会在iRet = recv(csock, buf, sizeof(buf), 0)处阻塞。

2、select

#define FD_SETSIZE 1024
#include <WinSock2.h>
#include <iostream>
#include <vector>


#define PORT 6666
#define IPSTR "127.0.0.1"
int main()
{
	int iRet = 0;
	WORD vr = (2, 2);
	WSADATA dta;
	iRet = WSAStartup(vr, &dta);
	/*错误处理*/
	SOCKET sock = INVALID_SOCKET;
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	/*错误处理*/
	sockaddr_in sin = {};
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	iRet = bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
	/*错误处理*/
	iRet = listen(sock, 5);
	/*错误处理*/
	SOCKET csock = INVALID_SOCKET;
	//普通select连接
	fd_set fdSocket;
	FD_ZERO(&fdSocket);
	FD_SET(sock, &fdSocket);
	SOCKET maxfd = sock;
	while (1)
	{
		fd_set fdRead = fdSocket;        //每次循环时都从新设置select监控信号集
		timeval val = { 0,10 };
		iRet = select(maxfd +1, &fdRead, 0, 0, &val);  //为了兼容其他系统
		if (iRet < 0)
		{
			std::cout << "select出错" << std::endl;
			break;
		}
		if (iRet > 0)
		{
			if (FD_ISSET(sock, &fdRead))    //说明有新的客户端链接请求
			{
				if (fdRead.fd_count < FD_SETSIZE) {
					sockaddr_in csin = {};
					int addrlen = sizeof(sockaddr_in);
					csock = accept(sock, (sockaddr*)&csin, &addrlen);
					std::cout << "客户端连接:" << csock << std::endl;
					FD_SET(csock,&fdSocket);
				}
				else
				{
					std::cout << "超出连接范围" << std::endl;
				}
			}
			else
			{
				for (size_t i = 0; i < fdSocket.fd_count; i++)
				{
					if (FD_ISSET(fdSocket.fd_array[i], &fdRead))
					{
						char buf[256];
						iRet = recv(fdSocket.fd_array[i], buf, sizeof(buf), 0);
						if (iRet <= 0)
						{
							closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i], &fdSocket);
							
						}
						else
						{
							std::cout << "来自" << fdSocket.fd_array[i] << ":" << buf << std::endl;
						}

					}
				}
			}
		}
	}
		closesocket(sock);
	WSACleanup();
}

 几个主要函数和结构:

WINSOCK_API_LINKAGE
int
WSAAPI
select(
    _In_ int nfds,                           
    _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
    );

返回值
int 若有就绪描述符返回其数目,若超时则为0,若出错则为-1

typedef struct fd_set {
        u_int fd_count;               /* how many are SET? */
        SOCKET  fd_array[FD_SETSIZE];   /* an array of SOCKETs */
} fd_set;


struct timeval {
        long    tv_sec;         /* seconds */
        long    tv_usec;        /* and microseconds */
};
timeval 置为NULL,即为阻塞。

void FD_CLR(int fd, fd_set *set);      //把文件描述符集合里fd清0
int  FD_ISSET(int fd, fd_set *set);    //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set);      //把文件描述符集合里fd位置1 
void FD_ZERO(fd_set *set);             //把文件描述符集合里所有位清0

在win32中,select的参一nfds没有意义,可以直接设置为0,

但在linux系统中,参数1代表被监听的文件描述符的总数,它比所有文件描述符集合中的文件描述符的最大值大1,因为文件描述符是从0开始计数的,为了之后可以多平台兼容,所以设置了maxfd+1;

另外,在win32中FD_SETSIZE的初始值为64,可以在开头添加#define FD_SETSIZE 1024开修改。

maxfd:起初服务端的监听socket的即为最大文件描述符,之后通过比较新加的csock来确定新的maxfd。

fd_set结构包括fd的数量以及由socket组成的集合,

2、select方式二

#ifdef _WIN32
	#define FD_SETSIZE      1024
        #include <WinSock2.h>
#else
	#include<unistd.h> //uni std
	#include<arpa/inet.h>
	#define SOCKET int
	#define INVALID_SOCKET  (SOCKET)(~0)
	#define SOCKET_ERROR            (-1)
#endif
#include <iostream>
#include <vector>

#define PORT 6666
#define IPSTR "127.0.0.1"
int main()
{
	int iRet = 0;
#ifdef _WIN32
	WORD vr = (2, 2);
	WSADATA dta;
	iRet = WSAStartup(vr, &dta);
#endif
	/*错误处理*/
	SOCKET sock = INVALID_SOCKET;
	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	/*错误处理*/
	sockaddr_in sin = {};
	sin.sin_family = AF_INET;
	sin.sin_port = htons(PORT);
#ifdef _WIN32
        sin.sin_addr.S_un.S_addr = INADDR_ANY;
#else
	sin.sin_addr.s_addr = INADDR_ANY;
#endif
	iRet = bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in));
	/*错误处理*/
	iRet = listen(sock, 5);
	/*错误处理*/
	SOCKET csock = INVALID_SOCKET;

	std::vector<SOCKET> client;
	fd_set fdSocket;
	FD_ZERO(&fdSocket);
	FD_SET(sock, &fdSocket);
	int maxfd = sock;
	int maxi = -1;
	while (1)
	{
		fd_set fdRead = fdSocket;
		timeval val = { 0,10 };
		iRet = select(sock + 1, &fdRead, 0, 0, &val);
		if (iRet < 0)
		{
			std::cout << "select出错" << std::endl;
			break;
		}
		if (iRet > 0)
		{
			if (FD_ISSET(sock, &fdRead))
			{
				sockaddr_in csin = {};
				int addrlen = sizeof(sockaddr_in);
#ifdef _WIN32
	                        csock = accept(sock, (sockaddr*)&csin, &addrlen);
#else
	                        csock = accept(sock, (sockaddr*)&csin, (socklen_t *)&addrlen);
#endif
				std::cout << "客户端连接:" << csock << std::endl;
				client.push_back(csock);
				if (client.size() == FD_SETSIZE)
				{
					std::cout << "超过连接限制" << std::endl;
					break;
				}
				FD_SET(csock,&fdSocket);
				if (maxfd < csock)
				{
					maxfd = csock;
				}
				if (maxi < client.size())
				{
					maxi = client.size();
				}
			}

			for (auto it = client.begin(); it != client.end(); it++)
			{
				if (FD_ISSET(*it, &fdRead))
				{
					char buf[256];
					iRet = recv(*it, buf, sizeof(buf), 0);
					if (iRet == 0)
					{
#ifdef _WIN32
						closesocket(*it);
#else
						close(*it);
#endif
						it = client.erase(it);
						std::cout << *it << "断开连接" << std::endl;

					}
					else if(iRet > 0)
					{
						std::cout << "来自" << *it << ":" << buf << std::endl;
					}
				}
			}

		}
	}
	

#ifdef _WIN32
	closesocket(sock);
	WSACleanup();
#else
	close(sock);
#endif
}


多用于类unix系统

在linux中:

文件描述符:

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。

程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。如果此时去打开一个新的文件,它的文件描述符会是3。

select机制的问题

1、每次调用select,都需要把fd_set集合从用户态拷贝到内核态,如果fd_set集合很大时,那这个开销也很大

2、同时每次调用select都需要在内核遍历传递进来的所有fd_set,如果fd_set集合很大时,那这个开销也很大

3、为了减少数据拷贝带来的性能损坏,内核对被监控的fd_set集合大小做了限制,并且这个是通过宏控制的,大小不可改变(限制为1024)

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值