Python—I/O多路復用
一、I/O多路復用概念:
監聽多個描述符的狀態,如果描述符狀態改變,則會被內核修改標誌位,從而被進程獲取進而進行讀寫操作
二、select,poll,epoll
select模塊,提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路復用。
Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
select
在python中,select函數是一個對底層操作系統的直接訪問的接口,它用來監控sockets、files和pipes,等待IO完成。當有可讀、可寫或是異常事件產生時,select可以很容易的監控到。
select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優點,這也是它所剩不多的優點之一。
select的一個缺點在於單個進程能夠監視的文件描述符的數量存在最大限制,在Linux上一般為1024,不過可以通過修改宏提升這一限制。
格式:rList,wList,eList = select.select(argv1,argv2,argv3,timeout)
參數:
argv1:監聽序列中的句柄發生變化時,則獲取發生變化的句柄添加到rList序列中
argv2:監聽序列中含有句柄時,則將該序列中所有的句柄添加到wList序列中
argv3:監聽序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到eList
timeout:設置阻塞時間,如果不設置則默認一直阻塞
select 實例:
用select實現處理多個socket客戶端請求
服務端
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select ip_port = (‘127.0.0.1‘,9999) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #創建socket對象 sk.bind(ip_port) #綁定ip、端口 sk.listen(5) #監聽 sk.setblocking(False) #不阻塞 inputs = [sk,] outputs = [] while True: rlist,wlist,eList = select.select(inputs,outputs,[],0.5) print("inputs:",inputs) #查看inputs列表變化 print("rlist:",rlist) #查看rlist列表變化 for r in rlist: if r == sk: #如果r是服務端 conn,address = r.accept()# inputs.append(conn) print (address) else: client_data = r.recv(1024) if client_data: #如果有數據,返回數據 r.sendall(client_data) else: #否則移除 inputs.remove(r)
客戶端:
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket ip_port = (‘127.0.0.1‘,9999) sk = socket.socket() #創建socket對象 sk.connect(ip_port) #通過ip和端口連接server端 while True: inpu=input(">>:") sk.sendall(bytes(inpu,"utf8")) #給server端發送信息 server_reply = sk.recv(1024) #接受消息 print (str(server_reply,"utf8")) #打印消息 sk.close() #關閉連接
過程:
啟動服務端,這時select會一直監聽服務端句柄,直到有客戶端請求過來發生變化。
當客戶端有新的連接請求過來時,select捕捉到服務端句柄發生變化,把變化的句柄加入到rlist,所以這時r == sk,接收這個鏈接並把句柄加入到inputs列表,
現在,select監聽的就是兩個句柄了。同理,當有多個鏈接請求過來時,都會把它添加到inputs列表中。
當其中的一個客戶端A發送信息過來時,select會在監聽的句柄列表中捕捉到客戶端A這個句柄發生了變化,並把發生變化的句柄加入到rlist,但這時r不等於sk,
執行另一步操作,接收返回數據。
上面講到了argv1參數的概述,是監聽argv1這個列表,當有發生變化時才會捕捉,並加入到rlist。
argv2參數:只要在這個列表裏有值,每次都會加入到wList,不同於argv1
所以可以利用argv2參數實現讀寫分離
server端
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select import queue ip_port = (‘127.0.0.1‘,9999) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #創建socket對象 sk.bind(ip_port) #綁定ip、端口 sk.listen(5) #監聽 sk.setblocking(False) #不阻塞 inputs = [sk,] outputs = [] message={} while True: rlist,wlist,eList = select.select(inputs,outputs,[],0.5) #print("inputs:",inputs) #查看inputs列表變化 #print("rlist:",rlist) #查看rlist列表變化 #print(message) for r in rlist: if r == sk: #如果r是服務端 conn,address = r.accept()# inputs.append(conn) #把連接的句柄加入inputs列表監聽 message[conn] = queue.Queue() #每個新的句柄對應一個隊列 print (address) else: client_data = r.recv(1024) if client_data: #如果有數據,返回數據 outputs.append(r) message[r].put(client_data) #在指定隊列中插入數據 else: inputs.remove(r) #否則移除 del message[r] #刪除隊列 for w in wlist: #如果wlist列表有值 try: data =message[w].get_nowait()#去指定隊列取數據 w.sendall(data) except queue.Empty: pass outputs.remove(w)#因為output列表只要有數據每次都會加入wlist列表,所以發送完數據都要移除
在argv3的監聽列表中,如果在跟某個socket連接通信過程中出了錯誤,就會把錯誤的句柄加到eList ,所以在加個判斷,當某個socket連接通信過程中出了錯誤,就把這個錯誤的連接對象在各個列表和字典中刪除。
在循環裏在加上一個判斷
for e in eList: inputs.remove(e)#刪除inputs監聽的錯誤句柄 if e in outputs:#如果outputs裏有也刪除 outputs.remove(e) e.close() del message[e] #刪除隊列
select的4個參數都介紹完後附上server端完整代碼
#!/usr/bin/env python # -*- coding:utf-8 -*- import socket import select import queue ip_port = (‘127.0.0.1‘,9999) sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #創建socket對象 sk.bind(ip_port) #綁定ip、端口 sk.listen(5) #監聽 sk.setblocking(False) #不阻塞 inputs = [sk,] outputs = [] message={} while True: rlist,wlist,eList = select.select(inputs,outputs,inputs,0.5) #print("inputs:",inputs) #查看inputs列表變化 #print("rlist:",rlist) #查看rlist列表變化 #print(message) for r in rlist: if r == sk: #如果r是服務端 conn,address = r.accept()# inputs.append(conn) #把連接的句柄加入inputs列表監聽 message[conn] = queue.Queue() #每個新的句柄對應一個隊列 print (address) else: client_data = r.recv(1024) if client_data: #如果有數據,返回數據 outputs.append(r) message[r].put(client_data) #在指定隊列中插入數據 else: inputs.remove(r) #否則移除 del message[r] #刪除隊列 for w in wlist: #如果wlist列表有值 try: data =message[w].get_nowait()#去指定隊列取數據 w.sendall(data) except queue.Empty: pass outputs.remove(w)#因為output列表只要有數據每次都會加入wlist列表,所以發送完數據都要移除 for e in eList: inputs.remove(e)#刪除inputs監聽的錯誤句柄 if e in outputs:#如果outputs裏有也刪除 outputs.remove(e) e.close() del message[e] #刪除隊列
參考:http://www.cnblogs.com/wupeiqi/articles/5040823.html
分類: python 好文Python—I/O多路復用