python成長之路【第十篇】:淺析python select模塊


一、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)>]
服務端s1.py
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()
客戶端c1.py

 

示例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)
服務端s1.py
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()
客戶端c1.py

 


免責聲明!

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



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