Winsock网络编程笔记:select()函数详解,select例子实现非阻塞TCPServer

Select I/O模型优缺点

优点:能从单个线程的多个套接字上进行多重连接,避免多线程的资源消耗。

缺点:fd_set结构中的最大套接字数量通常为64。


套接字集合:

fd_set   (defined in winsock2.h)

 fd_set结构可以把多个套接字集合在一起,形成一个套接字集合。select函数可以测试这个集合中哪些套接字有事件发生。

typedef struct fd_set
{
       u_int fd_count;                        // how many are SET

       SOCKET fd_array[FD_SETSIZE]; // an array of SOCKETs
} fd_set;

该结构内置函数:

nfd_count()    //Numbers of sockets in the set.

nfd_array[i]   //Array of sockets that are in the set.
Winsock定义的4个操作fd_set套接字集合的宏:

      

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

FD_CLR (s, *set) ----从set移除套接字s。

FD_ISSET (s, *set) ----检查s是否是set的成员,返回值是ture或false。

FD_SET (s, *set) ----添加套接字s到集合set。


select函数:

1. 用途

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

2. 大致原理

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

3. 函数定义

       该函数声明如下

int select(int nfds,  fd_set* readset,  fd_set* writeset,  fe_set* exceptset,  struct timeval* timeout);

nfds:需要检查的文件描述字个数,在Windows中这个参数值无所谓,可以设置不正确。

readset:readfds是指向fd_set结构的指针,用来检查可读性的一组文件描述字,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

writeset :writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。
exceptset : 用来检查是否有异常条件出现的文件描述字。(注:错误不包括在异常条件之内)
timeout  :  超时,填NULL为阻塞,填0为非阻塞,其他为一段超时时间。

 第一:若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;

  第二:若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;

  第三:timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。

 

返回值:

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


select例子实现非阻塞TCPServer:

#include"../common/initsock.h"//自己写的头文件
#include<iostream>
#include<cstring>
using namespace std;

CInitSock initSock;

int main()
{

	SOCKET Listen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	int port = 4567;
	sockaddr_in address;
	address.sin_addr.S_un.S_addr = INADDR_ANY;
	address.sin_family = AF_INET;
	address.sin_port = htons(port);

	if (::bind(Listen, (sockaddr*)&address, sizeof(address)) == SOCKET_ERROR)
	{
		cout << "绑定套接字失败"<< endl;
		return -1;
	}

	::listen(Listen, 5);

	fd_set fdSocket;
	FD_ZERO(&fdSocket);
	FD_SET(Listen, &fdSocket);

	while (true)
	{
		fd_set fdRead = fdSocket; //每次循环完毕都将fdSocket集合的所有套接字复制到fdRead中
		int sum = ::select(0, &fdRead, NULL, NULL, NULL);//只判断fdRead集合中有几个套接字处于都状态,有几个就返回几
		if (sum > 0) //select函数发现fdRead集合中有sum个套接字处于读状态
		{
			for (int i = 0; i < (int)fdSocket.fd_count; i++)//确定处于读状态的套接字到底是fdSocket中的哪一个
			{
				if (FD_ISSET(fdSocket.fd_array[i], &fdRead) == true)//在fdRead集合中检查到fdSocket.fd_array[i]的存在
				{
					if (fdSocket.fd_array[i] == Listen)//如果该套接字是监听套接字
					{
						if (fdSocket.fd_count < FD_SETSIZE)//FD_SETSIZE最大为64,也就是说最多只能放64个套接字
						{
							sockaddr_in RemoteAddress;
							int RemoteAddressLen = sizeof(RemoteAddress);
							SOCKET newSocket = ::accept(Listen, (SOCKADDR*)&RemoteAddress, &RemoteAddressLen);
							FD_SET(newSocket, &fdSocket);
							printf("收到来自地址为:(%s)的连接!\n", ::inet_ntoa(RemoteAddress.sin_addr));

						}
						else
						{
							cout << "fdSocket中的套接字放得太多啦!" << endl;
							continue;
						}
					 }
					else//如果该套接字不是监听套接字而是接受套接字
					{
						char data[256];
						int index = ::recv(fdSocket.fd_array[i], data, strlen(data) , 0);
						if (index > 0)
						{
							data[index] = '\0';
							printf("接收到数据: %s\n", &data);
						}
						else
						{
							printf("对方关闭了socket!\n");
							::closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i], &fdSocket);
						}
					}
				 }
			}
		}
		else
		{
			cout << "select函数未发现fdRead集合中有任何套接字处于读状态" << endl;
			break;
		}

	}

	return 0;
}

 

 

 

 

 

 

posted @ 2020-03-04 21:03  RomanticChopin  阅读(1295)  评论(0编辑  收藏  举报
-->
Live2D