zoukankan      html  css  js  c++  java
  • socket select模型

    由于socket recv()方法是堵塞式的,当多个客户端连接服务器时,其中一个socket的recv调用时,会产生堵塞,使其他连接不能继续。

    如果想改变这种一直等下去的焦急状态,可以多线程来实现(不再等待,同时去recv,同时阻塞),每个socket连接使用一个线程,这样效率十分低下,根本不可能应对负荷较大的情况(是啊,占用各种资源,电脑啊,你耗不起)。

    这时候我们便可以采取select模型。select允许进程指示内核等待多个事件中的任何一个发生,并仅在有一个或多个事件发生或经历一段指定时间后才唤醒它。select告诉内核对哪些描述子(文件描述符)感兴趣以及等待多长时间。这就是所谓的非阻塞模型,就是进程或线程执行此函数时不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况。如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高。

    select的函数原型:

    int select
    (
         int nfds, //Winsock中此参数无意义
         fd_set* readfds, //进行可读检测的Socket
         fd_set* writefds, //进行可写检测的Socket
         fd_set* exceptfds, //进行异常检测的Socket
         const struct timeval* timeout //非阻塞模式中设置最大等待时间
     )
    

      第一种解释版本:

    /*参数列表
    int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 
      
    fd_set *readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 
      
    fd_set *writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 
      
    fd_set *errorfds同上面两个参数的意图,用来监视文件错误异常。 
      
    struct timeval* timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态:
    第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
    第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;
    第三,timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。
    */
    

      OS内核通过查看读或者写数据缓冲区是否有数据类决定select的返回值。

    /*
    返回值: 
    
    负值:select错误
    
    正值:某些文件可读写或出错,即使是出错了,但是调用recv函数的时候不会阻塞了
    
    0:等待超时,没有可读写或错误的文件
    */
    

      第二种解释版本:

    参数解释:
    
    参数1 nfds:这个参数是一个被忽略的参数,我们在程序中传入0即可,其目的是与伯克利套接字兼容。
    
    参数2 readfds:可读性监视集合,可读性指有连接到来、有数据到来、连接已关闭、重置或终止。
    
    参数3 writefds:可写性监视集合,可写性指数据可以发送、连接可以成功。
    
    参数4 exceptfds:例外性监视集合,例外性指连接会失败、外带数据到来
    
    参数4 timeout:该参数指定select会等待的时间,者结构很简单,要是有兴趣可以看看msdn
    
    注意:select参数中的三个监视集合至少有一个不能为空,任何其它两个都可为空
    

      


    fd_set的结构,这个结构是用来装SOCKET的,把要监视SOCKET传给这个结构,然后同select函数进行监视。

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

      和select模型紧密结合的四个宏:

    FD_CLR( s,*set) 从队列set删除句柄s。
    
    FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。
    
    FD_SET( s,*set )把句柄s添加到队列set中。
    
    FD_ZERO( *set ) 把set队列初始化成空队列。
    

      select选择模式倚赖于select函数,其思想就是让select函数对传入fd_set进行监视(fd_set中装有你的SOCKET句柄),如果没什么事发生select就将fd_set中的SOCKET清除。

    timeval outTime;
    outTime.tv = 1;   //设置等待时间为1s
    outTime.usec = 0; //毫秒
    fd_set fdread;
    while(true)
    {
       FD_ZERO(&fdread);
       FD_SET(sessionSock, &fdread) //sessionSock为之前创建的会话套接字
       select(0, &fdread, NULL, NULL, &outTime);
       if(FD_ISSET(sessionSock, &fdread))//判断套接字是否还在集合中
       {
          recv_cnt = recv(sessionSock, buf, bufSize, 0);
       }
       else
       {
          //没有数据写入,进行其他操作
       }
    }
    

      结束了吗,有问题吗?是否有人觉得奇怪,我这说的是异步I/O,但这个select依然会阻塞进程啊,不是设置了outTime的吗?此问题很好,select本身是会阻塞的,我们可以使用select实现阻塞式套接字(如上),也可以实现异步套接字。我个人对实现异步套接字的理解是:你可以单独使用一个线程来进行你select,也就是说select阻塞你单独的线程,说白了就是让线程来完成异步。


    下面具体分析一下项目中的socket的select模型:

    对于工具机的select模型,它作为服务器只有一个socket,但是也用select模型,这样可以避免recv()函数阻塞:

    while(ros::ok())
    {
    	if(!connected)
    	{
    		ROS_INFO("Restarting connection...");
    
    		if(serverSocket > 0)
    		{
    		  close(serverSocket);
    		  serverSocket = -1;
    		}
    
    		if(clientSocket > 0)
    		{
    		  close(clientSocket);
    		  clientSocket = -1;
    		}
    
    		bool re;
    		if(bServer)
    		  re = startServer();
    		else
    		  re = connectServer();
    		if(!re)
    			continue;
    	}//if
    	int len =0;
    	int revDateCount = 0;
    	
    	int re = selectSocket(clientSocket, 100);
    
    	if(re < 0)
    	{
    	  ROS_ERROR("Receive Thread Select Error!");
    	  connected = false;
    	  continue;
    	}
    	else if(re == 0)
    	{
    	  continue;
    	}
    	else//re > 0
    	{
    	  len  = recv(clientSocket, (void*)recvBuffer, MAX_BUFFER_LEN, 0);
    	  if(len > 0)
    	  {
    		memcpy((void*)(buff + revDateCount), (void*)recvBuffer, len);
    		revDateCount += len;
    	  }//if
    	  else
    	  {
    		ROS_ERROR("Receive Data Error!");
    		connected = false;
    		goto __reconnect__;
    	  }//else
    	}// re > 0
    
    	__reconnect__:;
    }//while(ros::ok())
    

      其中的selectSocket函数如下,fd_set中只有一个描述符

    int CSocket::selectSocket(int &hSocket, int msec, int sec, bool bRead)
    {
        fd_set fdset;
        struct timeval tv;
        FD_ZERO(&fdset);
        FD_SET(hSocket, &fdset);
        msec = msec > 1000 ? 1000 : msec;
        tv.tv_sec  = sec;
        tv.tv_usec = msec * 1000;
    
        int iRet = 0;
        if ( bRead ) {
            iRet = select(hSocket + 1, &fdset, NULL , NULL, &tv);
        }else{
            iRet = select(hSocket + 1, NULL , &fdset, NULL, &tv);
        }
        return iRet;
    }
    

      利用select的模式,虽然在工控机的服务器端只有一个socket,但是select模式能够很好的在不阻塞线程的情况下,调用recv函数。

     再看云台角度查询的socket客户端:

    socketMutex.Lock();
    byte data[7] ={0xFF, 0x01,0x00, 0x51, 0x00, 0x00, 0x52};
    if(sendData(data, sizeof(data), false) == 7)
    {	
    	Sleep(50);
    	while(1)
    	{	
    		FD_ZERO(&readfds);
    		FD_SET(clientSocket, &readfds);
    		time.tv_sec = 0;
    		time.tv_usec = 500000;
    		re = select(nfds, &readfds, NULL, NULL, &time);
    
    		if (re > 0)
    		{	
    			num = recv(clientSocket, (char*)buffer, 128, 0);
    			if(num == 7)
    			{
    				p=buffer[4];
    				p = (p<<8);
    				p += buffer[5];
    				break;
    			}
    			else if (num <= 0)
    			{
    				bConnect = FALSE;
    				ErroFunc(_T("连接丢失"));
    				goto __reConnect__;
    			}
    			else
    			{
    				ErroFunc(_T("丢包"));
    			}
    		}//if
    		else
    		{
    			bConnect = FALSE;
    			ErroFunc(_T("连接丢失,正在重连..."));
    			socketMutex.Unlock();
    			goto __reConnect__;
    		}//else
    
    	}//while
    
    }//if
    socketMutex.Unlock();
    

      在select返回异常的时候,直接重新连接,不调用recv。

  • 相关阅读:
    HDU1879 kruscal 继续畅通工程
    poj1094 拓扑 Sorting It All Out
    (转)搞ACM的你伤不起
    (转)女生应该找一个玩ACM的男生
    poj3259 bellman——ford Wormholes解绝负权问题
    poj2253 最短路 floyd Frogger
    Leetcode 42. Trapping Rain Water
    Leetcode 41. First Missing Positive
    Leetcode 4. Median of Two Sorted Arrays(二分)
    Codeforces:Good Bye 2018(题解)
  • 原文地址:https://www.cnblogs.com/stemon/p/4728486.html
Copyright © 2011-2022 走看看