Python select

来源:http://www.cnblogs.com/bigberg/archive/2017/12/15/8044581.html
-Advertisement-
Play Games

一、前言 Python的select()方法直接調用操作系統的IO介面,它監控sockets,open files, and pipes(所有帶fileno()方法的文件句柄)何時變成readable 和writeable, 或者通信錯誤,select()使得同時監控多個連接變的簡單,並且這比寫一個 ...


一、前言

  Python的select()方法直接調用操作系統的IO介面,它監控sockets,open files, and pipes(所有帶fileno()方法的文件句柄)何時變成readable 和writeable, 或者通信錯誤,select()使得同時監控多個連接變的簡單,並且這比寫一個長迴圈來等待和監控多客戶端連接要高效,因為select直接通過操作系統提供的C的網路介面進行操作,而不是通過Python的解釋器。

  註意:Using Python’s file objects with select() works for Unix, but is not supported under Windows.

二、select socket

  接下來通過socket server例子要以瞭解select 是如何通過單進程實現同時處理多個非阻塞的socket連接的 

       2.1 socket server 開始監聽

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

  2.2 3個通信列表

  select()方法接收並監控3個通信列表, 第一個是所有的輸入的data,就是指外部發過來的數據,第2個是監控和接收所有要發出去的data(outgoing data),第3個監控錯誤信息,接下來我們需要創建2個列表來包含輸入和輸出信息來傳給select()。 

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

# 所有連接進來的對象都放在inputs
inputs = [server, ]  # 自己也要監控,因為server本身也是個對象

# 需要發送數據的對象
outputs = []

  2.3 添加一個隊列

  所有客戶端的進來的連接和數據將會被server的主迴圈程式放在上面的list中處理,我們現在的server端需要等待連接可寫(writable)之後才能過來,然後接收數據並返回(因此不是在接收到數據之後就立刻返回),因為每個連接要把輸入或輸出的數據先緩存到queue里,然後再由select取出來再發出去。

  Connections are added to and removed from these lists by the server main loop. Since this version of the server is going to wait for a socket to become writable before sending any data (instead of immediately sending the reply), each output connection needs a queue to act as a buffer for the data to be sent through it. 

# 對外發送數據的隊列,記錄到字典中
message_queues = {}

  2.4 主迴圈 

