zoukankan      html  css  js  c++  java
  • 异步套接字编程之select模型

     

    █ 选择(select)模型是Winsock中最常见的 I/O模型。核心便是利用 select 函数,实现对 I/O的管理!
    利用 select 函数来判断某Socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在Socket处于阻塞模式中时,
    在一次 I/O 调用(如send或recv、accept等)过程中,被迫进入“锁定”状态;同时防止在套接字处于非阻塞模
    式中时,产生WSAEWOULDBLOCK错误。


    █ select 的函数原型如下:
    int select(
      __in          int nfds,
      __in_out      fd_set* readfds,
      __in_out      fd_set* writefds,
      __in_out      fd_set* exceptfds,
      __in          const struct timeval* timeout
    );


    其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与Berkeley套接字兼容。
    后面大家看到有三个 fd_set类型的参数:
    一个用于检查可读性(readfds),
    一个用于检查可写性(writefds),
    一个用于例外数据(exceptfds)。


    fd_set 结构的定义如下:
    typedef struct fd_set { 
     u_int fd_count;
     SOCKET fd_array[FD_SETSIZE];
    } fd_set;


    #define FD_SETSIZE      64
    所以 fd_set 结构中最多只能监视64个套接字。


    fdset 代表着一系列特定套接字的集合。其中, readfds 集合包括符合下述任何一个条件的套接字:
    ● 有数据可以读入。
    ● 连接已经关闭、重设或中止。
    ● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。


    writefds 集合包括符合下述任何一个条件的套接字:
    ● 有数据可以发出。
    ● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。


    exceptfds 集合包括符合下述任何一个条件的套接字:
    ● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
    ● 有带外(Out-of-band,OOB)数据可供读取。


    举个例子,假设我们想测试一个套接字是否“可读”,必须将自己的套接字增添到readfds集合中,
    然后调用 select 函数并等待其完成。select 完成之后,再次判断自己的套接字是否仍为 readfds 集合的一部分。
    若答案是肯定的,则表明该套接字“可读”,可立即着手从它上面读取数据。


    在三个参数中(readfds、writefds 和 exceptfds),任何两个都可以是空值( NULL);
    但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;
    否则, select 函数便没有任何东西可以等待。最后一个参数 timeout 对应的是一个指针,它指向一个timeval 结构,
    用于决定select 最多等待 I/O操作完成多久的时间。如 timeout 是一个空指针,那么 select 调用会无限
    期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。


    对 timeval 结构的定义如下:
    tv_sec 字段以秒为单位指定等待时间;
    tv_usec 字段则以毫秒为单位指定等待时间。
    1秒 = 1000毫秒


    若将超时值设置为(0 , 0),表明 select 会立即返回,出于对性能方面的考虑,应避免这样的设置。


    █ select 函数返回值:
    select 成功完成后,会在 fdset 结构中,返回刚好有未完成的 I/O操作的所有套接字句柄的总量。
    若超过 timeval 设定的时间,便会返回0。若 select 调用失败,都会返回 SOCKET_ERROR,
    应该调用 WSAGetLastError 获取错误码!


    用 select 对套接字进行监视之前,必须将套接字句柄分配给一个fdset的结构集合,
    之后再来调用 select,便可知道一个套接字上是否正在发生上述的 I/O 活动。
    Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查:
    ● FD_CLR(s, *set):从set中删除套接字s。
    ● FD_ISSET(s, *set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
    ● FD_SET(s, *set):将套接字s加入集合set。
    ● FD_ZERO( * set):将set初始化成空集合。



    例如,假定我们想知道是否可从一个套接字中安全地读取数据,同时不会陷于无休止的
    “锁定”状态,便可使用 FDSET 宏,将自己的套接字分配给 fdread 集合,再来调用 select。要
    想检测自己的套接字是否仍属 fdread 集合的一部分,可使用 FD_ISSET 宏。采用下述步骤,便
    可完成用 select 操作一个或多个套接字句柄的全过程:
    1) 使用FDZERO宏,初始化一个fdset对象;
    2) 使用FDSET宏,将套接字句柄加入到fdset集合中;
    3) 调用 select 函数,等待其返回……select 完成后,会返回在所有 fdset 集合中设置的套接字句柄总数,
    并对每个集合进行相应的更新。
    4) 根据 select的返回值和 FDISSET宏,对 fdset 集合进行检查。
    5) 知道了每个集合中“待决”的 I/O操作之后,对 I/O进行处理,
    然后返回步骤1 ),继续进行 select 处理。


    select 函数返回后,会修改 fdset 结构,删除那些不存在待决 I/O 操作的套接字句柄。
    这正是我们在上述的步骤 ( 4 ) 中,为何要使用 FDISSET 宏来判断一个特定的套接字是否仍在集合中的原因。 
    [cpp] view plaincopy
     
    1. <span style="font-size:14px;">客户端代码:  
    2.   
    3. void CClientDlg::OnBnClickedLink()  
    4. {  
    5.     // TODO: 在此添加控件通知处理程序代码  
    6.     AfxBeginThread(ThreadPro,this); //开启一个线程  
    7. }  
    8.   
    9. BOOL CClientDlg::InitSock() //初始化套接字库,建msdn  
    10. {  
    11.     WORD wVersionRequested;  
    12.     WSADATA wsaData;  
    13.     int err;  
    14.     wVersionRequested = MAKEWORD( 2, 2 );  
    15.     err = WSAStartup( wVersionRequested, &wsaData );  
    16.     if ( err != 0 ) {  
    17.         return FALSE;  
    18.     }  
    19.   
    20.     if ( LOBYTE( wsaData.wVersion ) != 2 ||  
    21.         HIBYTE( wsaData.wVersion ) != 2 ) {                          
    22.             WSACleanup( );  
    23.             return FALSE;   
    24.     }  
    25.     return TRUE;  
    26. }  
    27.   
    28.   
    29. BOOL CClientDlg::SelectFun(SOCKET sock,BOOL ret) //ret为true表示要读取数据,ret为false表示要发送数据  
    30. {  
    31.     //1.用FD_ZEOR初始化fd_set,timeval  
    32.     //2.用FD_SET将SOCKET加入集合  
    33.     //3.调用select函数  
    34.     //4.调用FD_ISSET判断  
    35.   
    36.         fd_set fd;     
    37.     FD_ZERO(&fd);  
    38.     timeval tval;  
    39.     tval.tv_sec=0;  
    40.     tval.tv_usec=1000;  
    41.   
    42.     FD_SET(sock,&fd);  
    43.     int num;  
    44.     if (ret) //ret只是用来区分读写  
    45.     {  
    46.        num=select(0,&fd,NULL,NULL,&tval);  
    47.     }  
    48.     else  
    49.     {  
    50.         num=select(0,NULL,&fd,NULL,&tval);  
    51.     }  
    52.   
    53.     if(num<=0)   
    54.         return FALSE;  
    55.     else if( num>0 && FD_ISSET(sock,&fd))  
    56.         return TRUE;  
    57.     return FALSE;  
    58. }  
    59.   
    60.   
    61. UINT CClientDlg::ThreadPro( LPVOID pParam )  
    62. {  
    63.     ASSERT(pParam);  
    64.     CClientDlg* pDlg=(CClientDlg*)pParam; //将线程函数定义为类的静态成员函数,只能通过对象去操作类里的数据  
    65.     if (!pDlg->InitSock())  
    66.     {  
    67.         AfxMessageBox(_T("初始化套接字库失败"));  
    68.         return 0;  
    69.     }  
    70.       
    71.     SOCKADDR_IN serAddr;  
    72.     serAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //VS2008里用的是小写的s_addr  inet_addr将字符串转换为网络字节顺序,inet_addr将网络地址转换为字符串  
    73.     serAddr.sin_family=AF_INET;  
    74.         serAddr.sin_port=htons(5000);  
    75.       
    76.     pDlg->m_sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //这里不能直接使用m_sock非静态成员,最后一个参数是可选的  
    77.     if (pDlg->m_sock==INVALID_SOCKET)  
    78.     {  
    79.         AfxMessageBox(_T("创建套接字失败"));  
    80.         return 0;  
    81.     }  
    82.   
    83.     if (connect(pDlg->m_sock,(SOCKADDR*)&serAddr,sizeof(SOCKADDR_IN))==SOCKET_ERROR)  
    84.     {  
    85.         AfxMessageBox(_T("连接服务器失败"));  
    86.         return 0;  
    87.     }  
    88.       
    89.     while (1)  
    90.     {  
    91.         if(pDlg->SelectFun(pDlg->m_sock,TRUE)) // 判断,如果select判断为真则读取数据  
    92.         {  
    93.             int res;  
    94.             TCHAR bufData[1024]={0};  
    95.             res=recv(pDlg->m_sock,(char*)bufData,1024,0);  
    96.             //recv的返回值,>0接收正确(也就是数据的大小),=0连接断开 <0接收出错  
    97.             if(res>0)  
    98.            {  
    99.             pDlg->ShowMsg(bufData);  
    100.                 }  
    101.             else  
    102.             {  
    103.                                 pDlg->ShowMsg(_T("服务器已经断开!"));  
    104.                 break;  
    105.             }  
    106.   
    107.         }  
    108.         else  
    109.           Sleep(1000);  
    110.     }  
    111.     WSACleanup();  
    112. }  
    113.   
    114. void CClientDlg::OnBnClickedSend()  
    115. {  
    116.     // TODO: 在此添加控件通知处理程序代码  
    117.     CString strText;  
    118.     //ASSERT(m_cliSock != INVALID_SOCKET);  
    119.     if (m_sock==INVALID_SOCKET)  
    120.     {  
    121.         MessageBox(_T("套接字无效"));  
    122.         return;  
    123.     }  
    124.      GetDlgItemText(IDC_SENDDATA, strText);  
    125.     if (!strText.IsEmpty() && SelectFun(m_sock,FALSE)) //如果SelectFun返回真则表示可以写入数据  
    126.        {  
    127.         send(m_sock, (char *)strText.GetBuffer(), strText.GetLength()*sizeof(TCHAR), 0);  
    128.         SetDlgItemText(IDC_SENDDATA, _T(""));  
    129.     }  
    130.     else{  
    131.         MessageBox(_T("发送数据出错"));  
    132.         return;  
    133.     }  
    134.     //这里理解有个误区,上面链接后就进入while死循环了那不是不能发送数据了?  
    135.     //其实不是,链接是用一个新的线程启动了,所以有两个线程在执行,while始终有一个线程维护着  
    136.     //只是while里也可以做别的事,不用一直阻塞  
    137. }  
    138.   
    139. 服务器与客户端基本类似:  
    140.   
    141.   
    142. UINT CServerDlg::ThreadPro(LPVOID aparam)  
    143. {  
    144.     ASSERT(aparam);  
    145.     CServerDlg* pDlg=(CServerDlg*)aparam;  
    146.     if (!pDlg->InitSock())  
    147.     {  
    148.         AfxMessageBox(_T("初始化套接字库失败"));  
    149.         return 0;  
    150.     }  
    151.   
    152.     SOCKADDR_IN serAdd;  
    153.     serAdd.sin_family=AF_INET;  
    154.     serAdd.sin_port=htons(5000);  
    155.     serAdd.sin_addr.s_addr=INADDR_ANY; //INADDR_ANY表示可以接受任何连接  
    156.   
    157.     pDlg->m_sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);  
    158.       
    159.     if (bind(pDlg->m_sock,(SOCKADDR*)&serAdd,sizeof(SOCKADDR_IN))==SOCKET_ERROR)  
    160.     {  
    161.         AfxMessageBox(_T("绑定套接字失败"));  
    162.         return 0;  
    163.     }  
    164.   
    165.     if (listen(pDlg->m_sock,SOMAXCONN)==SOCKET_ERROR)  
    166.     {  
    167.         AfxMessageBox(_T("监听套接字失败"));  
    168.         return 0;  
    169.     }  
    170.   
    171.     SOCKADDR_IN cliAdd;  
    172.     int len=sizeof(SOCKADDR_IN);  
    173.     if ((pDlg->m_cliSock=accept(pDlg->m_sock,(SOCKADDR*)&cliAdd,&len))==INVALID_SOCKET) //记住,服务器有两个SOCKET,一个listen,一个与客户端通信  
    174.     //注意优先级别,找了好久的错误 ==号的优先级别要大于=  
    175.     {  
    176.         AfxMessageBox(_T("接受连接失败"));  
    177.         return 0;  
    178.     }  
    179.   
    180.     while (1)  
    181.     {  
    182.         if (pDlg->SelectFun(pDlg->m_cliSock,TRUE))  
    183.         {  
    184.             char bufData[1024]={0};  
    185.             int ret;  
    186.             ret=recv(pDlg->m_cliSock,(char*)bufData,1024,0);  
    187.             if (ret==0)  
    188.             {  
    189.                 AfxMessageBox(_T("客户端已经断开"));  
    190.                 break;  
    191.             }  
    192.             else  
    193.             {  
    194.                 //这里为什么不能用以下的方法,一直困扰了很久  
    195.                 //很简单,对话框属于一个类,不能随便新建一个类的对象就取操作对话框的界面!  
    196.                 //pDlg->m_strRec=bufData;  
    197.                 //pDlg->UpdateData(FALSE);  
    198.                 pDlg->ShowMsg(bufData);  
    199.             }  
    200.         }  
    201.         else  
    202.             Sleep(1000);  
    203.     }  
    204. }</span>  
  • 相关阅读:
    Add Two Numbers
    Reverse Linked List II
    Reverse Linked List
    Remove Duplicates from Sorted List
    Remove Duplicates from Sorted List II
    Partition List
    Intersection of Two Linked Lists
    4Sum
    3Sum
    2Sum
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/4504128.html
Copyright © 2011-2022 走看看