一、select介紹
select()的機制中提供一fd_set的數據結構,實際上是一long類型的數組, 每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成, 當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或文件可讀或可寫。主要用於Socket通信當中。
總結:select主要用於socket通信當中,能監視我們需要的文件描述變化。
二、非阻塞式I/O編程特點
2.1、如果一個發現I/O有輸入,讀取的過程中,另外一個也有了輸入,這時候不會產生任何反應.這就需要你的程序語句去用到select函數的時候才知道有數據輸入。
2.2、程序去select的時候,如果沒有數據輸入,程序會一直等待,直到有數據為止,也就是程序中無需循環和sleep。
Select在Socket編程中還是比較重要的,可是對於初學Socket的人來說都不太愛用Select寫程序,他們只是習慣寫諸如connect、accept、recv或recvfrom這樣的阻塞程序(所謂阻塞方式block,顧名思義,就是進程或是線程執行到這些函數時必須等待某個事件的發生,如果事件沒有發生,進程或線程就被阻塞,函數不能立即返回)。
可是使用Select就可以完成非阻塞(所謂非阻塞方式non-block,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生,則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高)方式工作的程序,它能夠監視我們需要監視的文件描述符的變化情況——讀寫或是異常。
返回值:准備就緒的描述符數,若超時則返回0,若出錯則返回-1。
三、示例
示例1:模擬select,同時監聽多個端口

import socket import select sk1 = socket.socket() sk1.bind(('0.0.0.0', 8001)) sk1.listen() sk2 = socket.socket() sk2.bind(('0.0.0.0', 8002)) sk2.listen() sk3 = socket.socket() sk3.bind(('0.0.0.0', 8003)) sk3.listen() inputs = [sk1, sk2, sk3, ] while True: r_list, w_list, e_list = select.select(inputs,[],inputs,1) for sk in r_list: # conn表示每一個連接對象 conn, address = sk.accept() conn.sendall(bytes('hello', encoding='utf-8')) conn.close() for sk in e_list: inputs.remove(sk) 解釋: # select內部自動監聽sk1,sk2,sk3三個對象,監聽三個句柄是否發生變化,把發生變化的元素放入r_list中。 # 如果有人連接sk1,則r_list = [sk1] # 如果有人連接sk1和sk2,則r_list = [sk1,sk2] # select中第1個參數表示inputs中發生變化的句柄放入r_list。 # select中第2個參數表示[]中的值原封不動的傳遞給w_list。 # select中第3個參數表示inputs中發生錯誤的句柄放入e_list。 # 參數1表示1秒監聽一次 # 當有用戶連接時,r_list里面的內容[<socket.socket fd=220, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('0.0.0.0', 8001)>]

import socket obj = socket.socket() obj.connect(('127.0.0.1', 8001)) content = str(obj.recv(1024), encoding='utf-8') print(content) obj.close() # 客戶端c2.py import socket obj = socket.socket() obj.connect(('127.0.0.1', 8002)) content = str(obj.recv(1024), encoding='utf-8') print(content) obj.close()
示例2:IO多路復用--使用socket模擬多線程,並實現讀寫分離

#使用socket模擬多線程,使多用戶可以同時連接 import socket import select sk1 = socket.socket() sk1.bind(('0.0.0.0', 8001)) sk1.listen() inputs = [sk1, ] outputs = [] message_dict = {} while True: r_list, w_list, e_list = select.select(inputs, outputs, inputs, 1) print('正在監聽的socket對象%d' % len(inputs)) print(r_list) for sk1_or_conn in r_list: #每一個連接對象 if sk1_or_conn == sk1: # 表示有新用戶來連接 conn, address = sk1_or_conn.accept() inputs.append(conn) message_dict[conn] = [] else: # 有老用戶發消息了 try: data_bytes = sk1_or_conn.recv(1024) except Exception as ex: # 如果用戶終止連接 inputs.remove(sk1_or_conn) else: data_str = str(data_bytes, encoding='utf-8') message_dict[sk1_or_conn].append(data_str) outputs.append(sk1_or_conn) #w_list中僅僅保存了誰給我發過消息 for conn in w_list: recv_str = message_dict[conn][0] del message_dict[conn][0] conn.sendall(bytes(recv_str+'好', encoding='utf-8')) outputs.remove(conn) for sk in e_list: inputs.remove(sk)

import socket obj = socket.socket() obj.connect(('127.0.0.1', 8001)) while True: inp = input('>>>') obj.sendall(bytes(inp, encoding='utf-8')) ret = str(obj.recv(1024),encoding='utf-8') print(ret) obj.close()