IO多路复用深入浅出
前言
从零单排高性能问题,这次轮到异步通信了。这个领域入门有点难,需要了解UNIX五种IO模型和 TCP协议,熟练使用三大异步通信框架:Netty、NodeJS、Tornado。目前所有标榜异步的通信框架用的都不是异步IO模型,而是IO多路复 用中的epoll。因为Python提供了对Linux内核API的友好封装,所以我选择Python来学习IO多路复用。
IO多路复用
select
举一个EchoServer的例子,客户端发送任何内容,服务端会原模原样返回。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Created on Feb 16, 2016 @author: mountain
'''
import socket
import select
from Queue import Queue #AF_INET指定使用IPv4协议,如果要用更先进的IPv6,就指定为AF_INET6。
#SOCK_STREAM指定使用面向流的TCP协议,如果要使用面向数据包的UCP协议,就指定SOCK_DGRAM。
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
#设置监听的ip和port
server_address = ('localhost', 1234)
server.bind(server_address)
#设置backlog为5,client向server发起connect,server accept后建立长连接,
#backlog指定排队等待server accept的连接数量,超过这个数量,server将拒绝连接。
server.listen(5)
#注册在socket上的读事件
inputs = [server]
#注册在socket上的写事件
outputs = []
#注册在socket上的异常事件
exceptions = []
#每个socket有一个发送消息的队列
msg_queues = {}
print "server is listening on %s:%s." % server_address
while inputs:
#第四个参数是timeout,可选,表示n秒内没有任何事件通知,就执行下面代码
readable, writable, exceptional = select.select(inputs, outputs, exceptions)
for sock in readable:
#client向server发起connect也是读事件,server accept后产生socket加入读队列中
if sock is server:
conn, addr = sock.accept()
conn.setblocking(False)
inputs.append(conn)
msg_queues[conn] = Queue()
print "server accepts a conn."
else:
#读取client发过来的数据,最多读取1k byte。
data = sock.recv(1024)
#将收到的数据返回给client
if data:
msg_queues[sock].put(data)
if sock not in outputs:
#下次select的时候会触发写事件通知,写和读事件不太一样,前者是可写就会触发事件,并不一定要真的去写
outputs.append(sock)
else:
#client传过来的消息为空,说明已断开连接
print "server closes a conn."
if sock in outputs:
outputs.remove(sock)
inputs.remove(sock)
sock.close()
del msg_queues[sock]
for sock in writable:
if not msg_queues[sock].empty():
sock.send(msg_queues[sock].get_nowait())
if msg_queues[sock].empty():
outputs.remove(sock)
for sock in exceptional:
inputs.remove(sock)
if sock in outputs:
outputs.remove(sock)
sock.close()
del msg_queues[sock][mountain@king ~/workspace/wire]$ telnet localhost 1234
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
1
1select有3个缺点:
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大。
- 每次调用select后,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大。
这点从python的例子里看不出来,因为python select api更加友好,直接返回就绪的socket列表。事实上linux内核select api返回的是就绪socket数目:int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
- fd数量有限,默认1024。
poll
采用poll重新实现EchoServer,只要搞懂了select,poll也不难,只是api的参数不太一样而已。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Created on Feb 27, 2016 @author: mountain
'''
import select
import socket
import sys
import Queue server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server_address = ('localhost', 1234)
server.bind(server_address)
server.listen(5)
print 'server is listening on %s port %s' % server_address
msg_queues = {}
timeout = 1000 * 60
#POLLIN: There is data to read
#POLLPRI: There is urgent data to read
#POLLOUT: Ready for output
#POLLERR: Error condition of some sort
#POLLHUP: Hung up
#POLLNVAL: Invalid request: descriptor not open
READ_ONLY = select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR
READ_WRITE = READ_ONLY | select.POLLOUT
poller = select.poll()
#注册需要监听的事件
poller.register(server, READ_ONLY)
#文件描述符和socket映射
fd_to_socket = { server.fileno(): server}
while True:
events = poller.poll(timeout)
for fd, flag in events:
sock = fd_to_socket[fd]
if flag & (select.POLLIN | select.POLLPRI):
if sock is server:
conn, client_address = sock.accept()
conn.setblocking(False)
fd_to_socket[conn.fileno()] = conn
poller.register(conn, READ_ONLY)
msg_queues[conn] = Queue.Queue()
else:
data = sock.recv(1024)
if data:
msg_queues[sock].put(data)
poller.modify(sock, READ_WRITE)
else:
poller.unregister(sock)
sock.close()
del msg_queues[sock]
elif flag & select.POLLHUP:
poller.unregister(sock)
sock.close()
del msg_queues[sock]
elif flag & select.POLLOUT:
if not msg_queues[sock].empty():
msg = msg_queues[sock].get_nowait()
sock.send(msg)
else:
poller.modify(sock, READ_ONLY)
elif flag & select.POLLERR:
poller.unregister(sock)
sock.close()
del msg_queues[sock]poll解决了select的第三个缺点,fd数量不受限制,但是失去了select的跨平台特性,它的linux内核api是这样的:
int poll (struct pollfd *fds, unsigned int nfds, int timeout);
struct pollfd {
int fd; /* file descriptor */
short events; /* requested events to watch */
short revents; /* returned events witnessed */
};epoll
用法与poll几乎一样。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
Created on Feb 28, 2016 @author: mountain
'''
import select
import socket
import Queue server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server_address = ('localhost', 1234)
server.bind(server_address)
server.listen(5)
print 'server is listening on %s port %s' % server_address
msg_queues = {}
timeout = 60
READ_ONLY = select.EPOLLIN | select.EPOLLPRI
READ_WRITE = READ_ONLY | select.EPOLLOUT
epoll = select.epoll()
#注册需要监听的事件
epoll.register(server, READ_ONLY)
#文件描述符和socket映射
fd_to_socket = { server.fileno(): server}
while True:
events = epoll.poll(timeout)
for fd, flag in events:
sock = fd_to_socket[fd]
if flag & READ_ONLY:
if sock is server:
conn, client_address = sock.accept()
conn.setblocking(False)
fd_to_socket[conn.fileno()] = conn
epoll.register(conn, READ_ONLY)
msg_queues[conn] = Queue.Queue()
else:
data = sock.recv(1024)
if data:
msg_queues[sock].put(data)
epoll.modify(sock, READ_WRITE)
else:
epoll.unregister(sock)
sock.close()
del msg_queues[sock]
elif flag & select.EPOLLHUP:
epoll.unregister(sock)
sock.close()
del msg_queues[sock]
elif flag & select.EPOLLOUT:
if not msg_queues[sock].empty():
msg = msg_queues[sock].get_nowait()
sock.send(msg)
else:
epoll.modify(sock, READ_ONLY)
elif flag & select.EPOLLERR:
epoll.unregister(sock)
sock.close()
del msg_queues[sock]epoll解决了select的三个缺点,是目前最好的IO多路复用解决方案。为了更好地理解epoll,我们来看一下linux内核api的用法。
int epoll_create(int size)//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)//注册事件,每个fd只拷贝一次。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)/*等待IO事件,事件发生时,
内核调用回调函数,把就绪fd放入就绪链表中,并唤醒epoll_wait,epoll_wait只需要遍历就绪链表即可,
而select和poll都是遍历所有fd,这效率高下立判。*/
IO多路复用深入浅出的更多相关文章
- IO模型--阻塞IO,非阻塞IO,IO多路复用,异步IO
IO模型介绍: * blocking IO 阻塞IO * nonblocking IO 非阻塞IO * IO multiplexing IO多路复用 * signal driven IO 信号驱动IO ...
- python 全栈开发,Day44(IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
昨日内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yield,greenlet 遇到IO gevent: 检测到IO,能够使用greenlet实现自动切换,规避了IO阻 ...
- {python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块
python之IO多路复用 阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 ...
- 并发编程(IO多路复用)
阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 异步IO(Asynchro ...
- python开发IO模型:阻塞&非阻塞&异步IO&多路复用&selectors
一 IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非 ...
- (IO模型介绍,阻塞IO,非阻塞IO,多路复用IO,异步IO,IO模型比较分析,selectors模块,垃圾回收机制)
参考博客: https://www.cnblogs.com/xiao987334176/p/9056511.html 内容回顾 协程实际上是一个线程,执行了多个任务,遇到IO就切换 切换,可以使用yi ...
- 【IO多路复用】
" 目录 一.IO模型介绍 二.阻塞IO(blocking IO) 三.非阻塞IO(non-blocking IO) 四.多路复用IO(IO multiplexing) 五.异步IO(Asy ...
- Python(七)Socket编程、IO多路复用、SocketServer
本章内容: Socket IO多路复用(select) SocketServer 模块(ThreadingTCPServer源码剖析) Socket socket通常也称作"套接字" ...
- IO多路复用概念性
sellect.poll.epoll三者的区别 先来了解一下什么是进程切换 为了控制进程的执行,内核必须有能力挂起正在CPU上运行的进程,并恢复以前挂起的某个进程的执行,这种行为为进程的切换,任务切换 ...
随机推荐
- Alamofire源码解读系列(十一)之多表单(MultipartFormData)
本篇讲解跟上传数据相关的多表单 前言 我相信应该有不少的开发者不明白多表单是怎么一回事,然而事实上,多表单确实很简单.试想一下,如果有多个不同类型的文件(png/txt/mp3/pdf等等)需要上传给 ...
- 我的iOS-App
1.PocketConfidential(密保箱) 简介 保存账号密码等敏感信息. 应用技术: sqlite.sqlcipher加密.AES数据加密.GCD https://itunes.apple. ...
- React-Native 之 项目实战(四)
前言 本文有配套视频,可以酌情观看. 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我. 文中所有内容仅供学习交流之用,不可用于商业用途,如因此引起的相关法律法规责任,与我无关. 如文中内容对 ...
- 自定义一个EL函数
自定义一个EL函数 一般就是一下几个步骤,顺便提供一个工作常用的 案例: 1.编写一个java类,并编写一个静态方法(必需是静态方法),如下所示: public class DateTag { pri ...
- MySQL数据库的安装布局
首先我们要安装(mysql-5.0.18-win32_zip) 第一步:点击(Setup.exe) 第二步:开始安装(MySQL Server5.0版本) 1.点击(Next) 2.选Custom自定 ...
- apache-maven-3.3.9 环境配置
本地 maven 环境 安装 Maven 之前要求先确定你的 JDK 已经安装配置完成.Maven是 Apache 下的一个项目,目前我的新版本是 3.3.9. 1.官网下载 Maven:http:/ ...
- 构造函数与普通函数的区别还有关于“new”操作符的一些原理
有一种创建对象的方法叫做工厂模式,例如: function person(name,age){ var o=new Object(); o.name=name; o.age=age; return o ...
- struts2 之 struts2类型转换
1. 在struts2中,相比servlet来时,获取数据时,程序员没有进行手动的类型转换,类型转换工作都有struts2来完成处理,但愿对于自定义类型数据,struts2不会帮助我们完成类型转换工作 ...
- 【web】之 jquery上传插件的Plupload的使用
首先下载plupload->http://www.plupload.com 因为Plupload可配置参数比较多,所以这里讲解最常用的,结合jquery-ui展示的界面!如下: Plupload ...
- 【转】JDBC学习笔记(5)——利用反射及JDBC元数据编写通用的查询方法
转自:http://www.cnblogs.com/ysw-go/ JDBC元数据 1)DatabaseMetaData /** * 了解即可:DatabaseMetaData是描述数据库的元数据对象 ...