异步套接字编程之select模型
利用 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 宏来判断一个特定的套接字是否仍在集合中的原因。
- <span style="font-size:14px;">客户端代码:
 - void CClientDlg::OnBnClickedLink()
 - {
 - // TODO: 在此添加控件通知处理程序代码
 - AfxBeginThread(ThreadPro,this); //开启一个线程
 - }
 - BOOL CClientDlg::InitSock() //初始化套接字库,建msdn
 - {
 - WORD wVersionRequested;
 - WSADATA wsaData;
 - int err;
 - wVersionRequested = MAKEWORD( 2, 2 );
 - err = WSAStartup( wVersionRequested, &wsaData );
 - if ( err != 0 ) {
 - return FALSE;
 - }
 - if ( LOBYTE( wsaData.wVersion ) != 2 ||
 - HIBYTE( wsaData.wVersion ) != 2 ) {
 - WSACleanup( );
 - return FALSE;
 - }
 - return TRUE;
 - }
 - BOOL CClientDlg::SelectFun(SOCKET sock,BOOL ret) //ret为true表示要读取数据,ret为false表示要发送数据
 - {
 - //1.用FD_ZEOR初始化fd_set,timeval
 - //2.用FD_SET将SOCKET加入集合
 - //3.调用select函数
 - //4.调用FD_ISSET判断
 - fd_set fd;
 - FD_ZERO(&fd);
 - timeval tval;
 - tval.tv_sec=0;
 - tval.tv_usec=1000;
 - FD_SET(sock,&fd);
 - int num;
 - if (ret) //ret只是用来区分读写
 - {
 - num=select(0,&fd,NULL,NULL,&tval);
 - }
 - else
 - {
 - num=select(0,NULL,&fd,NULL,&tval);
 - }
 - if(num<=0)
 - return FALSE;
 - else if( num>0 && FD_ISSET(sock,&fd))
 - return TRUE;
 - return FALSE;
 - }
 - UINT CClientDlg::ThreadPro( LPVOID pParam )
 - {
 - ASSERT(pParam);
 - CClientDlg* pDlg=(CClientDlg*)pParam; //将线程函数定义为类的静态成员函数,只能通过对象去操作类里的数据
 - if (!pDlg->InitSock())
 - {
 - AfxMessageBox(_T("初始化套接字库失败"));
 - return 0;
 - }
 - SOCKADDR_IN serAddr;
 - serAddr.sin_addr.s_addr=inet_addr("127.0.0.1"); //VS2008里用的是小写的s_addr inet_addr将字符串转换为网络字节顺序,inet_addr将网络地址转换为字符串
 - serAddr.sin_family=AF_INET;
 - serAddr.sin_port=htons(5000);
 - pDlg->m_sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); //这里不能直接使用m_sock非静态成员,最后一个参数是可选的
 - if (pDlg->m_sock==INVALID_SOCKET)
 - {
 - AfxMessageBox(_T("创建套接字失败"));
 - return 0;
 - }
 - if (connect(pDlg->m_sock,(SOCKADDR*)&serAddr,sizeof(SOCKADDR_IN))==SOCKET_ERROR)
 - {
 - AfxMessageBox(_T("连接服务器失败"));
 - return 0;
 - }
 - while (1)
 - {
 - if(pDlg->SelectFun(pDlg->m_sock,TRUE)) // 判断,如果select判断为真则读取数据
 - {
 - int res;
 - TCHAR bufData[1024]={0};
 - res=recv(pDlg->m_sock,(char*)bufData,1024,0);
 - //recv的返回值,>0接收正确(也就是数据的大小),=0连接断开 <0接收出错
 - if(res>0)
 - {
 - pDlg->ShowMsg(bufData);
 - }
 - else
 - {
 - pDlg->ShowMsg(_T("服务器已经断开!"));
 - break;
 - }
 - }
 - else
 - Sleep(1000);
 - }
 - WSACleanup();
 - }
 - void CClientDlg::OnBnClickedSend()
 - {
 - // TODO: 在此添加控件通知处理程序代码
 - CString strText;
 - //ASSERT(m_cliSock != INVALID_SOCKET);
 - if (m_sock==INVALID_SOCKET)
 - {
 - MessageBox(_T("套接字无效"));
 - return;
 - }
 - GetDlgItemText(IDC_SENDDATA, strText);
 - if (!strText.IsEmpty() && SelectFun(m_sock,FALSE)) //如果SelectFun返回真则表示可以写入数据
 - {
 - send(m_sock, (char *)strText.GetBuffer(), strText.GetLength()*sizeof(TCHAR), 0);
 - SetDlgItemText(IDC_SENDDATA, _T(""));
 - }
 - else{
 - MessageBox(_T("发送数据出错"));
 - return;
 - }
 - //这里理解有个误区,上面链接后就进入while死循环了那不是不能发送数据了?
 - //其实不是,链接是用一个新的线程启动了,所以有两个线程在执行,while始终有一个线程维护着
 - //只是while里也可以做别的事,不用一直阻塞
 - }
 - 服务器与客户端基本类似:
 - UINT CServerDlg::ThreadPro(LPVOID aparam)
 - {
 - ASSERT(aparam);
 - CServerDlg* pDlg=(CServerDlg*)aparam;
 - if (!pDlg->InitSock())
 - {
 - AfxMessageBox(_T("初始化套接字库失败"));
 - return 0;
 - }
 - SOCKADDR_IN serAdd;
 - serAdd.sin_family=AF_INET;
 - serAdd.sin_port=htons(5000);
 - serAdd.sin_addr.s_addr=INADDR_ANY; //INADDR_ANY表示可以接受任何连接
 - pDlg->m_sock=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
 - if (bind(pDlg->m_sock,(SOCKADDR*)&serAdd,sizeof(SOCKADDR_IN))==SOCKET_ERROR)
 - {
 - AfxMessageBox(_T("绑定套接字失败"));
 - return 0;
 - }
 - if (listen(pDlg->m_sock,SOMAXCONN)==SOCKET_ERROR)
 - {
 - AfxMessageBox(_T("监听套接字失败"));
 - return 0;
 - }
 - SOCKADDR_IN cliAdd;
 - int len=sizeof(SOCKADDR_IN);
 - if ((pDlg->m_cliSock=accept(pDlg->m_sock,(SOCKADDR*)&cliAdd,&len))==INVALID_SOCKET) //记住,服务器有两个SOCKET,一个listen,一个与客户端通信
 - //注意优先级别,找了好久的错误 ==号的优先级别要大于=
 - {
 - AfxMessageBox(_T("接受连接失败"));
 - return 0;
 - }
 - while (1)
 - {
 - if (pDlg->SelectFun(pDlg->m_cliSock,TRUE))
 - {
 - char bufData[1024]={0};
 - int ret;
 - ret=recv(pDlg->m_cliSock,(char*)bufData,1024,0);
 - if (ret==0)
 - {
 - AfxMessageBox(_T("客户端已经断开"));
 - break;
 - }
 - else
 - {
 - //这里为什么不能用以下的方法,一直困扰了很久
 - //很简单,对话框属于一个类,不能随便新建一个类的对象就取操作对话框的界面!
 - //pDlg->m_strRec=bufData;
 - //pDlg->UpdateData(FALSE);
 - pDlg->ShowMsg(bufData);
 - }
 - }
 - else
 - Sleep(1000);
 - }
 - }</span>
 
