windows下的IO模型之选择(select)模型
1.选择(select)模型:
选择模型:通过一个fd_set集合管理套接字,在满足套接字需求后,通知套接字。让套接字进行工作。
选择模型的核心是FD_SET集合和select函数。通过该函数,我们可以们判断套接字上是否存在数据,或者能否向一个套接字写入数据。
用途:如果我们想接受多个SOCKET的数据,该怎么处理呢?
由于当前socket是阻塞的,直接处理是一定完成不了要求的
a.我们会想到多线程,的确可以解决线程的阻塞问题,但开辟大量的线程并不是什么好的选择;
b我们可以想到用ioctlsocket()函数把socket设置成非阻塞的,然后用循环逐个socket查看当前套接字是否有数据,轮询进行。
这种是可以解决问题的,但是会导致频繁切换状态到内核去查看是否有数据到达,浪费时间。
c.于是想办法用只切换一次状态就知道所有socket的接受缓冲区是否有数据,于是有了select模型,select是阻塞的,Select的好处是可以同时处理若干个Socket,
select阻塞么
一个套接字阻塞或者不阻塞,select就在那里,它可以针对这2种套接字使用,对任何一种套接字的轮询检测,超时时间都是有效的,区别就在于:
当select完毕,认为该套接字可读时,
1 .阻塞的套接字,会让read阻塞,直到读到所需要的所有字节;
2 .非阻塞的套接字,会让read读完fd中的数据后就返回,但如果原本你要求读10个数据,这时只读了8个数据,如果你不再次使用select来判断它是否可读,而是直接read,很可能返回EAGAIN或=EWOULDBLOCK(BSD风格) ,
此错误由在非阻塞套接字上不能立即完成的操作返回,例如,当套接字上没有排队数据可读时调用了recv()函数。此错误不是严重错误,相应操作应该稍后重试。对于在非阻塞 SOCK_STREAM套接字上调用connect()函数来说,报告EWOULDBLOCK是正常的,因为建立一个连接必须花费一些时间。
EWOULDBLOCK的意思是如果你不把socket设成非阻塞(即阻塞)模式时,这个读操作将阻塞,也就是说数据还未准备好(但系统知道数据来了,所以select告诉你那个socket可读)。使用非阻塞模式做I/O操作的细心的人会检查errno是不是EAGAIN、EWOULDBLOCK、EINTR,如果是就应该重读,一般是用循环。如果你不是一定要用非阻塞就不要设成这样,这就是为什么系统的默认模式是阻塞。
通过完善select模型可以得到IO复用模型,详情请看:http://www.cnblogs.com/curo0119/p/8461520.html
一个IO模型的阻塞非阻塞指的是数据访问过程,而不是socket.
select是一个异步阻塞模型。
2.select函数:
int select(
int nfds,//忽略,只是为了保持与早期的Berkeley套接字应用程序的兼容
fd_set FAR* readfds,//可读性检查(有数据可读入,连接关闭,重设,终止),为空则不检查可读性
fd_set FAR* writefds,//可写性检查(有数据可发出),为空则不检查可写性
fd+set FAR* exceptfds,//带外数据检查(带外数据),为空则不检查
const struct timeval FAR* timeout//超时
);
3.select模型的工作步骤:
(1)定义一个集合fd_set并用fd_zero宏初始化为空
(2)用FD_SET宏,把套接字句柄加入到fd_set集合
(3)调用select函数,检查每个套接字的可读可写性,select完成后,会返回所有在fd_set集合中有数据到达的socket的socket句柄总数,并对每个集合进行更新,即没有数据到达的socket在原集合中会被置成空。
(4)根据select的返回值以及FD_ISSET宏,对FD_SET集合进行检查
(5)知道了每个集合中“待决”的I/O操作后,对相应I/O操作进行处理,返回步骤1,继续select
select函数返回后,会修改FD_SET的结构,删除不存在待决IO操作的套接字,这也就是为什么我们之后要用FD_ISSET判断是否还在集合中的原因。
bool UDPNet::SelectSocket()
{
timeval tv;
tv.tv_sec =0;
tv.tv_usec = 100;
fd_set fdsets;//创建集合
FD_ZERO(&fdsets); //初始化集合 FD_SET(m_socklisten,&fdsets);//将socket加入到集合中(此例子是一个socket),将多个socket加入时,可以用数组加for循环 select(NULL,&fdsets,NULL,NULL,&tv);//只检查可读性,即fd_set中的fd_read进行操作 if(!FD_ISSET(m_socklisten,&fdsets))//检查 s是否s e t集合的一名成员;如答案是肯定的是,则返回 T R U E。
{
return false;
}
return true;
}
4.select函数参数详解:
三个 fd_set参数:一个用于检查可读性(readfds),一个用于检查可写性(writefds),另一个用于例外数据( excepfds)。
从根本上说,fdset数据类型代表着一系列特定套接字的集合。其中,
readfds集合包括符合下述任何一个条件的套接字:
■ 有数据可以读入。
■ 连接已经关闭、重设或中止。
■ 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。
writefds集合包括符合下述任何一个条件的套接字:
■ 有数据可以发出。
■ 如果已完成了对一个非锁定连接调用的处理,连接就会成功。
最后,exceptfds集合包括符合下述任何一个条件的套接字:
■ 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
■ 有带外(out-of-band,OOB)数据可供读取。
最后一个参数timeout:
对应的是一个指针,它指向一个timeval结构,用于决定select最多等待 I / O操作完成多久的时间。
如 timeout是一个空指针,那么select调用会无限期地“锁定”或停顿下去,直到至少有一个描述符符合指定的条件后结束。
对timeval结构的定义如下:
struct timeval {
long tv_sec;
long tv_usec;
} ;
若将超时值设置为(0,0),表明select会立即返回,允许应用程序对 select操作进行“轮询”。出于对性能方面的考虑,应避免这样的设置。
select成功完成后,会在 fd_set结构中,返回刚好有未完成的I/O操作的所有套接字句柄的总量。
若超过timeval设定的时间,便会返回0。
如何测试一个套接字是否“可读”?
必须将自己的套接字增添到readfds集合,再等待select函数完成。
select完成之后,必须判断自己的套接字是否仍为readfds集合的一部分。若答案是肯定的,便表明该套接字“可读”,可立即着手从它上面读取数据。
在三个参数中(readfds、writedfss和exceptfds),任何两个都可以是空值(NULL);但是,至少有一个不能为空值!在任何不为空的集合中,必须包含至少一个套接字句柄;
否则, select函数便没有任何东西可以等待。
不管由于什么原因,假如select调用失败,都会返回SOCKET_ERROR
5.select优缺点:
优点:可实现单线程处理多个任务
缺点:
a.等待数据到达的过程以及将数据从内核拷贝到用户的过程总也存在一定阻塞
b.管理的set数组有一定上限,最多是64个(可通过重置fd_setsize将上限扩大到1024)
c.select低效是因为每次它都需要轮询。
完整代码参考:
#include "stdafx.h"
#include <WinSock2.h>
#include <iostream>
using namespace std; #include <stdio.h> #pragma comment(lib,"ws2_32.lib") #define PORT 8000
#define MSGSIZE 255
#define SRV_IP "127.0.0.1" int g_nSockConn = 0;//请求连接的数目 //FD_SETSIZE是在winsocket2.h头文件里定义的,这里windows默认最大为64
//在包含winsocket2.h头文件前使用宏定义可以修改这个值 struct ClientInfo
{
SOCKET sockClient;
SOCKADDR_IN addrClient;
}; ClientInfo g_Client[FD_SETSIZE]; DWORD WINAPI WorkThread(LPVOID lpParameter); int _tmain(int argc, _TCHAR* argv[])
{//基本步骤就不解释了,网络编程基础那篇博客里讲的很详细了
WSADATA wsaData;
WSAStartup(MAKEWORD(2,2),&wsaData); SOCKET sockListen = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr = inet_addr(SRV_IP);
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(PORT); bind(sockListen,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR)); listen(sockListen,64); DWORD dwThreadIDRecv = 0;
DWORD dwThreadIDWrite = 0; HANDLE hand = CreateThread(NULL,0, WorkThread,NULL,0,&dwThreadIDRecv);//用来处理手法消息的进程
if (hand == NULL)
{
cout<<"Create work thread failed\n";
getchar();
return -1;
} SOCKET sockClient;
SOCKADDR_IN addrClient;
int nLenAddrClient = sizeof(SOCKADDR);//这里用0初试化找了半天才找出错误 while (true)
{
sockClient = accept(sockListen,(SOCKADDR*)&addrClient,&nLenAddrClient);//第三个参数一定要按照addrClient大小初始化
//输出连接者的地址信息
//cout<<inet_ntoa(addrClient.sin_addr)<<":"<<ntohs(addrClient.sin_port)<<"has connect !"<<endl; if (sockClient != INVALID_SOCKET)
{
g_Client[g_nSockConn].addrClient = addrClient;//保存连接端地址信息
g_Client[g_nSockConn].sockClient = sockClient;//加入连接者队列
g_nSockConn++;
} } closesocket(sockListen);
WSACleanup(); return 0;
} DWORD WINAPI WorkThread(LPVOID lpParameter)
{
FD_SET fdRead;
int nRet = 0;//记录发送或者接受的字节数
TIMEVAL tv;//设置超时等待时间
tv.tv_sec = 1;
tv.tv_usec = 0;
char buf[MSGSIZE] = ""; while (true)
{
FD_ZERO(&fdRead);
for (int i = 0;i < g_nSockConn;i++)
{
FD_SET(g_Client[i].sockClient,&fdRead);
} //只处理read事件,不过后面还是会有读写消息发送的
nRet = select(0,&fdRead,NULL,NULL,&tv); if (nRet == 0)
{//没有连接或者没有读事件
continue;
} for (int i = 0;i < g_nSockConn;i++)
{
if (FD_ISSET(g_Client[i].sockClient,&fdRead))
{
//如果在集合中,向下进行相应的IO操作
nRet = recv(g_Client[i].sockClient,buf,sizeof(buf),0);//看是否能正常接收到数据 if (nRet == 0 || (nRet == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
{
cout<<"Client "<<inet_ntoa(g_Client[i].addrClient.sin_addr)<<"closed"<<endl;
closesocket(g_Client[i].sockClient); if (i < g_nSockConn-1)
{
//将失效的sockClient剔除,用数组的最后一个补上去
g_Client[i--].sockClient = g_Client[--g_nSockConn].sockClient;
//i--是因为要重新判断新的i的位置的socket是否失效
}
}
else
{
cout<<inet_ntoa(g_Client[i].addrClient.sin_addr)<<": "<<endl;
cout<<buf<<endl;
cout<<"Server:"<<endl;
//gets(buf);
strcpy(buf,"Hello!");
nRet = send(g_Client[i].sockClient,buf,strlen(buf)+1,0);
}
}
}
}
return 0;
}
服务器的主要步骤:
1.创建监听套接字,绑定,监听
2.创建工作者线程
3.创建一个套接字组,用来存放当前所有活动的客户端套接字,没accept一个连接就更新一次数组
4.接收客户端的连接,因为没有重新定义FD_SIZE宏,服务器最多支持64个并发连接。最好是记录下连接数,不要无条件的接受连接
工作线程
工作线程是一个死循环,依次循环完成的动作是:
1.将当前客户端套接字加入到fd_read集中
2.调用select函数
3.用FD_ISSET查看时候套接字还在读集中,如果是就接收数据。如果接收的数据长度为0,或者发生WSAECONNRESET错误,,则
表示客户端套接字主动关闭,我们要释放这个套接字资源,调整我们的套接字数组(让下一个补上)。上面还有个nRet==0的判断,
就是因为select函数会立即返回,连接数为0会陷入死循环。
本文参考:http://blog.csdn.net/rheostat/article/details/9815725
windows下的IO模型之选择(select)模型的更多相关文章
- 一.Windows I/O模型之选择(select)模型
1.选择(select)模型:选择模型:通过一个fd_set集合管理套接字,在满足套接字需求后,通知套接字.让套接字进行工作.避免套接字进入阻塞模式,进行无谓的等待.选择模型的核心的FD_SET集合和 ...
- Select模型及tcp select模型
参考:http://m.blog.csdn.net/article/details?id=51420015 一.套接字模式 套接字模式简单的决定了操作套接字时,Winsock函数是如何运转的.Wins ...
- windows下的IO模型之异步选择(WSAAsyncSelect)模型
异步选择(WSAAsyncSelect)模型是一个有用的异步I/O 模型.其核心函数是WSAAsyncSelect,该函数是非阻塞的 (关于异步io的理解详情可以看:http://www.cnblog ...
- Windows网络编程系列教程之四:Select模型
讲一下套接字模式和套接字I/O模型的区别.先说明一下,只针对Winsock,如果你要骨头里挑鸡蛋把UNIX下的套接字概念来往这里套,那就不关我的事. 套接字模式:阻塞套接字和非阻塞套接字.或者叫同步套 ...
- [转载]Windows网络编程系列教程之四:Select模型
原文:http://www.51see.com/asp/bbs/public/bp_show.asp?t_id=200308131152297103 讲一下套接字模式和套接字I/O模型的区别.先说明一 ...
- Windows下Qt开发环境:OpenGL导入3DMax模型(.3DS)
参考:http://blog.csdn.net/cq361106306/article/details/41876541 效果: 源代码: 解释: CLoad3DS.h为加载3DMax模型的头文件,C ...
- Windows I/O模型之一:Select模型
1.概念理解 在进行网络编程时,我们常常见到同步(Sync)/异步(Async),阻塞(Block)/非阻塞(Unblock) 四种调用模式: 同步:所谓同步,就是在发出一个功能调用时,在没有得到结果 ...
- windows下C语言调用系统文件选择对话框
代码片段,在windows下用C语言调用文件选择对话框,以备忘 #define DEFAULT_DIR "" char extraction_path[MAX_PATH] = DE ...
- windows下的IO模型之事件选择(WSAEventSelect)模型
异步选择模型类似的是,它也允许应用程序在一个或多个套接字上,接收以事件为基础的网络事件通知.对于异步选择模型采用的网络事件来说,它们均可原封不动地移植到事件选择模型.事件选择模型和异步选择模型最主要的 ...
随机推荐
- Oracle 11g R2 RAC 高可用连接特性
转自-阿里巴巴许春值 1.scan概念 什么叫 SCAN,SCAN (Single Client Access Name) 是 Oracle 从11g R2 开始推出的,客户端可以通过 SCAN 特性 ...
- Chinese_remainder_theorem
https://en.wikipedia.org/wiki/Chinese_remainder_theorem 中国剩余定理 https://en.wikipedia.org/wiki/RSA_(cr ...
- jQuery change事件
定义和用法 当元素的值发生改变时,会发生 change 事件. 该事件仅适用于文本域(text field),以及 textarea 和 select 元素. change() 函数触发 change ...
- java-信息安全(十八)java加密解密,签名等总结
一.基本概念 加密: 密码常用术语: 明文,密文,加密,加密算法,加密秘钥,解密,解密算法,解密秘钥, 密码分析:分析密文从而推断出明文或秘钥的过程 主动攻击:入侵密码系统,采用伪造,修改,删除等手段 ...
- Hook?
public interface IHook { /// <summary> /// 二维地图控件对象 /// </summary> ESRI.ArcGIS.Controls. ...
- Django Rest Framework(2)-----序列化详解(serializers)
REST framework中的序列化类与Django的Form和ModelForm类非常相似.我们提供了一个Serializer类,它提供了一种强大的通用方法来控制响应的输出,以及一个ModelSe ...
- 用Python实现的数据结构与算法:双端队列
一.概述 双端队列(deque,全名double-ended queue)是一种具有队列和栈性质的线性数据结构.双端队列也拥有两端:队首(front).队尾(rear),但与队列不同的是,插入操作在两 ...
- POJ3233:Matrix Power Series(矩阵快速幂+二分)
http://poj.org/problem?id=3233 题目大意:给定矩阵A,求A + A^2 + A^3 + … + A^k的结果(两个矩阵相加就是对应位置分别相加).输出的数据mod m.k ...
- Missing Number-[回溯][难]
2. Missing number 转自:https://mp.weixin.qq.com/s/WLRXLdi-3igkjtiWlHg7Ug Given a positive integer n(n≤ ...
- cocos代码研究(24)Widget子类PageView学习笔记
理论基础 PageView类又称Layout的管理器,可以让用户在多个Layout之间左右或者上下切换显示,继承自 Layout . 代码实践 static PageView * create ()创 ...