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