IO复用在阻塞IO下的坑及解决
一:引入
Unix/Linux上有五种IO模型:阻塞(blocking)、非阻塞(non-blocking)、IO复用(IO multiplexing)、信号驱动(signal-driven)、异步(asynchronous)
当IO复用搭配阻塞IO可能会出现什么问题呢?应该如何尽量避免呢?
二:测试工具
chargen:服务端accept连接之后,不停地发送测试数据。
linux的nc及python程序netcat_nonblock.py,netcat.py
三:测试实验
(1)开启服务端chargen,用不同的nc连接观察异同
(2)使用linux自带nc连接。
1)输入缓冲区无输入时
2)有输入时
(3)netcat.py连接chargen时
1)输入缓冲区无输入时
2)有输入时
使用strace追踪代码
strace python netcat.py localhost 5001 </dev/zero > /dev/null
发现程序阻塞在发送上
sendto(3, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 8192, 0, NULL, 0
使用netstate -tpn观察tcp读写缓冲区接收与发送数据大小
netstate -tpn | grep 5001
(4)netcat_nonblock.py连接chargen时
1)输入缓冲区无输入时
2)有输入时
四:实验分析
(1)netcat.py代码如下:
#!/usr/bin/pythonimport os
import select
import socket
import sys#发送方与接收方数据传输不是独立的,阻塞io可能导致阻塞
#io复用,检查那个描述符准备好
def relay(sock):poll = select.poll()#注册两个读事件,读sock和stdinpoll.register(sock, select.POLLIN) poll.register(sys.stdin, select.POLLIN)done = Falsewhile not done:events = poll.poll(10000) # 10 secondsfor fileno, event in events:if event & select.POLLIN:if fileno == sock.fileno():data = sock.recv(8192)if data:sys.stdout.write(data)else:done = Trueelse:assert fileno == sys.stdin.fileno()data = os.read(fileno, 8192)if data:sock.sendall(data)else:sock.shutdown(socket.SHUT_WR) #将socket数据读完再关闭连接,防止tcp发生rst分解,使数据接收不完整poll.unregister(sys.stdin)def main(argv):if len(argv) < 3:binary = argv[0]print "Usage:\n %s -l port\n %s host port" % (argv[0], argv[0])returnport = int(argv[2])if argv[1] == "-l":# serverserver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.bind(('', port))server_socket.listen(5)(client_socket, client_address) = server_socket.accept()server_socket.close()relay(client_socket)else:# clientsock = socket.create_connection((argv[1], port))relay(sock)if __name__ == "__main__":main(sys.argv)
结合实验(3)第(2)个实验看到,阻塞io下当从输入缓冲区输入数据时,tcp缓冲区chargen及python都在写数据,进而导致两者都不能读数据而永久阻塞在写数据上。故说明发送方与接收方数据传输不是独立的,阻塞io可能会阻塞。
注意点:
sock.shutdown(socket.SHUT_WR)
当发送完数据之后再进行关闭写端,否则会导致数据接收不完整。
netcat_nonblock.py
#!/usr/bin/pythonimport errno
import fcntl
import os
import select
import socket
import sysdef setNonBlocking(fd):flags = fcntl.fcntl(fd, fcntl.F_GETFL)fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)def nonBlockingWrite(fd, data):try:nw = os.write(fd, data)return nwexcept OSError as e:if e.errno == errno.EWOULDBLOCK:return -1def relay(sock):socketEvents = select.POLLINpoll = select.poll()poll.register(sock, socketEvents)poll.register(sys.stdin, select.POLLIN)setNonBlocking(sock) //sock用非阻塞 //stdout用阻塞# setNonBlocking(sys.stdin)# setNonBlocking(sys.stdout)done = FalsesocketOutputBuffer = ''while not done:events = poll.poll(10000) # 10 secondsfor fileno, event in events:if event & select.POLLIN:if fileno == sock.fileno():data = sock.recv(8192)if data:nw = sys.stdout.write(data) # stdout does support non-blocking write, thoughelse:done = Trueelse:assert fileno == sys.stdin.fileno()data = os.read(fileno, 8192)if data:assert len(socketOutputBuffer) == 0nw = nonBlockingWrite(sock.fileno(), data)if nw < len(data): //发生shortWriteif nw < 0:nw = 0socketOutputBuffer = data[nw:] //用socketOutputBuffer保存剩余数据socketEvents |= select.POLLOUTpoll.register(sock, socketEvents) //sock登记写事件 前三行通用poll.unregister(sys.stdin) //因netcat与stdin故采用此行,不通用else://数据读完sock.shutdown(socket.SHUT_WR)poll.unregister(sys.stdin)if event & select.POLLOUT:if fileno == sock.fileno():assert len(socketOutputBuffer) > 0nw = nonBlockingWrite(sock.fileno(), socketOutputBuffer)if nw < len(socketOutputBuffer): //也可能发生shortWriteassert nw > 0socketOutputBuffer = socketOutputBuffer[nw:] //保留未发送数据else:socketOutputBuffer = ''socketEvents &= ~select.POLLOUT //停止观察pollOut事件,否则缓冲区无数据写但sock可写引起电平触发使cpu占满poll.register(sock, socketEvents) //与前面对应poll.register(sys.stdin, select.POLLIN)def main(argv):if len(argv) < 3:binary = argv[0]print "Usage:\n %s -l port\n %s host port" % (argv[0], argv[0])print (sys.stdout.write)returnport = int(argv[2])if argv[1] == "-l":
0 # serverserver_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)server_socket.bind(('', port))server_socket.listen(5)(client_socket, client_address) = server_socket.accept()server_socket.close()relay(client_socket)else:# clientsock = socket.create_connection((argv[1], port))relay(sock)if __name__ == "__main__":main(sys.argv)
根据实验(4)可知使用非阻塞IO可解决阻塞问题。
关于写事件的通用形式:
if nw < len(data): //发生shortWriteif nw < 0:nw = 0socketOutputBuffer = data[nw:] //用socketOutputBuffer保存剩余数据socketEvents |= select.POLLOUTpoll.register(sock, socketEvents)
当发生shortWrite时,应将剩余数据保存在outBuffer中,然后注册POLLOUT事件,一旦socket变得可写就立刻发送数据。下次写时如果还有剩余,应该继续关注POLLOUT事件。
socketOutputBuffer = ''
socketEvents &= ~select.POLLOUT
poll.register(sock, socketEvents)
数据写完后停止观察pollOut事件,否则缓冲区无数据写但sock可写引起电平触发使cpu占满
四:实验结论
IO multiplexing和blocking IO用在一起可能会阻塞,在Linux多线程服务端编程中因为blocking IO中read()/write()/accept()/connect()都有可能阻塞当前线程,这样线程就没办法处理其他socket上的IO事件了,故一般不将IO multiplexing和blocking IO用在一起。
五:参考
1《Linux多线程服务端编程使用muduo C++网络库》 --陈硕
2陈硕老师的程序chargen,netcat.py 及netcat_nonblock.py源于https://github.com/chenshuo/recipes.git
IO复用在阻塞IO下的坑及解决相关推荐
- 同步IO、异步IO、阻塞IO、非阻塞IO、复用IO
参考:同步IO 异步IO 作者:今天天气眞好 发布时间: 2021-04-19 09:42:29 网址:https://blog.csdn.net/qq_51118175/article/detail ...
- 分不清楚阻塞IO,非阻塞IO,IO复用?用最贴近生活的例子带你理解这三者的区别!
文章目录 前言 一.什么是IO 二.阻塞IO模型 三.非阻塞 IO模型 四.IO复用模型 总结 前言 在<Unix网络编程>一书中提到了五种IO模型,分别是:阻塞IO.非阻塞IO.IO复用 ...
- 【死磕NIO】— 阻塞IO,非阻塞IO,IO复用,信号驱动IO,异步IO,这你真的分的清楚吗?
通过上篇文章([死磕NIO]- 阻塞.非阻塞.同步.异步,傻傻分不清楚),我想你应该能够区分了什么是阻塞.非阻塞.异步.非异步了,这篇文章我们来彻底弄清楚什么是阻塞IO,非阻塞IO,IO复用,信号驱动 ...
- 【多线程】0.理解一下5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO
5种IO模型.阻塞IO和非阻塞IO.同步IO和异步IO 看了一些文章,发现有很多不同的理解,可能是因为大家入切的角度.环境不一样.所以,我们先说明基本的IO操作及环境. 本文是在<UNIX网络编 ...
- 5种网络IO模型:阻塞IO、非阻塞IO、异步IO、多路复用IO、信号驱动IO
目录 前言 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) 模型间的区别 ...
- IO模型 :阻塞IO、非阻塞IO、信号驱动IO、异步IO、多路复用IO
文章目录 IO模型 阻塞IO 非阻塞IO 信号驱动IO 多路复用IO 异步IO IO模型 根据各自的特性不同,IO模型被分为阻塞IO.非阻塞IO.信号驱动IO.异步IO.多路复用IO五类. 最主要的两 ...
- 阻塞IO、非阻塞IO的区别
阻塞IO.非阻塞IO的区别 1.类与类之间的关系:依赖,实现,泛化(继承),关联,组合,聚合. 1)依赖(虚线):一个类是 另一个类的函数参数 或者 函数返回值. 2)实现(实线加小圆):对纯虚函数类 ...
- 简述同步IO、异步IO、阻塞IO、非阻塞IO之间的联系与区别
POSIX 同步IO.异步IO.阻塞IO.非阻塞IO,这几个词常见于各种各样的与网络相关的文章之中,往往不同上下文中它们的意思是不一样的,以致于我在很长一段时间对此感到困惑,所以想写一篇文章整理一下. ...
- 5种IO模型、阻塞IO和非阻塞IO、同步IO和异步IO
5种IO模型.阻塞IO和非阻塞IO.同步IO和异步IO 看了一些文章,发现有很多不同的理解,可能是因为大家入切的角度.环境不一样.所以,我们先说明基本的IO操作及环境.本文是在<UNIX网络编程 ...
最新文章
- android web3j 代币查询_wallet-eth 以太坊代币钱包 助记词 私钥 keystore 转账
- 算法回顾(三) 二分查找
- python excel 自动化-Python 自动化:处理 Excel(笔记)
- ImportError cannot import name BytesIO when import caffe
- 字符串匹配算法Java_如何简单理解字符串匹配算法?
- 对话 Dubbo 唤醒者北纬:3.0 将至,阿里核心电商业务也在用 Dubbo
- java中集合的结构Set类型
- .net framework 2.0 Silent install(.net framework 静默安装)
- 手机协处理器java,HBase1.x实战:协处理器Java开发实例--ObserverCoprocessor
- dubbo协议_Dubbo协议解析与OPPO自研ESA RPC框架实践
- linux初学文档,51CTO博客-专业IT技术博客创作平台-技术成就梦想
- PHP学习笔记十九【析构函数】
- myeclipse-common 找不到
- 《Linux编程》上机作业 ·001【Linux命令】
- cmw500综合测试仪使用_辽宁优质继电器综合测试仪供应商-广州炫通电气科技
- jmail组件 java,分享Jmail发送邮件工具类
- 深度学习vs深度学习,到底嘛意思?
- 视频服务器信号转换器,DVI转换器
- SCRAPY爬虫实例
- 顶级科学家是哲学家,顶级investor是哲学家