C++ 非阻塞套接字的使用 (3)
异步非阻塞套接字避免了死循环的接收问题,但是软件用起来体验还是很差。究其原因,软件在指令的发送、接收上,
采取了一种不合理的方式:在指令的发送后,立刻调用接收函数,等待回令。
若是采用同步阻塞套接字,那么如果连接出现问题没有回令,那么软件进程会始终等待锁死,这样显然是不合理。于是
采用同步非阻塞的接收方式,timeout被设置为NULL。这时会出现发令与回令的错位问题。这是由于,相应回令没有及时到
达,而接收函数已经返回,第一条发令被当做无回令处理;而此时回令被堆积在了缓冲区,被第二条发令接收到,造成指令错位。
实际上,这种同步的接收方式是不合理的。如果软件有长连接中接收消息的可能性,那么显然单独设置接受线程是更为合理,
这种主动的接受方式优于被动的调用接收函数的方式。在软件的设计中,通信需要下一番功夫,这是个教训。
总之,软件的设计已经无法更改,我们需要缓解这个问题,所以只好采用同步非阻塞的结束方式,使用select()函数实现。
以下内容引用自http://blog.csdn.net/inspiron_110/article/details/4764847。
select模型(选择模型)
先看一下下面的这句代码:
int iResult = recv(s, buffer,1024);
这是用来接收数据的,在默认的阻塞模式下的套接字里,recv会阻塞在那里,直到套接字连接上有数据可读,把数据读到
buffer里后recv函数才会返回,不然就会一直阻塞在那里。在单线程的程序里出现这种情况会导致主线程(单线程程序里只有
一个默认的主线程)被阻塞,这样整个程序被锁死在这里,如果永远没数据发送过来,那么程序就会被永远锁死。这个问题可以
用多线程解决,但是在有多个套接字连接的情况下,这不是一个好的选择,扩展性很差。Select模型就是为了解决这个问题而出现的。
再看代码:
int iResult = ioctlsocket(s, FIOBIO, (unsigned long *)&ul);
iResult = recv(s, buffer,1024);
这一次recv的调用不管套接字连接上有没有数据可以接收都会马上返回。原因就在于我们用ioctlsocket把套接字设置为非阻塞模式了。
不过你跟踪一下就会发现,在没有数据的情况下,recv确实是马上返回了,但是也返回了一个错误:WSAEWOULDBLOCK,意思就是请
求的操作没有成功完成。看到这里很多人可能会说,那么就重复调用recv并检查返回值,直到成功为止,但是这样做效率很成问题,开销太大。
感谢天才的微软工程师吧,他们给我们提供了好的解决办法。
先看看select函数
int select(
int nfds,
fd_set FAR *readfds,
fd_set FAR *writefds,
fd_set FAR *exceptfds,
const struct timeval FAR *timeout
);
第一个参数不要管,会被系统忽略的。第二个参数是用来检查套接字可读性,也就说检查套接字上是否有数据可读,同样,第三个参数
用来检查数据是否可以发出。最后一个是检查是否有带外数据可读取。
参数详细的意思请去看MSDN,这里限于篇幅不详细解释了。
最后一个参数是用来设置select等待多久的,是个结构:
struct timeval {
long tv_sec; // seconds
long tv_usec; // and microseconds
};
如果将这个结构设置为(0,0),那么select函数会马上返回。
说了这么久,select的作用到底是什么?
他的作用就是:防止在在阻塞模式的套接字里被锁死,避免在非阻塞套接字里重复检查WSAEWOULDBLOCK错误。
他的工作流程如下:
1:用FD_ZERO宏来初始化我们感兴趣的fd_set,也就是select函数的第二三四个参数。
2:用FD_SET宏来将套接字句柄分配给相应的fd_set。
3:调用select函数。
4:用FD_ISSET对套接字句柄进行检查,如果我们所关注的那个套接字句柄仍然在开始分配的那个fd_set里,那么
说明马上可以进行相应的IO操作。比如一个分配给select第一个参数的套接字句柄在select返回后仍然在select第一个参数的
fd_set里,那么说明当前数据已经来了,马上可以读取成功而不会被阻塞。
最后附上一段示例代码:
WSADATA wsa;
WSAStartup(MAKEWORD(,), &wsa);
SOCKET fdServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_addr.s_addr = inet_addr(InternetAddr);
server.sin_port = htons(iPort);
int ret = bind(fdServer, (sockaddr*)&server, sizeof(server));
ret = listen(fdServer, ); SOCKET AcceptSocket;
fd_set fdread;
timeval tv;
int nSize; while()
{
FD_ZERO(&fdread);//初始化fd_set
FD_SET(fdServer, &fdread);//分配套接字句柄到相应的fd_set
//这里我们打算让select等待两秒后返回,避免被锁死
tv.tv_sec = ;
tv.tv_usec = ;
select(, &fdread, NULL, NULL, &tv);
nSize = sizeof(server); //如果套接字句柄还在fd_set里,说明客户端已经有
connect的请求发过来了,马上可以accept成功
if (FD_ISSET(fdServer, &fdread))
{
AcceptSocket = accept(fdServer,
( sockaddr*) &server, &nSize);
break;
}
} char buffer[];
ZeroMemory(buffer, ); //这里同样可以用select,用法和上面一样
ret = recv(AcceptSocket,buffer,,); closesocket(AcceptSocket);
WSACleanup();
return;
C++ 非阻塞套接字的使用 (3)的更多相关文章
- python_非阻塞套接字及I/O流
http://www.cnblogs.com/lixy-88428977/p/9638949.html 首先,我们要明确2个问题: 普通套接字实现的服务端有什么缺陷吗? 有,一次只能服务一个客户端! ...
- 非阻塞套接字编程, IO多路复用(epoll)
非阻塞套接字编程: server端 import socket server = socket.socket() server.setblocking(False) server.bind(('', ...
- 非阻塞套接字与IO多路复用
我们了解了socket之后已经知道,普通套接字实现的服务端的缺陷:一次只能服务一个客户端! 并且,为了使一个客户端能够不断收发消息,我们还要使用while循环来轮询,这极大地降低了我们的效率 acce ...
- 11 非阻塞套接字与IO多路复用(进阶)
1.非阻塞套接字 第一部分 基本IO模型 1.普通套接字实现的服务端的缺陷 一次只能服务一个客户端! 2.普通套接字实现的服务端的瓶颈!!! accept阻塞! 在没有新的套接字来之前,不能处理已经建 ...
- 非阻塞套接字与IO多路复用(转,python实现版)
非阻塞:指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回.epoll工作在非阻塞模式时,才会发挥作用. 我们了解了socket之后已经知道,普通套接字实现的服务端的缺陷:一次只能服务一个 ...
- C++ 非阻塞套接字的使用 (2)
继续话题——软件中的异步非阻塞通讯方式. 由于软件基于MFC开发,所以实现异步通讯时使用了CAsyncSocket类. 首先要了解CAsyncSocket异步机制,引用自 http://blog.cs ...
- C++ 非阻塞套接字的使用 (1)
在维护代码的过程中,发现软件运行的CPU占用率居高不下,在4核的电脑上占用了25%的CPU.查阅资料的得知,这是可能是由于软件中出现了死循环. 经过对软件的一些测试,最终确定了死循环出现的位置——通讯 ...
- 阻塞套接字返回EAGAIN
今天用NDK写了一个通信程序,发现阻塞SOKCET 读写的时候返回了EAGAIN.NDK下PERROR输出为Try Again.查了半天头文件 在网上找到了原因.在此纪录.网址为http://blog ...
- 非阻塞SOCKET套接字connect等待时间的实现
SOCKET cClient; cClient=socket(AF_INET,SOCK_STREAM,0); //设置为非阻塞套接字 int iMode = 1; i ...
随机推荐
- Eclipse各种配置
1.配置工作空间编码 Window -- Preferences-- Genereal -- Workspace (Text file encoding -- orther -- utf-8) 2 ...
- wamp如何添加多个站点
1.打开wamp目录下的bin目录下的apache/conf/extra/httpd-vhosts.conf文件(虚拟目录配置文件),修改文件:在num01下创建index.php文件,输出01,:在 ...
- curl 模拟登录微信公众平台带验证码
这段时间一直写个项目, 从切图到前端到后台都要搞定,真tm累. 今天下午手残,不停用错误的密码去模拟登录微信公众平台,结果后来出现验证码,瞬间悲剧(菜鸟从来没搞过带验证码的). 研究了一下,发现其实很 ...
- [转] Oracle sql 查询突然变慢 -- 案例分析
转自:http://jingyan.baidu.com/article/8275fc868ce57946a03cf692.html 一条sql突然执行变慢,耗时9秒,应用是不能改的,只能从数据库方面下 ...
- 使用opengl 绘制9个点,理解各个参数的含义
// SimpleTest1.cpp : 定义控制台应用程序的入口点. // #include "stdafx.h" #include<gl/glut.h> #incl ...
- 使用WebDriver遇到的那些坑
在做web项目的自动化端到端测试时主要使用的是Selenium WebDriver来驱动浏览器.Selenium WebDriver的优点是支持的语言多,支持的浏览器多.主流的浏览器Chrome.Fi ...
- OOP之C#设计及其UML(反向工程)
现在总结一下C#类关键字(virtual.abstract.override.new.sealed)的使用(以C#代码体现),并再次熟悉一下OOP思想,使用UML工具EA(Enterprise Arc ...
- Oracle 服务器端执行带参数的procedure
进入服务器后 su - oracle sqlplus schema/schemapass 连接上以后,输入以下,然后执行 declare vRet number(5) := 8; begin proc ...
- 将Tomcat加入windows系统服务
将Tomcat加入windows系统服务 将Tomcat加入服务 1.修改bin目录中的service.bat: REM 添加下面的一行 set CATALINA_HOME=%cd% 如果从来没有安装 ...
- ADV-时间分配
#include<stdio.h> int map[20][4]; typedef struct node{ int star; int end; }node; node dui[100] ...