一、I/O多路複用指:經過一種機制,能夠監視多個描述符,一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。
二、I/O多路複用避免阻塞在io上,本來爲多進程或多線程來接收多個鏈接的消息變爲單進程或單線程保存多個socket的狀態後輪詢處理。
select
select是經過系統調用來監視一組由多個文件描述符組成的數組,經過調用select()返回結果,數組中就緒的文件描述符會被內核標記出來,而後進程就能夠得到這些文件描述符,而後進行相應的讀寫操做python
select的實際執行過程以下:linux
一、select須要提供要監控的數組,而後由用戶態拷貝到內核態。windows
二、內核態線性循環監控數組,每次都須要遍歷整個數組。數組
三、內核發現文件描述符狀態符合操做結果,將其返回。服務器
因此對於咱們監控的socket都要設置爲非阻塞的,只有這樣才能保證不會被阻塞。數據結構
優勢
基本各個平臺都支持多線程
缺點
一、每次調用select,都須要把fd集合由用戶態拷貝到內核態,在fd多的時候開銷會很大app
二、單個進程可以監控的fd數量存在最大限制,由於其使用的數據結構是數組。socket
三、每次select都是線性遍歷整個數組,當fd很大的時候,遍歷的開銷也很大函數
python使用select
語法:r_list, w_list, e_list = select.select( rlist, wlist, errlist [,timeout] )
說明詳解:
rlist,wlist和errlist均是waitable object; 都是文件描述符,就是一個整數,或者一個擁有返回文件描述符的函數fileno()的對象。
rlist: 等待讀就緒的文件描述符數組
wlist: 等待寫就緒的文件描述符數組
errlist: 等待異常的數組
在linux下這三個列表能夠是空列表,可是在windows上不行
當rlist數組中的文件描述符發生可讀時(調用accept或者read函數),則獲取文件描述符並添加到r數組中。
當wlist數組中的文件描述符發生可寫時,則獲取文件描述符添加到w數組中
當errlist數組中的文件描述符發生錯誤時,將會將文件描述符添加到e隊列中
當超時時間沒有設置時,若是監聽的文件描述符沒有任何變化,將會一直阻塞到發生變化爲止
當超時時間設置爲1時,若是監聽的描述符沒有變化,則select會阻塞1秒,以後返回三個空列表。 若是由變化,則直接執行並返回。
1、基於select實現的IO多路複用的基礎實例:
io_server.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路複用服務器端 5 """ 6 import socket 7 8 sk1 = socket.socket() 9 sk1.bind(('127.0.0.1', 8001)) 10 sk1.listen(5) 11 12 sk2 = socket.socket() 13 sk2.bind(('127.0.0.1', 8002)) 14 sk2.listen(5) 15 16 sk3 = socket.socket() 17 sk3.bind(('127.0.0.1', 8003)) 18 sk3.listen(5) 19 20 inputs = [sk1, sk2, sk3] 21 import select 22 23 while True: 24 #[sk1, sk2, sk3],select內部啓動監聽sk1, sk2, sk3三個對象,一旦某個句柄發生變化 25 #若是有人用sk1 26 #r_list = [sk1, sk2, sk3] 27 r_list, w_list, e_list = select.select(inputs, [], [], 1) 28 print(r_list) 29 for sk in r_list: 30 #每個鏈接對象 31 conn, address = sk.accept() 32 conn.sendall(bytes('Hello', encoding='utf-8')) 33 conn.close()
io_client.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 客戶端1,請求8001端口 5 """ 6 import socket 7 8 ck = socket.socket() 9 ck.connect(('127.0.0.1', 8001)) 10 11 content = str(ck.recv(1024), encoding='utf-8') 12 print(content)
2、IO多路複用服務器端升級改造後的代碼實現
io_server2.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路複用服務器端升級改造 5 """ 6 7 import socket 8 import select 9 10 sk = socket.socket() 11 12 sk.bind(('127.0.0.1', 8001)) 13 sk.listen() 14 15 inputs = [sk,] 16 while True: 17 r_list, w_list, e_list = select.select(inputs, [], [], 1) 18 print('正在監聽的socket對象:%d' % len(inputs)) 19 for sk_or_conn in r_list: 20 #每個鏈接對象 21 if sk_or_conn == sk: 22 #表示有新用戶來鏈接 23 conn, address = sk.accept() 24 inputs.append(conn) 25 else: 26 #有老用戶發消息了 27 try: 28 data_bytes = sk_or_conn.recv(1024) 29 except Exception as ex: 30 #若是有用戶終斷鏈接,則移除句柄 31 inputs.remove(sk_or_conn) 32 else: 33 #用戶正常發送信息 34 data_str = str(data_bytes, encoding='utf-8') 35 sk_or_conn.sendall(bytes(data_str + '好', encoding='utf-8')) 36 37 for sk in e_list: 38 inputs.remove(sk)
3、IO多路複用服務器端升級改造,讀、寫分離
io_server3.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 """ 4 IO多路複用服務器端升級改造,讀、寫分離 5 """ 6 7 import socket 8 import select 9 10 sk = socket.socket() 11 sk.bind(('127.0.0.1', 8001)) 12 sk.listen() 13 14 inputs = [sk,] 15 outputs = [] 16 message_dict = {} 17 18 while True: 19 20 r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1) 21 22 print('正在監聽的socket對象:%d' % len(inputs)) 23 for sk_or_conn in r_list: 24 #每個鏈接對象 25 if sk_or_conn == sk: 26 #表示有新用戶來鏈接 27 conn, address = sk.accept() 28 inputs.append(conn) 29 #將鏈接的用戶添加到字典中 30 message_dict[conn] = [] 31 else: 32 #有老用戶發消息了 33 try: 34 data_bytes = sk_or_conn.recv(1024) 35 except Exception as ex: 36 #若是有用戶終斷鏈接,則移除句柄 37 inputs.remove(sk_or_conn) 38 else: 39 # 用戶正常發送信息 40 data_str = str(data_bytes, encoding='utf-8') 41 message_dict[sk_or_conn].append(data_str) #將用戶發送過來的信息存在在字典中 42 # sk_or_conn.sendall(bytes(data_str + '好', encoding='utf-8')) 43 outputs.append(sk_or_conn) 44 45 #寫操做 46 for sk_out in w_list: 47 recv_data = message_dict[sk_out][0] #從字典中獲取信息數據 48 del message_dict[sk_out][0] #獲取數據後,清空字典,等待存儲下次的數據 49 sk_out.sendall(bytes(recv_data + '好', encoding='utf-8')) 50 outputs.remove(sk_out) 51 52 for sk in e_list: 53 inputs.remove(sk)