IO多路复用
6.select版-TCP服务器:最多1024
import select import socket import sys server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.bind(('', 7788)) server.listen(5) inputs = [server, sys.stdin] running = True while True: # 调用 select 函数,阻塞等待 readable, writeable, exceptional = select.select(inputs, [], []) # 数据抵达,循环 for sock in readable: # 监听到有新的连接 if sock == server: conn, addr = server.accept() # select 监听的socket inputs.append(conn) # 监听到键盘有输入 elif sock == sys.stdin: cmd = sys.stdin.readline() running = False break # 有数据到达 else: # 读取客户端连接发送的数据 data = sock.recv(1024) if data: sock.send(data) else: # 移除select监听的socket inputs.remove(sock) sock.close() # 如果检测到用户输入敲击键盘,那么就退出 if not running: break server.close()
总结
优点
select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点。
缺点
select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制,但是这样也会造成效率的降低。
一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
对socket进行扫描时是依次扫描的,即采用轮询的方法,效率较低。
当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。
7.epoll版-TCP服务器 (听不懂???)
1. epoll的优点:
- 没有最大并发连接的限制,能打开的FD(指的是文件描述符,通俗的理解就是套接字对应的数字编号)的上限远大于1024
- 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,epoll的效率就会远远高于select和poll。
2.文件描述符
3.. epoll使用参考代码
import socket import select # 创建套接字 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 设置可以重复使用绑定的信息 s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) # 绑定本机信息 s.bind(("",7788)) # 变为被动 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]))