socket之IO多路復用


概述

  目的:同一個線程同時處理多個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)
server

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()
client

操作步驟

  1. 啟動server
  2. 啟動client,並輸入:123
  3. 再次啟動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]
select

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM