簡體   English   中英

Python - 使用選擇器的非阻塞套接字

[英]Python - non-blocking sockets using selectors

我的問題簡而言之:我不知道選擇器如何知道應該先讀取或寫入哪個套接字。

它是一個可以處理多連接的服務器,它的流程應該是:

  1. 服務器創建監聽套接字
  2. 客戶端創建 2 個套接字並將它們連接到服務器
  3. 客戶端 2 套接字發送消息
  4. 服務器 2 套接字回顯這些消息,客戶端和服務器關閉連接

這就是發生的情況,但是如果創建的服務器套接字首先寫入,則連接將立即關閉或拋出異常(?),因為它甚至不調用 send 並且客戶端套接字不會接收任何內容。 那么選擇器如何知道哪些套接字應該首先准備好寫入/讀取? 我錯過了哪些信息才能理解這一點?

服務器:

import socket
import selectors
import types

host = "127.0.0.1"
port = 63210

def accept_wrapper(sock):
    conn, addr = sock.accept()
    print('accepted connection from', addr)
    conn.setblocking(False)
    data = types.SimpleNamespace(addr=addr, inb=b'', outb=b'')
    events = selectors.EVENT_READ | selectors.EVENT_WRITE
    sel.register(conn, events, data=data)

def service_connection(key, mask):
    sock = key.fileobj
    data = key.data
    if mask & selectors.EVENT_READ:
        recv_data = sock.recv(1024)
        if recv_data:
            data.outb += recv_data
        else:
            print('closing connection to', data.addr)
            sel.unregister(sock)
            sock.close()
    if mask & selectors.EVENT_WRITE:
        if data.outb:
            print('echoing', repr(data.outb), 'to', data.addr)
            sent = sock.send(data.outb)
            data.outb = data.outb[sent:]

sel = selectors.DefaultSelector()

lsock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
lsock.bind((host, port))
lsock.listen()
print('listening on', (host, port))
lsock.setblocking(False)
sel.register(lsock, selectors.EVENT_READ, data=None)

while True:
    events = sel.select(timeout=None)
    for key, mask in events:
        if key.data is None:
            accept_wrapper(key.fileobj)
        else:
            service_connection(key, mask)

客戶:

import socket
import selectors
import types

host = "127.0.0.1"
port = 63210
num_conns = 2
messages = [b'Message 1 from client.', b'Message 2 from client.']

def start_connections(host, port, num_conns):
    server_addr = (host, port)
    for i in range(0, num_conns):
        connid = i + 1
        print('starting connection', connid, 'to', server_addr)
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.setblocking(False)
        sock.connect_ex(server_addr)
        events = selectors.EVENT_READ | selectors.EVENT_WRITE
        data = types.SimpleNamespace(connid=connid,
                                     msg_total=sum(len(m) for m in messages),
                                     recv_total=0,
                                     messages=list(messages),
                                     outb=b'')
        sel.register(sock, events, data=data)

def service_connection(key, mask):
    sock = key.fileobj
    data = key.data
    if mask & selectors.EVENT_READ:
        recv_data = sock.recv(1024)
        if recv_data:
            print('received', repr(recv_data), 'from connection', data.connid)
            data.recv_total += len(recv_data)
        if not recv_data or data.recv_total == data.msg_total:
            print('closing connection', data.connid)
            sel.unregister(sock)
            sock.close()
    if mask & selectors.EVENT_WRITE:
        if not data.outb and data.messages:
            data.outb = data.messages.pop(0)
        if data.outb:
            print('sending', repr(data.outb), 'to connection', data.connid)
            sent = sock.send(data.outb)
            data.outb = data.outb[sent:]

sel = selectors.DefaultSelector()
start_connections(host, port, num_conns)

while True:
    events = sel.select(timeout=None)
    for key, mask in events:
        service_connection(key, mask)

套接字實際上並不直接寫入對等方,也不從對等方讀取。 相反,它們寫入本地套接字特定的寫入緩沖區並從套接字特定的讀取緩沖區中讀取。 操作系統內核負責將數據從套接字寫入緩沖區傳送到對等方,並將從對等方接收到的數據包放入套接字接收緩沖區。

這些內核套接字緩沖區的狀態和對這些緩沖區的更改可以使用selectpollkqueue等函數進行監視。 本質上:如果套接字寫入緩沖區中有空間,則認為套接字可寫。 如果套接字讀取緩沖區中有數據,則認為套接字是可讀的。

我查看了您擁有的相同代碼,並針對您所關注的問題提出了拉取請求: https : //github.com/realpython/materials/pull/112

本質上,答案是套接字和讀+寫事件由系統在一個循環中循環。 最終每個套接字都可以在這個循環中讀寫,你只需要等待輪到發生。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM