之前异步IO一直没搞明白,大致的理解就是在一个大的循环中,有两部分:第一部分是监听事件;第二部分是处理事件(通过添加回调函数的方式)。就拿网络通信来说,可以先通过调用 select 模块中的 select 监听各个 socket 。当 socket 有事件到来时,针对相应的事件做出处理,就这么一直循环下去。所以异步IO也被称为事件驱动IO。原理其实我说得太简单了,所以我会以一个例子来说明一切。不过在这之前我还是要说一下 select 和 epoll 的区别。

一、IO多路服用的select

  IO多路复用相对于阻塞式和非阻塞式的好处就是它可以监听多个 socket ,并且不会消耗过多资源。当用户进程调用 select 时,它会监听其中所有 socket 直到有一个或多个 socket 数据已经准备好,否则就一直处于阻塞状态。select的缺点在于单个进程能够监视的文件描述符的数量存在最大限制,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的的开销也线性增长。同时,由于网络响应时间的延迟使得大量的tcp链接处于非常活跃状态,但调用select()会对所有的socket进行一次线性扫描,所以这也浪费了一定的开销。不过它的好处还有就是它的跨平台特性。

二、 异步IO的epoll

  epoll的优点就是完全的异步,你只需要对其中 poll 函数注册相应的 socket 和事件,就可以完全不管。当有时间发生时,数据已经从内核态拷贝到用户态,也就是完全没有阻塞。

三、基于epoll的聊天室程序

  说了这么多,我决定还是用epoll写一个多人聊天程序。epoll可以支持大量连接,select却有限制,所以这就是我决定用epoll的原因。首先看服务器程序:

 import socket, select
# 服务端 serverSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
serverSock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
serverSock.bind(('127.0.0.1', 8001))
serverSock.listen(5)
serverSock.setblocking(False) EOL = bytes('\n\n', 'utf-8')
QUIT = bytes('\r\n', 'utf-8')
epoll = select.epoll()
epoll.register(serverSock.fileno(), select.EPOLLIN)
print('注册事件:%s'%serverSock.fileno()) try:
connections = {}; requests = {}; responses = {}
while True:
events = epoll.poll(1)
for fileno, event in events:
# print('event:%s fileno:%s'%(event, fileno))
if fileno == serverSock.fileno():
clientSock, address = serverSock.accept()
print('连接到客户端: %s:%s'%(address[0], address[1]))
clientSock.setblocking(False)
connections[clientSock.fileno()] = (clientSock, address)
epoll.register(clientSock.fileno(), select.EPOLLOUT) # socket只能注册输入或输出一个,不能同时注册
requests[clientSock.fileno()] = bytes('', 'utf-8')
responses[clientSock.fileno()] = bytes('你已连接到服务器,IP为{}:{}\n\n'.format(*serverSock.getsockname()),
'utf-8')
elif event & select.EPOLLIN:
requests[fileno] += connections[fileno][0].recv(1024)
if requests[fileno].endswith(EOL):
msg = str(requests[fileno], 'utf-8')
msg = '来自{}的消息:{}'.format(connections[fileno][1], msg[:-2])
requests[fileno] = b''
#print(msg)
for i in responses:
if i == fileno:
continue
responses[i] += bytes(msg, 'utf-8')
epoll.modify(i, select.EPOLLOUT)
if QUIT in requests[fileno]:
epoll.modify(fileno, select.EPOLLOUT) elif event & select.EPOLLOUT:
#print('开始发送消息:%s'%str(responses[fileno], 'utf-8'))
bytesSend = connections[fileno][0].send(responses[fileno])
responses[fileno] = responses[fileno][bytesSend:]
#print('发送完成')
if responses[fileno] == b'':
epoll.modify(fileno, select.EPOLLIN)
if QUIT in requests[fileno]:
epoll.modify(fileno, 0)
connections[fileno][0].shutdown(socket.SHUT_RDWR) elif event & select.EPOLLHUP:
epoll.unregister(fileno)
connections[fileno][0].close()
del connections[fileno]
finally:
epoll.unregister(serverSock.fileno())
epoll.close()
serverSock.close()
print('已退出服务端程序')

注意,我首先定义了两个终止符:EOL表示这段话已经发完了;QUIT表示客户端想要退出。客户端的程序有点让我为难,既要在命令行输入又要同时保证能输出别人发过来的消息,所有我只好用了prompt_toolkit再加上一个线程。如下:

 import socket, prompt_toolkit, select
import threading, queue class Client:
def __init__(self, sock):
self.sock = sock
self.want_to_send = False
self.want_to_recv = True
self._msg = queue.Queue() def fileno(self):
return self.sock.fileno() def handle_recv(self):
print('接受消息..')
msg = self.sock.recv(1024)
print(str(msg, 'utf-8')) def handle_send(self):
msg = self._msg.get()
if msg == '\r\n':
self.want_to_send = False
self.want_to_recv = False
self.sock.sendall(bytes(msg, 'utf-8'))
self.want_to_send = False def handle_sock(want_to_send, want_to_recv, sock):
print('开始处理消息...')
want_to_recv.append(sock.fileno())
while True:
if sock.want_to_send:
if not want_to_send:
want_to_send.append(myclient.fileno())
else:
want_to_send.clear()
can_recv, can_send, _ = select.select(want_to_recv, want_to_send, [], 1)
if can_recv:
sock.handle_recv()
if can_send:
sock.handle_send()
if not (sock.want_to_send or sock.want_to_recv):
print('正停止客户端连接...')
break
if sock._msg.qsize():
sock.want_to_send = True s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1',8001)) myclient = Client(s)
want_to_send = []
want_to_recv = [] t = threading.Thread(target=handle_sock,
args=(want_to_send, want_to_recv, myclient),
daemon=True)
t.start() try:
while True:
messages = prompt_toolkit.shortcuts.prompt('\n\n>>> ',patch_stdout=True)
myclient._msg.put(messages+'\n\n')
except KeyboardInterrupt:
myclient._msg.put('\r\n')
finally:
t.join()
myclient.sock.close()
print('网络已断开')

