Windows 中Select网络模型

一、select函数
1. 用途
       在编程的过程中,经常会遇到许多阻塞的函数,好像read和网络编程时使用的recv, recvfrom函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码。这是就需要用到非阻塞的编程方式,使用select函数就可以实现非阻塞编程。
       select函数是一个轮循函数,循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行。

2.概念理解

       在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)

四种调用模式:

同步:所谓同步,就是在发出一个功能调用时,在没有得到结果前,该调用就不返回。也就是必须一件

一件做事,等前一件做完了才能做另一件。

例如在C/S模式的某个流程中,你服务器提交了某个请求,在服务器处理完毕返回结果期间客户端什么

也不能做。

异步:异步概念和同步相对。当一个异步过程调用发出后,调用者不会立刻得到结果。调用者在发出

调用后可以继续做自己的事,被调用者通过状态、通知来通知调用者,或者通过回调函数处理这个调用。

阻塞:阻塞调用是指调用结果返回前,当前线程会被挂起(当前线程处于非可执行状态,在这个状态下,

CPU不会给线程分配时间片,即线程暂停运行),函数只有在得到结果后才回返回。

非阻塞:非阻塞和阻塞的概念相对,是指不能立刻得到借过前,该函数不会阻塞当前进程,而回立刻返回。

区别:有人会把同步和阻塞调用等同起来,实际上他们是不同的,对于同步调用来说,很多时候当前调用

还是激活的,只是从逻辑上当前函数没有返回而已。阻塞的话当前线程会被挂起

3. select函数大致原理
       select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。
4. 函数定义
       该函数声明如下:

select模型中需要一个结构体fd_set,该结构体是一个socket的集合,我们可以看到该结构体的定义:

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


从这个定义中可以看到,结构体中主要保存了一个socket的数组和一个保存数组的大小变量; 
使用select模型主要使用函数select,该函数原型如下:

int select (
  int nfds,     //系统保留,无意义                      
  fd_set FAR * readfds,//可读的socket集合               
  fd_set FAR * writefds,//可写的socket集合
  fd_set FAR * exceptfds,//外带socket集合             
  const struct timeval FAR * timeout//该函数的超时值  
);

返回值:

       返回fd的总数,错误时返回SOCKET_ERROR

二、 fd_set结构体

      fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪个句柄可读。

       系统提供了FD_SET, FD_CLR, FD_ISSET, FD_ZERO进行操作,声明如下:

FD_SET(int fd, fd_set *fdset);       //将fd加入set集合
FD_CLR(int fd, fd_set *fdset);       //将fd从set集合中清除
FD_ISSET(int fd, fd_set *fdset);     //检测fd是否在set集合中,不在则返回0
FD_ZERO(fd_set *fdset);              //将set清零使集合中不含任何f

 实例代码:

std::vector<SOCKET> g_clients;//声明SOCKET类型容器,用来存储客户端_Csocket
int main()
{
	//启动Windows socket 2.x环境
	WORD ver = MAKEWORD(2, 2);
	WSADATA dat;
	WSAStartup(ver, &dat);
	//------------

	//-- 用Socket API建立简易TCP服务端
	// 1 建立一个socket 套接字
	SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	// 2 bind 绑定用于接受客户端连接的网络端口
	sockaddr_in _sin = {};
	_sin.sin_family = AF_INET;
	_sin.sin_port = htons(4567);//host to net unsigned short
	_sin.sin_addr.S_un.S_addr = INADDR_ANY;//inet_addr("127.0.0.1");
	if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
	{
		printf("错误,绑定网络端口失败...\n");
	}
	else {
		printf("绑定网络端口成功...\n");
	}
	// 3 listen 监听网络端口
	if (SOCKET_ERROR == listen(_sock, 5))
	{
		printf("错误,监听网络端口失败...\n");
	}
	else {
		printf("监听网络端口成功...\n");
	}

	while (true)
	{
		//伯克利 socket
		fd_set fdRead;
		fd_set fdWrite;
		fd_set fdExp;

		FD_ZERO(&fdRead);
		FD_ZERO(&fdWrite);
		FD_ZERO(&fdExp);

		FD_SET(_sock, &fdRead);
		FD_SET(_sock, &fdWrite);
		FD_SET(_sock, &fdExp);

		for (int n = (int)g_clients.size()-1; n >= 0 ; n--)
		{
			FD_SET(g_clients[n], &fdRead);//设置整个容器都是可读的
		}
		///nfds 是一个整数值 是指fd_set集合中所有描述符(socket)的范围,而不是数量
		///既是所有文件描述符最大值+1 在Windows中这个参数可以写0
		timeval t = {0,0};
		int ret = select(_sock + 1, &fdRead, &fdWrite, &fdExp, &t);//如果t为NULL时是阻塞模式,如果是0时即可返回,可以设置时间
		if (ret < 0)
		{
			printf("select任务结束。\n");
			break;
		}
		if (FD_ISSET(_sock, &fdRead))//判断创建的_sock是否在fdRead队列中
		{
			FD_CLR(_sock, &fdRead);//从fdRead中清除_sock
			// 4 accept 等待接受客户端连接
			sockaddr_in clientAddr = {};
			int nAddrLen = sizeof(sockaddr_in);
			SOCKET _cSock = INVALID_SOCKET;
			_cSock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);
			if (INVALID_SOCKET == _cSock)
			{
				printf("错误,接受到无效客户端SOCKET...\n");
			}
			g_clients.push_back(_cSock);//从尾部压入容器中
			printf("新客户端加入:socket = %d,IP = %s \n", (int)_cSock, inet_ntoa(clientAddr.sin_addr));
		}
        //如果客户端退出,从g_clients容器中查找要退出的客户端(find()),并删除(erase(iter))
		for (size_t n = 0; n < fdRead.fd_count; n++)
		{
			if (-1 == processor(fdRead.fd_array[n]))
			{
				auto iter = find(g_clients.begin(), g_clients.end(), fdRead.fd_array[n]);
				if (iter != g_clients.end())
				{
					g_clients.erase(iter);
				}
			}
		}
	}
   //关闭g_clients所有的socket
	for (size_t n = g_clients.size() - 1; n >= 0; n--)
	{
		closesocket(g_clients[n]);
	}
	// 8 关闭套节字closesocket
	closesocket(_sock);


 

猜你喜欢

转载自blog.csdn.net/qq_33263769/article/details/88713642