while True:
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
    # 如果沒有任何fd就緒,那程式就會一直阻塞在這裡

  當你把inputs,outputs,exceptional(這裡跟inputs共用)傳給select()後,它返回3個新的list,我們上面將他們分別賦值為readable,writable,exceptional, 所有在readable list中的socket連接代表有數據可接收(recv),所有在writable list中的存放著你可以對其進行發送(send)操作的socket連接,當連接通信出現error時會把error寫到exceptional列表中。

   2.5 Readable list

   Readable list 中的socket 可以有3種可能狀態,第一種是如果這個socket是main "server" socket,它負責監聽客戶端的連接,如果這個main server socket出現在readable里,那代表這是server端已經ready來接收一個新的連接進來了,為了讓這個main server能同時處理多個連接,在下麵的代碼里,我們把這個main server的socket設置為非阻塞模式。  

    for s in readable:  # 每一個s就是有個socket

        if s is server:
            # 別忘記,上面我們server自己也當做一個fd放在了inputs列表裡,傳給了select,如果這個s是server,代表server這個fd就緒了,
            # 就是有活動了, 什麼情況下它才有活動? 當然 是有新連接進來的時候
            # 新連接進來了,接受這個連接
            conn, client_addr = s.accept()
            print("new connection from", client_addr)
            conn.setblocking(0)
            inputs.append(conn)
            # 為了不阻塞整個程式,我們不會立刻在這裡開始接收客戶端發來的數據, 把它放到inputs里, 下一次loop時,這個新連接
            # 就會被交給select去監聽,如果這個連接的客戶端發來了數據 ,那這個連接的fd在server端就會變成就續的,select就會把這個連接返回,
            # 返回到readable 列表裡,然後你就可以loop readable列表,取出這個連接,開始接收數據了, 下麵就是這麼乾的
            
            message_queues[conn] = queue.Queue()  
            # 接收到客戶端的數據後,不立刻返回 ,暫存在隊列里,以後發送

  第二種情況是這個socket是已經建立了的連接,它把數據發了過來,這個時候你就可以通過recv()來接收它發過來的數據,然後把接收到的數據放到queue里,這樣你就可以把接收到的數據再傳回給客戶端了。

        else:   # s不是server的話,那就只能是一個 與客戶端建立的連接的fd了
            # 客戶端的數據過來了,在這接收
            data = s.recv(1024)
            if data:
                print('received [%s] from %s' % (data, s.getpeername()[0]))
                message_queues[s].put(data)  # 收到的數據先放到queue里,一會返回給客戶端
                if s not in outputs:
                    outputs.append(s)  # 為了不影響處理與其它客戶端的連接 , 這裡不立刻返回數據給客戶端

  第三種情況就是這個客戶端已經斷開了,所以你再通過recv()接收到的數據就為空了,所以這個時候你就可以把這個跟客戶端的連接關閉了。

            else:  # 如果收不到data代表什麼呢? 代表客戶端斷開了
                print("client [%s] closed", s)

                if s in outputs:
                    # 既然客戶端都斷開了,我就不用再給它返回數據了,
                    # 所以這時候如果這個客戶端的連接對象還在outputs列表中,就把它刪掉
                    outputs.remove(s)

                inputs.remove(s)  # 這個連接必然在inputs中,也刪掉
                s.close()

                # 關閉的連接在隊列中也刪除
                del message_queues[s]

 

  2.6 writable list

  對於writable list中的socket,也有幾種狀態,如果這個客戶端連接在跟它對應的queue里有數據,就把這個數據取出來再發回給這個客戶端,否則就把這個連接從output list中移除,這樣下一次迴圈select()調用時檢測到outputs list中沒有這個連接,那就會認為這個連接還處於非活動狀態 

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except queue.Empty:
            # 沒有數據了,該連接對象隊列為空,停止檢測
            print('output queue for [%s] is empty' % s.getpeername()[0])
            outputs.remove(s)
        
        else:
            print('send %s to %s' % (next_msg, s.getpeername()[0]))
            s.send(next_msg)

  2.7 exceptional condition

  最後,如果在跟某個socket連接通信過程中出了錯誤,就把這個連接對象在inputs\outputs\message_queue中都刪除,再把連接關閉掉 

    for s in exceptional:
        print('handling exceptional condition for', s.getpeername()[0])
        # 從inputs中刪除
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()
        
        # 刪除隊列
        del message_queues[s]

  註: getpeername() / getsocketname

    getpeername可以獲得伺服器的地址信息和埠號,正好和getsockname獲得本機地址信息和埠號完全相反

三、完整事例

  select server  

# -*- coding: UTF-8 -*-

import select
import socket
import queue
import sys

# Create a TCP/IP socket
server = socket.socket()
# set noblocking
server.setblocking(False)

# Bind the socket to the port
server_address = ('localhost', 9999)
print(sys.stderr, 'starting up on %s port %s' % server_address)
server.bind(server_address)

# Listen for incoming connections
server.listen()

# 所有連接進來的對象都放在inputs
inputs = [server, ]  # 自己也要監控,因為server本身也是個對象

# 需要發送數據的對象
outputs = []

# 對外發送數據的隊列,記錄到字典中
message_queues = {}

