Python-Select/Poll/Epoll使用
select
select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。
另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。
poll
poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。
poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。
另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。
epoll
直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。
另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。
有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的。
下面记录下分别基于Select/Poll/Epoll的echo server实现。
Python Select Server,可监控事件数量有限制:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import select
import socket
import Queue server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(False)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR , 1)
server_address= ('192.168.1.5',8080)
server.bind(server_address)
server.listen(10) #select轮询等待读socket集合
inputs = [server]
#select轮询等待写socket集合
outputs = []
message_queues = {}
#select超时时间
timeout = 20 while True:
print "等待活动连接......"
readable , writable , exceptional = select.select(inputs, outputs, inputs, timeout) if not (readable or writable or exceptional) :
print "select超时无活动连接,重新select...... "
continue;
#循环可读事件
for s in readable :
#如果是server监听的socket
if s is server:
#同意连接
connection, client_address = s.accept()
print "新连接: ", client_address
connection.setblocking(0)
#将连接加入到select可读事件队列
inputs.append(connection)
#新建连接为key的字典,写回读取到的消息
message_queues[connection] = Queue.Queue()
else:
#不是本机监听就是客户端发来的消息
data = s.recv(1024)
if data :
print "收到数据:" , data , "客户端:",s.getpeername()
message_queues[s].put(data)
if s not in outputs:
#将读取到的socket加入到可写事件队列
outputs.append(s)
else:
#空白消息,关闭连接
print "关闭连接:", client_address
if s in outputs :
outputs.remove(s)
inputs.remove(s)
s.close()
del message_queues[s]
for s in writable:
try:
msg = message_queues[s].get_nowait()
except Queue.Empty:
print "连接:" , s.getpeername() , '消息队列为空'
outputs.remove(s)
else:
print "发送数据:" , msg , "到", s.getpeername()
s.send(msg) for s in exceptional:
print "异常连接:", s.getpeername()
inputs.remove(s)
if s in outputs:
outputs.remove(s)
s.close()
del message_queues[s]
Python Poll Server,Select升级版,无可监控事件数量限制,还是要轮询所有事件:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket
import select
import Queue server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(False)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ("192.168.1.5", 8080)
server.bind(server_address)
server.listen(5)
print "服务器启动成功,监听IP:" , server_address
message_queues = {}
#超时,毫秒
timeout = 5000
#监听哪些事件
READ_ONLY = ( select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
READ_WRITE = (READ_ONLY|select.POLLOUT)
#新建轮询事件对象
poller = select.poll()
#注册本机监听socket到等待可读事件事件集合
poller.register(server,READ_ONLY)
#文件描述符到socket映射
fd_to_socket = {server.fileno():server,}
while True:
print "等待活动连接......"
#轮询注册的事件集合
events = poller.poll(timeout)
if not events:
print "poll超时,无活动连接,重新poll......"
continue
print "有" , len(events), "个新事件,开始处理......"
for fd ,flag in events:
s = fd_to_socket[fd]
#可读事件
if flag & (select.POLLIN | select.POLLPRI) :
if s is server :
#如果socket是监听的server代表有新连接
connection , client_address = s.accept()
print "新连接:" , client_address
connection.setblocking(False) fd_to_socket[connection.fileno()] = connection
#加入到等待读事件集合
poller.register(connection,READ_ONLY)
message_queues[connection] = Queue.Queue()
else :
#接收客户端发送的数据
data = s.recv(1024)
if data:
print "收到数据:" , data , "客户端:" , s.getpeername()
message_queues[s].put(data)
#修改读取到消息的连接到等待写事件集合
poller.modify(s,READ_WRITE)
else :
# Close the connection
print " closing" , s.getpeername()
# Stop listening for input on the connection
poller.unregister(s)
s.close()
del message_queues[s]
#连接关闭事件
elif flag & select.POLLHUP :
print " Closing ", s.getpeername() ,"(HUP)"
poller.unregister(s)
s.close()
#可写事件
elif flag & select.POLLOUT :
try:
msg = message_queues[s].get_nowait()
except Queue.Empty:
print s.getpeername() , " queue empty"
poller.modify(s,READ_ONLY)
else :
print "发送数据:" , data , "客户端:" , s.getpeername()
s.send(msg)
#异常事件
elif flag & select.POLLERR:
print " exception on" , s.getpeername()
poller.unregister(s)
s.close()
del message_queues[s]
Python Epoll Server,基于回调的事件通知模式,轻松管理大量连接:
#!/usr/bin/python
# -*- coding: utf-8 -*-
import socket, select
import Queue serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_address = ("192.168.1.5", 8080)
serversocket.bind(server_address)
serversocket.listen(1)
print "服务器启动成功,监听IP:" , server_address
serversocket.setblocking(0)
timeout = 10
#新建epoll事件对象,后续要监控的事件添加到其中
epoll = select.epoll()
#添加服务器监听fd到等待读事件集合
epoll.register(serversocket.fileno(), select.EPOLLIN)
message_queues = {} fd_to_socket = {serversocket.fileno():serversocket,}
while True:
print "等待活动连接......"
#轮询注册的事件集合
events = epoll.poll(timeout)
if not events:
print "epoll超时无活动连接,重新轮询......"
continue
print "有" , len(events), "个新事件,开始处理......"
for fd, event in events:
socket = fd_to_socket[fd]
#可读事件
if event & select.EPOLLIN:
#如果活动socket为服务器所监听,有新连接
if socket == serversocket:
connection, address = serversocket.accept()
print "新连接:" , address
connection.setblocking(0)
#注册新连接fd到待读事件集合
epoll.register(connection.fileno(), select.EPOLLIN)
fd_to_socket[connection.fileno()] = connection
message_queues[connection] = Queue.Queue()
#否则为客户端发送的数据
else:
data = socket.recv(1024)
if data:
print "收到数据:" , data , "客户端:" , socket.getpeername()
message_queues[socket].put(data)
#修改读取到消息的连接到等待写事件集合
epoll.modify(fd, select.EPOLLOUT)
#可写事件
elif event & select.EPOLLOUT:
try:
msg = message_queues[socket].get_nowait()
except Queue.Empty:
print socket.getpeername() , " queue empty"
epoll.modify(fd, select.EPOLLIN)
else :
print "发送数据:" , data , "客户端:" , socket.getpeername()
socket.send(msg)
#关闭事件
elif event & select.EPOLLHUP:
epoll.unregister(fd)
fd_to_socket[fd].close()
del fd_to_socket[fd]
epoll.unregister(serversocket.fileno())
epoll.close()
serversocket.close()
Python-Select/Poll/Epoll使用的更多相关文章
- python select poll epoll的区别
select 优点:为最早的异步io处理模块,他可以再linux上和windows上使用,跨平台兼容性好,而poll和epoll都不能在windows系统环境中使用. 缺点:select的机制决定了他 ...
- python Select\Poll\Epoll异步IO与事件驱动
参考:http://www.cnblogs.com/alex3714/articles/5248247.html 写服务器处理模型的程序时通常采用的模型: (1)每收到一个请求,创建一个新的进程,来处 ...
- Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)
一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...
- Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO
本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO 1. 多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...
- 转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】
下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架, ...
- IO多路复用select/poll/epoll详解以及在Python中的应用
IO multiplexing(IO多路复用) IO多路复用,有些地方称之为event driven IO(事件驱动IO). 它的好处在于单个进程可以处理多个网络IO请求.select/epoll这两 ...
- python 套接字之select poll epoll
python下的select模块使用 以及epoll与select.poll的区别 先说epoll与select.poll的区别(总结) select, poll, epoll 都是I/O多路复用的具 ...
- Python异步非阻塞IO多路复用Select/Poll/Epoll使用,线程,进程,协程
1.使用select模拟socketserver伪并发处理客户端请求,代码如下: import socket import select sk = socket.socket() sk.bind((' ...
- 多进程、协程、事件驱动及select poll epoll
目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...
- Select/Poll/Epoll异步IO
IO多路复用 同步io和异步io,阻塞io和非阻塞io分别是什么,有什么样的区别? io模式 对于一次io 访问(以read为例),数据会先拷贝到操作系统内核的缓冲区,然后才会从操作系统内核的缓冲区拷 ...
随机推荐
- IFrame实现的无刷新(仿ajax效果)...
前台代码: <iframe style="display:none;" name="gg"></iframe> <form act ...
- php将汉字转换为拼音和得到词语首字母(三)
<?php function getfirstchar($s0){ $fchar = ord($s0{0}); if($fchar >= ord("A") and $f ...
- shell脚本中特定符合变量的含义
shell脚本中特定符合变量的含义: $# 传递到脚本的参数个数 $* 以一个单字符串显示所有向脚本传递的参数.与位置变量不同,此选项参数可超过9个 $$ 脚本运行的当前进程PID号 ...
- ChemDraw常用到的几种技巧
ChemDraw对于化学学习的重要性相当于CAD和建筑学.PS和设计,所以如果你是一名生化相关的工作人员,拥有ChemDraw并掌握ChemDraw的使用方法十分必要!这是一款对于我们研究化学的人来说 ...
- CSS样式中” 大于号”
CSS样式中” 大于号” 在一段CSS代码中见到一个大于号(>),代码如下: BODY#css-zen-garden > DIV#extraDiv2 { BACKGROUND-IMAGE: ...
- CRC-16校验C#代码
[csharp] view plaincopyprint? using System; using System.Collections.Generic; using System.Text; usi ...
- 给IT同学推荐这15个不错的学习网站,收藏起来慢慢看吧
1.学堂在线:http://www.xuetangx.com 目前,学堂在线运行了包括包括清华大学.北京大学.复旦大学.斯坦福大学.麻省理工学院.加州大学伯克利分校等国内外几十所顶尖高校的优质课程.在 ...
- java集合的中的集合关系实现或继承关系图
放在这儿一目了然.
- iteritems()
iteritems() 是列表的一个方法,用法如下: In [1]: dict1 = {"name": "Jeny", "age": 18, ...
- Unity读取 JSon配置文件
一.记录 只是记录,现在在项目中删除掉了.先保留下来,以飨来着!!当然有包括自己. 二.读取配置的代码 简单粗暴 [ExecuteInEditMode] public class Config : M ...