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;
    }
  • 相关阅读:
    select、poll和epoll
    Linux 常用命令之文件和目录
    SmartPlant Review 帮助文档机翻做培训手册
    SmartPlant Foundation 基础教程 3.4 菜单栏
    SmartPlant Foundation 基础教程 3.3 标题栏
    SmartPlant Foundation 基础教程 3.2 界面布局
    SmartPlant Foundation 基础教程 3.1 DTC登陆界面
    SmartPlant Foundation 基础教程 1.4 SPF架构
    SmartPlant Foundation 基础教程 1.3 SPF其他功能
    SmartPlant Foundation 基础教程 1.2 SPF集成设计功能
  • 原文地址:https://www.cnblogs.com/Romantic-Chopin/p/12451009.html
Copyright © 2011-2022 走看看