Python网络编程篇之select和epoll
Posted 宁信
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python网络编程篇之select和epoll相关的知识,希望对你有一定的参考价值。
1. select 原理
在多路复?的模型中, ?较常?的有select模型和epoll模型。 这两个都是系统接?, 由操作系统提供。 当然, Python的select模块进?了更?级的封装。
?络通信被Unix系统抽象为?件的读写, 通常是?个设备, 由设备驱动程序提供, 驱动可以知道?身的数据是否可?。 ?持阻塞操作的设备驱动通常会实现?组?身的等待队列, 如读/写等待队列?于?持上层(?户层)所需的block或non-block操作。 设备的?件的资源如果可?( 可读或者可写) 则会通知进程, 反之则会让进程睡眠, 等到数据到来可?的时候, 再唤醒进程。这些设备的?件描述符被放在?个数组中, 然后select调?的时候遍历这个数组, 如果对于的?件描述符可读则会返回改?件描述符。 当遍历结束之后,如果仍然没有?个可?设备?件描述符, select让?户进程则会睡眠, 直到等待资源可?的时候在唤醒, 遍历之前那个监视的数组。 每次遍历都是依次进?判断的。
# -*- coding: utf-8 -*- # 2017/11/25 22:55 # select 模拟一个socket server,注意socket必须在非阻塞情况下才能实现IO多路复用。 # 接下来通过例子了解select 是如何通过单进程实现同时处理多个非阻塞的socket连接的。 import select import socket import queue server = socket.socket() server.bind((‘localhost‘,9000)) server.listen(1000) server.setblocking(False) # 设置成非阻塞模式,accept和recv都非阻塞 # 这里如果直接 server.accept() ,如果没有连接会报错,所以有数据才调他们 # BlockIOError:[WinError 10035] 无法立即完成一个非阻塞性套接字操作。 msg_dic = {} inputs = [server,] # 交给内核、select检测的列表。 # 必须有一个值,让select检测,否则报错提供无效参数。 # 没有其他连接之前,自己就是个socket,自己就是个连接,检测自己。活动了说明有链接 outputs = [] # 你往里面放什么,下一次就出来了 while True: readable, writeable, exceptional = select.select(inputs, outputs, inputs) # 定义检测 #新来连接 检测列表 异常(断开) # 异常的也是inputs是: 检测那些连接的存在异常 print(readable,writeable,exceptional) #[<socket.socket fd=500, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=(‘127.0.0.1‘, 9000), raddr=(‘127.0.0.1‘, 61685)>] [] [] for r in readable: if r is server: # 有数据,代表来了一个新连接 conn, addr = server.accept() print("来了个新连接",addr) inputs.append(conn) # 把连接加到检测列表里,如果这个连接活动了,就说明数据来了 # inputs = [server.conn] # 【conn】只返回活动的连接,但怎么确定是谁活动了 # 如果server活动,则来了新连接,conn活动则来数据 msg_dic[conn] = queue.Queue() # 初始化一个队列,后面存要返回给这个客户端的数据 else: try : data = r.recv(1024) # 注意这里是r,而不是conn,多个连接的情况 print("收到数据",data) # r.send(data) # 不能直接发,如果客户端不收,数据就没了 msg_dic[r].put(data) # 往里面放数据 outputs.append(r) # 放入返回的连接队列里 except ConnectionResetError as e: print("客户端断开了",r) if r in outputs: outputs.remove(r) #清理已断开的连接 inputs.remove(r) #清理已断开的连接 del msg_dic[r] ##清理已断开的连接 for w in writeable: # 要返回给客户端的连接列表 data_to_client = msg_dic[w].get() # 在字典里取数据 w.send(data_to_client) # 返回给客户端 outputs.remove(w) # 删除这个数据,确保下次循环的时候不返回这个已经处理完的连接了。 for e in exceptional: # 如果连接断开,删除连接相关数据 if e in outputs: outputs.remove(e) inputs.remove(e) del msg_dic[e]
客户端
# -*- coding: utf-8 -*- # 2017/11/25 22:55 import socket client = socket.socket() client.connect((‘localhost‘, 9000)) while True: cmd = input(‘>>> ‘).strip() if len(cmd) == 0 : continue client.send(cmd.encode(‘utf-8‘)) data = client.recv(1024) print(data.decode()) client.close()
优点
select?前?乎在所有的平台上?持, 其良好跨平台?持也是它的?个优点。
缺点
select的?个缺点在于单个进程能够监视的?件描述符的数量存在最?限制,在Linux上?般为1024, 可以通过修改宏定义甚?重新编译内核的?式提升这?限制, 但是这样也会造成效率的降低。?般来说这个数?和系统内存关系很?, 具体数?可以cat /proc/sys/fs/filemax察看。 32位机默认是1024个。 64位机默认是2048.对socket进?扫描时是依次扫描的, 即采?轮询的?法, 效率较低。当套接字?较多的时候, 每次select()都要通过遍历FD_SETSIZE个Socket来完成调度, 不管哪个Socket是活跃的, 都遍历?遍。 这会浪费很多CPU时间。
2. epoll的优点:
1. 没有最?并发连接的限制, 能打开的FD(指的是?件描述符, 通俗的理解就是套接字对应的数字编号)的上限远?于1024
2. 效率提升, 不是轮询的?式, 不会随着FD数?的增加效率下降。 只有活跃可?的FD才会调?callback函数; 即epoll最?的优点就在于它只管你“活跃”的连接, ?跟连接总数?关, 因此在实际的?络环境中, epoll的效率就会远远?于select和poll。 说明
EPOLLIN ( 可读)
EPOLLOUT ( 可写)
EPOLLET ( ET模式)
epoll对?件描述符的操作有两种模式: LT( level trigger) 和ET( edge trigger) 。
LT模式是默认模式, LT模式与ET模式的区别如下:
LT模式: 当epoll检测到描述符事件发?并将此事件通知应?程序, 应?程序可以不?即处理该事件
ET模式: 当epoll检测到描述符事件发?并将此事件通知应?程序, 应?程序必须?即处理
# -*- coding: utf-8 -*- # 2017/11/26 13:54 import socket import select # 创建套接字 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 设置可以重复使?绑定的信息 s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 绑定本机信息 s.bind(("127.0.0.1",9000)) # 变为被动 s.listen(10) # 创建?个epoll对象 epoll=select.epoll() # 测试, ?来打印套接字对应的?件描述符 print(s.fileno()) print(select.EPOLLIN|select.EPOLLET) # 注册事件到epoll中 # epoll.register(fd[, eventmask]) # 注意, 如果fd已经注册过, 则会发生异常 # 将创建的套接字添加到epoll的事件监听中 epoll.register(s.fileno(),select.EPOLLIN|select.EPOLLET) connections = {} addresses = {} # 循环等待客户端的到来或者对?发送数据 while True: # epoll 进? fd 扫描的地? -- 未指定超时时间则为阻塞等待 epoll_list=epoll.poll() # 对事件进?判断 for fd,events in epoll_list: print(fd) print(events) # 如果是socket创建的套接字被激活 if fd == s.fileno(): conn,addr=s.accept() print(‘有新的客户端到来%s‘%str(addr)) # 将 conn 和 addr 信息分别保存起来 connections[conn.fileno()] = conn addresses[conn.fileno()] = addr # 向 epoll 中注册 连接 socket 的 可读 事件 epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET) elif events == select.EPOLLIN: # 从激活 fd 上接收 recvData = connections[fd].recv(1024) if len(recvData)>0: print(‘recv:%s‘%recvData) else: # 从 epoll 中移除该 连接 fd epoll.unregister(fd) # server 侧主动关闭该 连接 fd connections[fd].close() print("%s---offline---"%str(addresses[fd]))
client
# -*- coding: utf-8 -*- # 2017/11/25 22:55 import socket client = socket.socket() client.connect((‘127.0.0.1‘, 9000)) while True: cmd = input(‘>>> ‘).strip() if len(cmd) == 0 : continue client.send(cmd.encode(‘utf-8‘)) data = client.recv(1024) print(data.decode()) client.close()
以上是关于Python网络编程篇之select和epoll的主要内容,如果未能解决你的问题,请参考以下文章
Python——IO多路复用之select模块epoll方法