概述
目的:同一個線程同時處理多個IO請求。
本文以python的select模塊來實現socket編程中一個server同時處理多個client請求的問題。
web框架tornado就是以此實現多客戶端連接問題的。以下為select源碼說明:
def select(rlist, wlist, xlist, timeout=None): # real signature unknown; restored from __doc__ """ select(rlist, wlist, xlist[, timeout]) -> (rlist, wlist, xlist) Wait until one or more file descriptors are ready for some kind of I/O. The first three arguments are sequences of file descriptors to be waited for: rlist -- wait until ready for reading wlist -- wait until ready for writing xlist -- wait for an ``exceptional condition'' If only one kind of condition is required, pass [] for the other lists. A file descriptor is either a socket or file object, or a small integer gotten from a fileno() method call on one of those. The optional 4th argument specifies a timeout in seconds; it may be a floating point number to specify fractions of seconds. If it is absent or None, the call will never time out. The return value is a tuple of three lists corresponding to the first three arguments; each contains the subset of the corresponding file descriptors that are ready. *** IMPORTANT NOTICE *** On Windows and OpenVMS, only sockets are supported; on Unix, all file descriptors can be used. """ pass # classes
實例1
server端
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket import select ss = socket.socket() ss.bind(("localhost",8000)) ss.listen(5) ss.setblocking(False) inputs = [ss] while True: rList,wList,e = select.select(inputs,[],[],2) print "inputs:", inputs print "resaults:", rList for r in rList: if r == ss: con,addr = r.accept() inputs.append(con) else: try: data = r.recv(1024) except socket.error,e: inputs.remove(r) else: r.send(data)
client端
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket sc = socket.socket() sc.connect(("localhost",8000)) while True: data = raw_input("Input:") sc.sendall(data) print sc.recv(1024) sc.close()
操作步驟
- 啟動server
- 啟動client,並輸入:123
- 再次啟動client,並輸入:345
操作結果:
- client1
Input:123
123
Input:
- client2
Input:345
345
Input:
- server端
#啟動server,不啟動client,select監聽的句柄為:ss_70,無變化,select監聽結果:resaults = [] inputs: [<socket._socketobject object at 0x0241FC70>] resaults: [] #啟動client,句柄ss_FC70連接了客戶端,select將監聽到的變化的句柄返回,rList=[ss_70] inputs: [<socket._socketobject object at 0x0241FC70>] resaults: [<socket._socketobject object at 0x0241FC70>] #將ss.accept()得到的客戶端句柄conn_A8追加至監聽列表,inputs = [ss_70,conn_A8,],當client不發送請求時,select監聽的inputs列表中的句柄沒有發生變化,返回列表resaults=[] inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>] resaults: [] #client發送信息時,server端select監聽的inputs列表中conn_A8句柄發生變化,select返回監聽結果:rList = [conn_A8] inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>] resaults: [<socket._socketobject object at 0x0241FCA8>] #在無client連接和沒有已連接的客戶端發送消息,select所監聽的inputs列表無增加,返回列表rList為空 inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>] resaults: [] #client項server發送消息,select監聽列表中客戶端句柄conn_A8發生變化,select監聽返回結果rList=[conn_A8] inputs: [<socket._socketobject object at 0x024DFC70>, <socket._socketobject object at 0x024DFCA8>] resaults: [<socket._socketobject object at 0x024DFCA8>] #接下來就有意思了,保持第一個客戶端不斷開,再打開第二個客戶端client2,句柄ss_70發生變化(我們在服務端只創建了一個socket實例),又會產生一個新的client2的回話句柄conn_E0,追加至select監聽列表inputs中 inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>] resaults: [<socket._socketobject object at 0x0241FC70>] inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>, <socket._socketobject object at 0x0241FCE0>] resaults: [] #client2向server端發送消息,client2的回話句柄conn_E0發生變化,被返回,rList=[conn_E0] inputs: [<socket._socketobject object at 0x0241FC70>, <socket._socketobject object at 0x0241FCA8>, <socket._socketobject object at 0x0241FCE0>] resaults: [<socket._socketobject object at 0x0241FCE0>]
實例2
#!/usr/bin/env python #-*- coding:utf-8 -*- import socket import select import Queue ss = socket.socket() ss.bind(("localhost",8000)) ss.listen(5) #設置為False,accept接受消息時為非阻塞 ss.setblocking(False) #selec監聽列表,若列表中哪個句柄發生變化,返回個readAble,否則readAble列表為空 rList = [ss] #寫列表,writeAble == wList,二者相等,select返回值即wList的值 wList = [] #定義一個字典,key:客戶端句柄,value:接收和發送的消息隊列;用於收和發之間共享數據 msg_queues = {} while True: readAble,writeAble,e = select.select(rList,wList,[],2) for r in readAble: if r == ss: conn,addr = r.accept() rList.append(conn) else: #創建接收的消息隊列 msg_queues[r] = Queue.Queue() try: data = r.recv(1024) #客戶端斷開連接會拋出:socket.error 10054異常,將此客戶端連接句柄從select監聽列表中移除 except socket.error,e: rList.remove(r) else: #將接收到的消息加入句柄所對應的隊列中 msg_queues[r].put(data) #如果此client句柄wList列表不存在,就加入wList列表 if r not in wList: wList.append(r) for w in wList: try: w.sendall(msg_queues[w].get_nowait()) except Queue.Empty: pass #因為select監聽wList時,只要wList列表中有,就返回wList中所有的句柄,所以使用完后需要刪除 wList.remove(w) del msg_queues[w]