while True:
    readable, writable, exceptional = select.select(inputs, outputs, inputs)
    # 如果沒有任何fd就緒,那程式就會一直阻塞在這裡

    for s in readable:  # 每一個s就是有個socket

        if s is server:
            # 別忘記,上面我們server自己也當做一個fd放在了inputs列表裡,傳給了select,如果這個s是server,代表server這個fd就緒了,
            # 就是有活動了, 什麼情況下它才有活動? 當然 是有新連接進來的時候
            # 新連接進來了,接受這個連接
            conn, client_addr = s.accept()
            print("new connection from", client_addr)
            conn.setblocking(0)
            inputs.append(conn)
            # 為了不阻塞整個程式,我們不會立刻在這裡開始接收客戶端發來的數據, 把它放到inputs里, 下一次loop時,這個新連接
            # 就會被交給select去監聽,如果這個連接的客戶端發來了數據 ,那這個連接的fd在server端就會變成就續的,select就會把這個連接返回,
            # 返回到readable 列表裡,然後你就可以loop readable列表,取出這個連接,開始接收數據了, 下麵就是這麼乾的

            message_queues[conn] = queue.Queue()
            # 接收到客戶端的數據後,不立刻返回 ,暫存在隊列里,以後發送

        else:   # s不是server的話,那就只能是一個 與客戶端建立的連接的fd了
            # 客戶端的數據過來了,在這接收
            data = s.recv(1024)
            if data:
                print('received [%s] from %s' % (data, s.getpeername()[0]))
                message_queues[s].put(data)  # 收到的數據先放到queue里,一會返回給客戶端
                if s not in outputs:
                    outputs.append(s)  # 為了不影響處理與其它客戶端的連接 , 這裡不立刻返回數據給客戶端

            else:  # 如果收不到data代表什麼呢? 代表客戶端斷開了
                print("client [%s] closed", s)

                if s in outputs:
                    # 既然客戶端都斷開了,我就不用再給它返回數據了,
                    # 所以這時候如果這個客戶端的連接對象還在outputs列表中,就把它刪掉
                    outputs.remove(s)

                inputs.remove(s)  # 這個連接必然在inputs中,也刪掉
                s.close()

                # 關閉的連接在隊列中也刪除
                del message_queues[s]

    for s in writable:
        try:
            next_msg = message_queues[s].get_nowait()
        except queue.Empty:
            # 沒有數據了,該連接對象隊列為空,停止檢測
            print('output queue for [%s] is empty' % s.getpeername()[0])
            outputs.remove(s)

        else:
            print('send %s to %s' % (next_msg, s.getpeername()[0]))
            s.send(next_msg)

    for s in exceptional:
        print('handling exceptional condition for', s.getpeername()[0])
        # 從inputs中刪除
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()

        # 刪除隊列
        del message_queues[s]

  client

# -*- coding: UTF-8 -*-
import socket

HOST = 'localhost'  # The remote host
PORT = 9999  # The same port as used by the server
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))
while True:
    msg = bytes(input(">>:"), encoding="utf8")
    s.sendall(msg)
    data = s.recv(1024)
    # print(data)

    print('Received', repr(data))

  


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在分散式中,session如何共用,用戶登陸要解決的問題如下圖所示,通過nignx請求轉發,到不同的應用模塊中,需要判斷用戶有沒有登陸驗證通過,問題又來了,app的移動端不像瀏覽器,沒有cookie,session,那麼怎麼搞呢?這時可以使用session外置方式解決,用redis統一管理sessi ...
  • typedef void( *sighandler_t)(int); 1.用typedef給類型起一個別名。 2.為函數指針類型定義別名, 3.函數指針(指向函數的指針) sighandler_t signal(int signum, sighandler_t handler); 1.函數原型 2. ...
  • import java.io.StringReader; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; i... ...
  • 區別: “==” 比較的是兩個引用在記憶體中指向的是不是同一對象(即同一記憶體空間),也就是說在記憶體空間中的存儲位置是否一致。 如果兩個對象的引用相同時(指向同一對象時),“==”操作符返回true,否則返回flase。 註:如果有對記憶體分配及變數存儲位置(堆、棧、方法區常量池、方法區靜態區)感興趣的可 ...
  • 大多數人選擇Java可能只是因為聽說Java前景好、Java比較好找工作、Java語言在TIOBE排行榜上一直位於前三等等之類的原因,但是Java具體好在哪裡,心裡卻是沒有什麼概念的。本文為你解答學Java的前景。 一、Java工程師發展前景 作為一種最流行的網路編程語言之一,java語言在當今信息 ...
  • 在高性能的IO體系設計中,有幾個名詞概念常常會使我們感到迷惑不解。具體如下: 1 什麼是同步? 2 什麼是非同步? 3 什麼是阻塞? 4 什麼是非阻塞? 5 什麼是同步阻塞? 6 什麼是同步非阻塞? 7 什麼是非同步阻塞? 8 什麼是非同步非阻塞? 先來舉個實例生活中的例子: 如果你想吃一份宮保雞丁蓋飯: ...
  • Unexpected exception thrown when create new site: Solution: The doubling was caused by the Counter service (not sure what it actually does but assume ...
  • puts是print string的縮寫。儘管沒有直觀的表示會調用換行符,但是puts會這樣做:如同print,列印用戶的數據,之後自動地轉到新一行。假如讓puts列印已經以換行符結束的一行,它不會再次添加換行符。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...