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>  
  • 相关阅读:
    通配符
    Hibernate入门简介
    Java的参数传递是值传递?
    Java线程详解
    java基础总结
    谈谈对Spring IOC的理解
    Oracle SQL语句之常见优化方法总结--不定更新
    JVM 工作原理和流程
    Java中的String为什么是不可变的? -- String源码分析
    Spring AOP 简介
  • 原文地址:https://www.cnblogs.com/lvdongjie/p/4504128.html
Copyright © 2011-2022 走看看