异步套接字编程之select模型的更多相关文章
- 异步套接字基础:select函数以及FD_ZERO、FD_SET、FD_CLR、FD_ISSET
		
参考:[原创]技术系列之 网络模型(三)多路复用模型 select函数 select函数: 系统提供select函数来实现多路复用输入/输出模型.原型: #include <sys/time.h ...
 - linux网络编程之IO模型
		
本文转自作者:huangguisu 1. 概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock)四种调用方式:同步: 所谓 ...
 - C++网络编程之select
		
select函数决定一个或者多个套接字(socket)的状态,如果需要的话,等待执行异步I/O. int select( __in int nfds, __inout fd_ ...
 - 孙鑫MFC学习笔记16:异步套接字
		
16 1.事件对象 2.CreateEvent创建事件对象 3.SetEvent设置事件对象为通知状态 4.ResetEvent设置事件对象为非通知状态 5.InitializeCriticalSec ...
 - VC基于消息的异步套接字
		
用WSAStartup,需要在StdAfx.h头文件中需要声明 #include #pragma comment(lib,"WS2_32.lib") 用AfxSocket ...
 - 【转】 VC中TCP实现 异步套接字编程的原理+代码
		
所谓的异步套接字编程就是 调用了 如下函数 WSAAsyncSelect 设置了 套接字的状态为异步,有关函数我会在下面详细介绍... 异步套接字解决了 套接字编程过程中的堵塞问题 .... ...
 - DotNet:Socket Server 异步套接字服务端实现
		
异步服务器套接字示例 From https://msdn.microsoft.com/zh-cn/library/fx6588te(v=vs.110).aspx 下面的示例程序创建接收来自客户端的连接 ...
 - socket编程之 select、poll、kqueue、epoll
		
原生API select int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct tim ...
 - Python并发编程之IO模型
		
目录 IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) IO多路复用 异步IO IO模型比较分析 selectors模块 一.IO模型介绍 Stevens ...
 
随机推荐
- 开始学习<p>标签,添加段落
			
如果想在网页上显示文章,这时就需要<p>标签了,把文章的段落放到<p>标签中. 语法: <p>段落文本</p> 注意一段文字一个<p>标签, ...
 - JVM调优实践-Tomcat调优
			
调优几个重要指标 GC频率 提升每次GC的效率 准备环节 jmeter的配置 未压测前JVM配置 工程未调优前配置 -Xms400m -Xmx400m -XX:PermSize=64m -XX:Max ...
 - PHP添加、更新solr索引
			
<?php $options = array ( 'hostname' => 'localhost', 'port' => '8080', 'path'=>'solr/help ...
 - Nginx配置文件nginx.conf详细说明
			
Nginx配置文件nginx.conf详细说明 #worker_processes 8; #worker_cpu_affinity 00000001 00000010 00000100 0000100 ...
 - localStorage  的基本使用
			
① localstorage大小限制在500万字符左右,各个浏览器不一致② localstorage在隐私模式下不可读取③ localstorage本质是在读写文件,数据多的话会比较卡(firefox ...
 - 为UITableViewController瘦身
			
在IOS开发中采用了MVC得模式,ViewController通常是最庞大的文件,里面包含了各种各样的大概,造成代码的复用率低下,可读性也降低,那么有什么办法来解决这个问题呢. 在创建Table的时候 ...
 - Solr In Action 笔记(2) 之 评分机制(相似性计算)
			
Solr In Action 笔记(2) 之评分机制(相似性计算) 1 简述 我们对搜索引擎进行查询时候,很少会有人进行翻页操作.这就要求我们对索引的内容提取具有高度的匹配性,这就搜索引擎文档的相似性 ...
 - osvdb
			
http://www.91ri.org/3117.html http://linux.cn/article-5332-1-rss.html http://www.freebuf.com/article ...
 - Android调用Asp.net Web Service示例
			
WebService代码: using System; using System.Collections.Generic; using System.Linq; using System.Web; u ...
 - MFC自定义消息
			
本文地址:http://blog.163.com/strive_only/blog/static/893801682010101911467765/ 消息机制是windows的典型运行机制,在MFC中 ...