【问题标题】:Receive fragmented messages with selectors使用选择器接收碎片消息
【发布时间】:2021-10-14 10:24:09
【问题描述】:

由于 TCP 数据包在传输过程中可能会出现碎片,因此您必须调用 recv() 直到它返回 0。这反映在 the python socket documentation 中。

# Echo server program
import socket

HOST = ''                 # Symbolic name meaning all available interfaces
PORT = 50007              # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data: break
            conn.sendall(data)

Python 选择器似乎很酷,但有一个方面让我感到困惑。 example in the docs 建议只调用一次 recv()。

import selectors
import socket

sel = selectors.DefaultSelector()

def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)

def read(conn, mask):
    data = conn.recv(1000)  # Should be ready
    if data:
        print('echoing', repr(data), 'to', conn)
        conn.send(data)  # Hope it won't block
    else:
        print('closing', conn)
        sel.unregister(conn)
        conn.close()

sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

在收到对方发送的所有内容之前,我如何才能确保自己阅读?在直接套接字示例中添加循环不起作用,因为这会导致 BlockingIOError

import selectors
import socket

sel = selectors.DefaultSelector()


def accept(sock, mask):
    conn, addr = sock.accept()  # Should be ready
    print('accepted', conn, 'from', addr)
    conn.setblocking(False)
    sel.register(conn, selectors.EVENT_READ, read)


def read(conn, mask):
    d = bytearray()
    while True:
        data = conn.recv(1000)  # Should be ready
        if data:
            d.extend(data)
        else:
            print('closing', conn)
            sel.unregister(conn)
            conn.close()
            break

    print('echoing', repr(data), 'to', conn)
    conn.send(data)  # Hope it won't block


sock = socket.socket()
sock.bind(('localhost', 1234))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept)

while True:
    events = sel.select()
    for key, mask in events:
        callback = key.data
        callback(key.fileobj, mask)

当然,我可以离开 read() 函数并相信我会得到另一个 EVENT_READ,然后将我得到的内容附加到我保存在某处的缓冲区中。但是我如何区分来自客户端的传输?即我如何检测来自客户端的单个 send() 何时结束?使用直接套接字服务器,在我看来,每当我从单个 send() 收到所有内容时,recv() 都会返回 0。

【问题讨论】:

    标签: python sockets


    【解决方案1】:

    您不能永远(可靠地)仅使用套接字系统调用区分来自客户端的发送。 TCP 旨在可靠地将字节“流”从 peer1 传递到 peer2,而不是“消息”。有很多因素 - 以及各种竞争条件 - 会影响给定的 recv 调用传递的字节数。

    例如,如果某些数据包在您发送消息 1 时被路由器丢弃,但此时您已发送消息 2。TCP 旨在在数据包被丢弃时重试。但是现在,操作系统可能会选择将您的两个“消息”连接起来,并以一大块的形式发送它们。然后,服务器上的 recv 可能会将两条消息都传递到您的缓冲区中。或者根据缓冲区大小和时间,您可以获得 message1 的结尾和 message2 的开头。

    如果您需要不同的信息,则需要在它们周围放置自己的“框架”。有很多方案可以做到这一点,但一种简单的方法是在每条消息前面加上一个固定长度的字段,指示消息的剩余部分有多长:

    [4-byte binary length field indicating length of message 1]
    message 1 blah blah
    [4-byte binary length of message 2]
    message 2 blah blah
    ...
    

    然后对方首先接收固定长度的“header”(如果需要多次调用recv以获得所有4个字节),然后接收指示的“payload”字节数(再次调用recv尽可能多必要的时间)。

    话虽如此,你的read 函数可以有一个循环调用recv 一遍又一遍,你只需要处理BlockingIOError 异常。该异常告诉您现在没有更多数据要接收。它并不表示不可恢复的错误。所以你可以像这样构造你的代码:

    while True:
        select() # Wait for socket to become readable
        callback to read()
    
    def read():
        while True:
            try:
                data = recv()
            except BlockingIOError:
                # End of data for now, return to select
                break
              
            if data:
                append-to-buffer()
            else:
                # EOF (peer closed connection)
                break
    

    【讨论】:

      猜你喜欢
      • 2012-04-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-02
      • 1970-01-01
      • 1970-01-01
      • 2012-09-08
      相关资源
      最近更新 更多