windows的select函数

select的大概思想:将多个套接字放在一个集合里,然后统一检查这些套接字的状态(可读、可写、异常等),调用select后,会更新这些套接字的状态,然后做判断,如果套接字可读,就执行read操作。这样就巧妙地避免了阻塞,达到同时处理多个连接的目的。当然如果没有事件发生,select会一直阻塞,如果不想一直让它等待,想去处理其它事情,可以设置一个最大的等待时间。


  1. int select(  
  2.   _In_     int nfds,  
  3.   _Inout_  fd_set *readfds,  
  4.   _Inout_  fd_set *writefds,  
  5.   _Inout_  fd_set *exceptfds,  
  6.   _In_     const struct timeval *timeout  
  7. );  

windows下的select的第一个参数是0,因为fd_set中已经涵盖了这个信息,只是为了和Linux下保持一致,所以这块还是存在。

然后第二,三,四个参数,主要是将fd加到这个readfds, writefds,exceptfds, 这些相当于把对应的fd加到对应的fd的队列中,表示我对这种事件感兴趣。调用select之后,如果指定套接字不可读或者不可写,就会从相应队列中清除,这样就可以判断哪些套接字可读或者可写。 

可读性是指:如果有客户的连接请求到达,套接口就是可读的,调用accept能够立即完成,而不发生阻塞;如果套接口接收队列缓冲区中的字节数大于0,调用recv或者recvfrom就不会阻塞。可写性是指,可以向套接字发送数据(套接字创建成功后,就是可写的)。当然不是套接字可写就会去发送数据,就像不是看到电话就去打电话一样,而是由打电话的需求了,才去看电话是否可打;可读就不一样了,电话响了,自然要去接电话(除非,你有事忙或者不想接,一般都是要接的)。可读已经包含了缓冲区中有数据可以读取,可写只是说明了缓冲区有空间让你写,你需不需要写就要看你有没有数据要写了.

第5个参数,表示等待的时间,

是一个结构体:struct timeval,它的定义是:



  • /* 
  • * Structure used in select() call, taken from the BSD file sys/time.h. 
  • */  
  • struct timeval {  
  •         long    tv_sec;         /* seconds */  
  •         long    tv_usec;        /* and microseconds */  
  • }; 

     

  • /* 
  • * Structure used in select() call, taken from the BSD file sys/time.h. 
  • */  
  • struct timeval {  
  •         long    tv_sec;         /* seconds */  
  •         long    tv_usec;        /* and microseconds */  
  • }; 

    集合的管理操作,比如元素的清空、加入、删除以及判断元素是否在集合中都是用宏来完成的。四个宏是:

     

    1. FD_ZERO(*set)  
    2. FD_SET(s, *set)  
    3. FD_ISSET(s, *set)  
    4. FD_CLR(s, *set)  


    FD_ISSET(s,*set),检查描述符是否在集合中,如果在集合中返回非0值,否则返回0. 它的宏定义并没有给出具体实现,但实现的思路很简单,就是搜索集合,判断套接字s是否在数组中。它的宏定义是:

    1. #define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set FAR *)(set))  

    FD_CLR(s,*set),从集合中移出一个套接口描述符(比如一个套接字连接中断后,就应该移除它)。实现思路是,在数组集合中找到对应的描述符,然后把后面的描述依次前移一个位置,最后把描述符的个数减1. 它的宏定义是:


    1. #define FD_CLR(fd, set) do { \  
    2.     u_int __i; \  
    3.     for (__i = 0; __i < ((fd_set FAR *)(set))->fd_count ; __i++) { \  
    4.         if (((fd_set FAR *)(set))->fd_array[__i] == fd) { \  
    5.             while (__i < ((fd_set FAR *)(set))->fd_count-1) { \  
    6.                 ((fd_set FAR *)(set))->fd_array[__i] = \  
    7.                     ((fd_set FAR *)(set))->fd_array[__i+1]; \  
    8.                 __i++; \  
    9.             } \  
    10.             ((fd_set FAR *)(set))->fd_count--; \  
    11.             break; \  
    12.         } \  
    13.     } \  
    14. while(0)  



    至此,一些基础的点基本就讲完了,然后给出大概流程和一个示例:

    1.调用FD_ZERO来初始化套接字状态;

    2.调用FD_SET将感兴趣的套接字描述符加入集合中(每次循环都要重新加入,因为select更新后,会将一些没有满足条件的套接字移除队列);

    3.设置等待时间后,调用select函数--更新套接字的状态;

    4.调用FD_ISSET,来判断套接字是否有相应状态,然后做相应操作,比如,如果套接字可读,就调用recv函数去接收数据。

    关键技术:套接字队列和状态的表示与处理。

    server端得程序如下(套接字管理队列一个很重要的作用就是保存套接字描述符,因为accept得到的套接字描述符会覆盖掉原来的套接字描述符,而readfs中的描述符在select后会删除这些套接字描述符):

    udp的select代码

    #include <iostream>
    using namespace std;
    
    #include <WinSock2.h>
    
    #pragma comment(lib, "ws2_32.lib")
    
    int main(int argc, char** argv)
    {
        WSADATA wsaData = {0};
        int nRet = 0;
        if(SOCKET_ERROR == WSAStartup(MAKEWORD(2,2), &wsaData))
        {
            cout << "fail to WSAStartUp." << endl;
            return -1;
        }
        
        do 
        {
            SOCKET server = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if(NULL == server)
            {
                cout << "fail to create a socket." << endl;
                nRet = -2;
                break;
            }
            
            do 
            {
                SOCKADDR_IN serverAddress = {0};
                serverAddress.sin_addr.S_un.S_addr = inet_addr("0.0.0.0");
                serverAddress.sin_family = AF_INET;
                serverAddress.sin_port = htons(10000);
                
                if(SOCKET_ERROR == bind(server, (SOCKADDR *)&serverAddress, sizeof(SOCKADDR_IN)))
                {
                    cout << "Fail to bind port:" << 10000 << endl;
                    nRet = -3;
                    break;
                }
                
                fd_set fdRead = {0};
                timeval timeout = {0};
                timeout.tv_usec = 500;
                while(true)
                {
                    FD_ZERO(&fdRead);
                    FD_SET(server, &fdRead);
                    int nRet2 = select(0, &fdRead, NULL, NULL, &timeout);
                    if(SOCKET_ERROR == nRet2)
                    {
                        nRet = WSAGetLastError();
                        break;
                    }
                    else if(0 == nRet2)
                    {
                        continue;
                    }
                    else
                    {
                        char szBuffer[1024] = {0};
                        SOCKADDR_IN clientAddress = {0};
                        int clientLength = sizeof(SOCKADDR_IN);
                        int length = recvfrom(server, szBuffer, sizeof(szBuffer)-1, 0, (SOCKADDR *)&clientAddress, &clientLength);
                        if(length == 0)
                        {
                            cout << "sorry" << endl;
                        }
                        else
                        {
                            cout << szBuffer << endl;
                        }
                    }
                }
                if(0 != nRet)
                {
                    break;
                }
            } while (0);
            
            closesocket(server);
        } while (0);
        
        (void)WSACleanup();
        return nRet;
    }



  • /* 
  • * Structure used in select() call, taken from the BSD file sys/time.h. 
  • */  
  • struct timeval {  
  •         long    tv_sec;         /* seconds */  
  •         long    tv_usec;        /* and microseconds */  
  • }; 
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值