I/O多路复用技术经过把多个I/O的阻塞复用到同一个select的阻塞上,从而使得系统在单线程的状况下能够同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的最大优点是系统开销小,系统不须要建立新的额外进程或者线程,也不须要维护这些进程和线程的运行,降底了系统的维护工做量,节省了系统资源,I/O多路复用的主要应用场景以下:html
服务器须要同时处理多个处于监听状态或者多个链接状态的套接字。python
服务器须要同时处理多种网络协议的套接字数组
在Python中的异步I/O的基础就是 select
模块的 select
函数,服务器
select [IO多路复用的机制]网络
# select每次遍历都须要把fd集合从用户态拷贝到内核态,开销较大,受系统限制最大1024
select.select(rlist, wlist, xlist[, timeout])
# poll和select很像 经过一个pollfd数组向内核传递须要关注的事件,没有描述符1024限制
select.poll()
# 建立epoll句柄,注册监听事件,经过回调函数等待事件产生,不作主动扫描,整个过程对fd只作一次拷贝.打开最大文件数后,不受限制,1GB内存大约是10万连接
select.epoll([sizehint=-1])多线程
select.epollapp
EPOLLIN # 监听可读事件
EPOLLET # 高速边缘触发模式,即触发后不会再次触发直到新接收数据
EPOLLOUT # 监听写事件异步
epoll.poll([timeout=-1[, maxevents=-1]]) # 等待事件,未指定超时时间[毫秒]则为一直阻塞等待
epoll.register(fd,EPOLLIN) # 向epoll句柄中注册,新来socket连接,监听可读事件
epoll.modify(fd, EPOLLET | EPOLLOUT) # 改变监听事件为边缘触发,监听写事件
epoll.fileno() # 经过连接对象获得fd
epoll.unregister(fd) # 取消fd监听事件socket
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select s = socket.socket() # 建立一个对象 s.bind(("127.0.0.1", 8888)) # 须要监听的地址和端口(传入参数是一个元组) s.listen(5) inputs = [s] # 监听队列 while True: rs, ws, es = select.select(inputs, [], []) for r in rs: c, addr = s.accept() # 获取客户端链接的信息 print "Client from:", addr inputs.append(c) # 把客户端加入监听队列 else: try: data = r.recv(1024) disconnected = not data except socket.error: # 出现socker错误把 disconnected设置为True disconnected = True if disconnected: # 若是disconnected结果为True print r.getpeername(), "disconnected" inputs.remove(r) # 移除监听 else: print data
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 import socket 4 5 c = socket.socket() 6 c.connect(('127.0.0.1', 8888)) 7 8 while True: 9 inp = raw_input('please input:') 10 c.sendall(inp) 11 c.close()
参考资料:python基础教程(第二版)ide
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket, select EOL1 = b'\n\n' EOL2 = b'\n\r\n' response = b'HTTP/1.0 200 OK\r\nDate: Mon, 1 Jan 1996 01:01:01 GMT\r\n' response += b'Content-Type: text/plain\r\nContent-Length: 13\r\n\r\n' response += b'Hello, world!' s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(('0.0.0.0', 8888)) s.listen(1) s.setblocking(0) epoll = select.epoll() epoll.register(s.fileno(), select.EPOLLIN) try: connections = {} requests = {} responses = {} while True: events = epoll.poll(1) for fileno, event in events: if fileno == s.fileno(): connection, address = s.accept() connection.setblocking(0) epoll.register(connection.fileno(), select.EPOLLIN) connections[connection.fileno()] = connection requests[connection.fileno()] = b'' responses[connection.fileno()] = response elif event & select.EPOLLIN: requests[fileno] += connections[fileno].recv(1024) if EOL1 in requests[fileno] or EOL2 in requests[fileno]: epoll.modify(fileno, select.EPOLLOUT) connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 1) print('-' * 40 + '\n' + requests[fileno].decode()[:-2]) elif event & select.EPOLLOUT: byteswritten = connections[fileno].send(responses[fileno]) responses[fileno] = responses[fileno][byteswritten:] if len(responses[fileno]) == 0: connections[fileno].setsockopt(socket.IPPROTO_TCP, socket.TCP_CORK, 0) epoll.modify(fileno, 0) connections[fileno].shutdown(socket.SHUT_RDWR) elif event & select.EPOLLHUP: epoll.unregister(fileno) connections[fileno].close() del connections[fileno] finally: epoll.unregister(s.fileno())
参考资料 http://scotdoyle.com/python-epoll-howto.html