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

异步套接字编程之select模型的相关文章

Socket编程之Select模型

echoserver_select.c 1 #include <apue.h> 2 3 #define BACKLOG 10 4 #define PORT 8080 5 #define MAXCLIENT 20 6 #define LEN_BUF 255 7 8 fd_set grset; 9 int maxfd; 10 struct client 11 { 12 char ip[16]; 13 unsigned short port; 14 int connfd; 15 }; 16 17 s

异步套接字基础:select函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET

select函数: 系统提供select函数来实现多路复用输入/输出模型.原型: #include <sys/time.h> #include <unistd.h> int select(int nfds, fd_set *readset, fd_set *writeset,fd_set* exceptset, struct tim *timeout); 功能: 测试指定的fd可读?可写?有异常条件待处理?     参数:    nfds :    需要检查的文件描述符个数(即检查

Python学习【第26篇】:并发编程之IO模型

python并发编程之IO模型, 了解新知识之前需要知道的一些知识 同步(synchronous):一个进程在执行某个任务时,另外一个进程必须等待其执行完毕,才能继续执行 #所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回.按照这个定义,其实绝大多数函数都是同步调用.但是一般而言,我们在说同步.异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务. #举例: #1. multiprocessing.Pool下的apply #发起同步调用后,就在原地等着任务结束,

(转)linux网络编程之IO模型

原文:http://www.cnblogs.com/kunhu/p/3624000.html 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:同步:      所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回.也就是必须一件一件事做,等前一件做完了才能做下一件事. 例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事 异步:  

网络编程之select

一 select函数简介 select一般用在socket网络编程中,在网络编程的过程中,经常会遇到许多阻塞的函数,网络编程时使用的recv, recvfrom.connect函数都是阻塞的函数,当函数不能成功执行的时候,程序就会一直阻塞在这里,无法执行下面的代码.这是就需要用到非阻塞的编程方式,使用 selcet函数就可以实现非阻塞编程.selcet函数是一个轮循函数,即当循环询问文件节点,可设置超时时间,超时时间到了就跳过代码继续往下执行 select(),确定一个或多个套接口的状态,本函数

网络编程之IO模型——selectors模块

网络编程之IO模型--selectors模块 一.了解select,poll,epoll IO复用:为了解释这个名词,首先来理解下复用这个概念,复用也就是共用的意思,这样理解还是有些抽象, 为此,咱们来理解下复用在通信领域的使用,在通信领域中为了充分利用网络连接的物理介质. 往往在同一条网络链路上采用时分复用或频分复用的技术使其在同一链路上传输多路信号,到这里我们就基本上理解了复用的含义, 即公用某个"介质"来尽可能多的做同一类(性质)的事,那IO复用的"介质"是什

windows和linux套接字中的select机制浅析

先来谈谈为什么会出现select函数,也就是select是解决什么问题的? 平常使用的recv函数时阻塞的,也就是如果没有数据可读,recv就会一直阻塞在那里,这是如果有另外一个连接过来,就得一直等待,这样实时性就不是太好. 这个问题的几个解决方法:1. 使用ioctlsocket函数,将recv函数设置成非阻塞的,这样不管套接字上有没有数据都会立刻返回,可以重复调用recv函数,这种方式叫做轮询(polling),但是这样效率很是问题,因为,大多数时间实际上是无数据可读的,花费时间不断反复执行

网络编程之IO模型——非阻塞IO

网络编程之IO模型--非阻塞IO 非阻塞IO(non-blocking IO) Linux下,可以通过设置socket使其变为non-blocking.当对一个non-blocking socket执行读操作时,流程是这个样子: 从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error.从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果.用户进程判断结果是一个error时,它就

&quot;现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作&quot;的处理

"现在已经正在使用此 SocketAsyncEventArgs 实例进行异步套接字操作" 发现不少人在使用SocketAsyncEventArgs进行高性能通信开发时碰到此问题,但网络上没有具体的解决方案,因此记录分享下我的处理方式 首先此问题通常在接入连接.和数据发送.接收时出现此异常 1.接收连接时抛出此异常,需要实现接收连接的同步,使用semaphor等待即可 2.使用SocketAsyncEventArgs的核心是实现对象复用,减少对象的分配和回收从而提高程序性能.通常是使用s