python - 为什么套接字行为依赖于打印语句?

标签 python sockets printing

我不明白像打印语句这样的东西会如何影响套接字行为。以下代码在 Windows 上从空闲和 cmd 运行。

服务器.py:

from types import SimpleNamespace
import socket
import selectors

HOST = '127.0.0.1'
PORT = 65433

sel = selectors.DefaultSelector()

server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind((HOST, PORT))
server_sock.listen()
print('Server listening on', (HOST, PORT))

server_sock.setblocking(False)
sel.register(server_sock, selectors.EVENT_READ, data=None)

def accept_new_connection(server_sock):
    connected_sock, addr = server_sock.accept()
    connected_sock.setblocking(False)

    events = selectors.EVENT_READ | selectors.EVENT_WRITE
    data = SimpleNamespace(addr = addr, outb = b'', inb = b'')
    sel.register(connected_sock, events, data=data)

    print('new connection', addr, 'accepted')

def service_connection(key, event):
    connected_sock = key.fileobj
    data = key.data

    if event & selectors.EVENT_READ:
        recv_data = connected_sock.recv(1024)
        if data:
            data.outb += recv_data
        else:
            print('closing the connection', data.addr)
            sel.unregister(connected_sock)
            connected_sock.close()

    elif event & selectors.EVENT_WRITE:
        if data.outb:
            print('echoing', data.outb, 'to', data.addr)
            sent = connected_sock.send(data.outb)
            data.outb = data.outb[sent:]
            print('ANYTHING') # removing this changes socket behaviour
        else:
            # this executes if print('ANYTHING') is not present
            print('nothing to send') 

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

客户端.py:

import socket

HOST = '127.0.0.1'
PORT = 65433

ls = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ls.connect((HOST, PORT))
ls.send(b'Hi world')
ls.recv(1024)
ls.close()

运行这两个程序(首先是服务器,然后是客户端) 产生不同的结果,取决于是否 print('ANYTHING') 存在。

当包含 print('ANYTHING') 运行时, 我明白了

Server listening on ('127.0.0.1', 65433)
new connection ('127.0.0.1', 58675) accepted
echoing b'Hi world' to ('127.0.0.1', 58675)
ANYTHING

没有它

Server listening on ('127.0.0.1', 65433)
new connection ('127.0.0.1', 58678) accepted
echoing b'Hi world' to ('127.0.0.1', 58678)
nothing to send

最佳答案

events = selectors.EVENT_READ | selectors.EVENT_WRITE
...
sel.register(connected_sock, events, data=data)

只要套接字可写,这就会触发 select - 无论您是否确实有东西要在 data.outb 中写入。这意味着只要套接字尚未关闭,它就会始终触发。

ls.connect((HOST, PORT))
ls.send(b'Hi world')

这将连接到服务器。连接将在操作系统内核内部完成,并最终触发程序中的服务器套接字,然后调用 accept。但是,connect 实际上是在服务器内核中建立连接之后(可能是在调用 accept 之前)在客户端中返回的。因此,send 将为新创建的套接字提供已有的数据。

def service_connection(key, event):
    ...
    if event & selectors.EVENT_READ:
        ...
    elif event & selectors.EVENT_WRITE:
        ...

即使套接字既可读又可写,您的服务器仅处理套接字上的单个条件。如上所述,它始终是可写的,但首先会遇到可读的情况,因为一旦完成 accept,来自客户端的数据就已经在服务器端的套接字缓冲区中。因此,只有当 data.outb 中已有前一个 EVENT_READ 的数据时,才会处理第一个 EVENT_WRITE

        sent = connected_sock.send(data.outb)
        data.outb = data.outb[sent:]
        print('ANYTHING') # removing this changes socket behaviour

这会将数据发送到客户端,然后给服务器增加一些延迟,因为打印到终端实际上​​需要一些时间。如果不存在这种延迟(即没有print),那么选择器将立即再次被调用(因为EVENT_WRITE总是触发,见上文),并且您的代码将偶然发现空的data.outb.

如果存在这种延迟,则套接字将被关闭(因此不再触发 EVENT_WRITE),因为客户端在收到数据后立即关闭连接:

ls.recv(1024)
ls.close()

请注意,只有在同一台计算机上使用客户端和服务器时,才会出现这种情况。如果它们位于不同的计算机上,那么网络延迟会自行增加足够的时间差异,因此您可能会看到不同但可能相似的令人困惑的行为。

处理这种情况的正确方法当然是仅当 data.outb 中确实有数据时才启用 EVENT_WRITE,并在发送所有数据后立即禁用它。

关于python - 为什么套接字行为依赖于打印语句?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57999637/

相关文章:

windows - 解锁 accept()

java - 聊天客户端 - 同时使用套接字和服务器套接字

java - 如何在java中按列打印出来

javascript - 如何从 php 服务器打印到 POS 打印机

python - 列表索引越界和堆栈溢出错误

python - 出于好奇 : Why is python3 not using __dict__ compare as the default equality implementation?

python - 简单客户端-服务器脚本不起作用,尝试连接时出现超时错误10060

python - Pandas 日期未显示在绘图上

Python 2.6 导入错误 : No module named argparse

perl - 在 Perl 中如何打印到变量而不是文件?