1、I/O多路複用概念:python
監聽多個描述符的狀態,若是描述符狀態改變,則會被內核修改標誌位,從而被進程獲取進而進行讀寫操做app
2、select,poll,epollsocket
select模塊,提供了:select、poll、epoll三個方法,分別調用系統的 select,poll,epoll 從而實現IO多路複用。ide
Windows Python:
提供: select
Mac Python:
提供: select
Linux Python:
提供: select、poll、epoll
select 函數
在python中,select函數是一個對底層操做系統的直接訪問的接口,它用來監控sockets、files和pipes,等待IO完成。當有可讀、可寫或是異常事件產生時,select能夠很容易的監控到。spa
select目前幾乎在全部的平臺上支持,其良好跨平臺支持也是它的一個優勢,這也是它所剩很少的優勢之一。操作系統
select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024,不過能夠經過修改宏提高這一限制。code
格式:rList,wList,eList = select.select(argv1,argv2,argv3,timeout)orm
參數:server
argv1:監聽序列中的句柄發生變化時,則獲取發生變化的句柄添加到rList序列中
argv2:監聽序列中含有句柄時,則將該序列中全部的句柄添加到wList序列中
argv3:監聽序列中的句柄發生錯誤時,則將該發生錯誤的句柄添加到eList序列中
timeout:設置阻塞時間,若是不設置則默認一直阻塞
select 實例:
用select實現處理多個socket客戶端請求
服務端
#!/usr/bin/env python# -*- coding:utf-8 -*-import socketimport 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] #刪除隊列