zoukankan      html  css  js  c++  java
  • 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)的连接!
    ", ::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] = '';
    							printf("接收到数据: %s
    ", &data);
    						}
    						else
    						{
    							printf("对方关闭了socket!
    ");
    							::closesocket(fdSocket.fd_array[i]);
    							FD_CLR(fdSocket.fd_array[i], &fdSocket);
    						}
    					}
    				 }
    			}
    		}
    		else
    		{
    			cout << "select函数未发现fdRead集合中有任何套接字处于读状态" << endl;
    			break;
    		}
    
    	}
    
    	return 0;
    }
  • 相关阅读:
    ArcGIS for window mobile 数据打开
    Linux学习拾遗
    ArcEngine 连接sql server sde
    Server Objects Extension(SOE)开发(三)
    Server Objects Extension(SOE)开发(二)
    C# 调用ArcGIS server admin api
    切片文件发布成切片服务
    Printing tools 自定义模板打印的实现
    ubuntu下安装matlab
    修改ubuntu中的gcc和g++版本
  • 原文地址:https://www.cnblogs.com/Romantic-Chopin/p/12451009.html
Copyright © 2011-2022 走看看