我的服务器跑在 jupyter 上,客户端跑在命令行上,效果如下:

  客户端接受和发送消息都是互不影响的,这样就实现了一个多人聊天的功能。而且服务器使用的是epoll,所以哪怕是成千上万的人同时在线也没有任何压力。至于怎么测试暂时还没想到办法。

利用epoll实现异步IO的更多相关文章

  1. Select、Poll、Epoll、 异步IO 介绍

    一.概念相关介绍 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的net ...

  2. Python之路-python(Queue队列、进程、Gevent协程、Select\Poll\Epoll异步IO与事件驱动)

    一.进程: 1.语法 2.进程间通讯 3.进程池 二.Gevent协程 三.Select\Poll\Epoll异步IO与事件驱动 一.进程: 1.语法 简单的启动线程语法 def run(name): ...

  3. Python自动化 【第十篇】:Python进阶-多进程/协程/事件驱动与Select\Poll\Epoll异步IO

    本节内容: 多进程 协程 事件驱动与Select\Poll\Epoll异步IO   1.  多进程 启动多个进程 进程中启进程 父进程与子进程 进程间通信 不同进程间内存是不共享的,要想实现两个进程间 ...

  4. python的协程和异步io【select|poll|epoll】

    协程又叫做微线程,协程是一种用户态的轻量级的线程,操作系统根本就不知道协程的存在,完全由用户来控制,协程拥有自己的的寄存器的上下文和栈,协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来后, ...

  5. IO模型之IO多路复用 异步IO select poll epoll 的用法

    IO 模型之 多路复用 IO 多路复用IO IO multiplexing 这个词可能有点陌生,但是如果我说 select/epoll ,大概就都能明白了.有些地方也称这种IO方式为 事件驱动IO ( ...

  6. [.NET] 利用 async & await 进行异步 IO 操作

    利用 async & await 进行异步 IO 操作 [博主]反骨仔 [出处]http://www.cnblogs.com/liqingwen/p/6082673.html  序 上次,博主 ...

  7. Select/Poll/Epoll异步IO

    IO多路复用 同步io和异步io,阻塞io和非阻塞io分别是什么,有什么样的区别? io模式 对于一次io 访问(以read为例),数据会先拷贝到操作系统内核的缓冲区,然后才会从操作系统内核的缓冲区拷 ...

  8. Select\Poll\Epoll异步IO与事件驱动

    事件驱动与异步IO 事件驱动编程是一种编程规范,这里程序的执行流由外部事件来规定.它的特点是包含一个事件循环,但外部事件发生时使用回调机制来触发响应的处理.另外两种常见的编程规范是(单线程)同步以及多 ...

  9. Day11-协程/异步IO/RabbitMQ

    协程,又称微线程,纤程.英文名Coroutine.一句话说明什么是线程:协程是一种用户态的轻量级线程. 协程拥有自己的寄存器上下文和栈.协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候 ...

随机推荐

  1. 基于FPGA的DDR3多端口读写存储管理系统设计

    基于FPGA的DDR3多端口读写存储管理系统设计 文章出处:电子技术设计 发布时间: 2015/03/12 | 1747 次阅读 每天新产品 时刻新体验专业薄膜开关打样工厂,12小时加急出货   机载 ...

  2. 0047 Spring的AOP入门基础--切面组件--通知--切入点

    AOP的全称是Aspect Oriented Programming,面向切面编程. 切面是什么呢,就是一个Java类.之所以叫切面,就是说在调用其他一些类的方法的某个时候(之前,之后,抛异常等),调 ...

  3. 初探csrf学习笔记

    以下是学习了对CSRF的理解,大家切勿作为标准,如有出错请告之! 严禁转载.不想拿自己刚学到的知识去[误人子弟]之所以写出来是让自己巩固和增加理解,他日对此文有不当之处自会修改. [00x1]csrf ...

  4. Windows下安装Django【转】

      Windows下安装Django及WEB服务启动 如果使用的是 Linux 或 Mac OS X ,系统可能已经预装了 Python .在命令提示符下 (或 OS X 的终端中) 输入python ...

  5. jquery Ajax Queue 队列实现

    有时候我们需要按顺序调用一组ajax,这些ajax需要有先后顺序,类似于同步的ajax,那么我们可以通过以下的方式来实现: (这个Ajax用到jQuery.post) //定义一个AJAX队列 $.n ...

  6. Kmeans原理与实现

    原理 http://www.cnblogs.com/jerrylead/archive/2011/04/06/2006910.html 实现 http://www.cnblogs.com/zjutzz ...

  7. MAC Ruby版本需要升级至2.2.2以上

    第一例: 默认情况下,Mac OS X 系统已经安装好 Ruby(最新的 Mavericks 随机的 Ruby 版本为 2.0.0p247),安装在 /System/Library/Framework ...

  8. bootstrap随笔点击增加

          ht5:   <div class="form-group"><label class="col-sm-2 control-label&qu ...

  9. ajax 跨域 4种方法

    一,传统的ajax方法 1,js代码 查看复制打印? $("#ajax").click(function(){ $.ajax({ type: "POST", u ...

  10. js 自定义类

    将近20年前,Javascript诞生的时候,只是一种简单的网页脚本语言.如果你忘了填写用户名,它就跳出一个警告. 如今,它变得几乎无所不能,从前端到后端,有着各种匪夷所思的用途.程序员用它完成越来越 ...