在这个Python 3客户端-服务器示例中,客户端无法发送多个消息

4 投票
1 回答
9110 浏览
提问于 2025-04-20 12:35

这是一个简单的客户端-服务器示例,服务器会把客户端发送的内容返回,但会把内容反转过来。

服务器:

import socketserver

class MyTCPHandler(socketserver.BaseRequestHandler):
    def handle(self):
        self.data = self.request.recv(1024)
        print('RECEIVED: ' + str(self.data))
        self.request.sendall(str(self.data)[::-1].encode('utf-8'))

server = socketserver.TCPServer(('localhost', 9999), MyTCPHandler)
server.serve_forever()

客户端:

import socket
import threading

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(('localhost',9999))

def readData():
    while True:
        data = s.recv(1024)
        if data:
            print('Received: ' + data.decode('utf-8'))

t1 = threading.Thread(target=readData)
t1.start()

def sendData():
    while True:
        intxt = input()
        s.send(intxt.encode('utf-8'))

t2 = threading.Thread(target=sendData)
t2.start()

我从谷歌找到的一个例子中拿到了服务器的代码,但客户端是我自己从头写的。我的想法是让客户端能够不断地发送和接收来自服务器的数据。

第一次用客户端发送消息是成功的。但是当我尝试发送第二条消息时,出现了这个错误:

ConnectionAbortedError: [WinError 10053] 已建立的连接被您主机上的软件中止

我哪里做错了?

1 个回答

6

对于 TCPServer 来说,处理请求的 handle 方法只会被调用一次来处理整个会话。虽然文档中可能没有说得很清楚,但 socketserver 这个库就像很多标准库一样,既可以直接使用,也可以作为示例代码来参考。这就是为什么文档中会链接到 源代码,你可以清楚地看到每个连接只会调用一次 handle 方法(TCPServer.get_request 只是简单地在套接字上调用 accept)。

所以,你的服务器接收到一个请求后,发送回响应,然后就结束了,关闭连接。

要解决这个问题,你需要使用一个循环:

def handle(self):
    while True:
        self.data = self.request.recv(1024)
        if not self.data:
            print('DISCONNECTED')
            break
        print('RECEIVED: ' + str(self.data))
        self.request.sendall(str(self.data)[::-1].encode('utf-8'))

几个附带说明:

首先,单独使用 BaseRequestHandler 只能处理一个客户端连接。如果你查看 文档的介绍,会发现:

这四个类是 同步 处理请求的;每个请求必须完成后才能开始下一个请求。如果每个请求需要很长时间才能完成,比如需要大量计算,或者返回的数据量很大,客户端处理得很慢,这种方式就不太合适。解决办法是为每个请求创建一个单独的进程或线程;可以使用 ForkingMixInThreadingMixIn 这两个混入类来支持异步行为。

这些混入类在介绍的后面部分有进一步的说明,页面下方底部也有相关内容,最后还有一个不错的示例。文档没有明确说明,但如果你需要在处理程序中进行任何 CPU 密集型的工作,你应该使用 ForkingMixIn;如果你需要在处理程序之间共享数据,你应该使用 ThreadingMixIn;否则选择哪个其实没太大关系。

注意,如果你想处理大量同时连接的客户端(超过几十个),使用分叉或线程都不太合适,这意味着 TCPServer 可能不太适用。在这种情况下,你可能需要使用 asyncio,或者一些第三方库(比如 Twisted、gevent 等)。


调用 str(self.data) 并不是个好主意。你得到的只是字节字符串的源代码兼容表示,比如 b'spam\n'。你想要的是将字节字符串 解码 成相应的 Unicode 字符串:self.data.decode('utf8')


没有保证一边的每个 sendall 都会对应另一边的一个 recv。TCP 是字节流,而不是消息流;在一个 recv 中接收到半条消息,在下一个 recv 中接收到两条半消息都是完全可能的。当在本地进行单一连接测试时,系统负载轻时可能看起来“正常”,但一旦你尝试部署任何假设每个 recv 都能接收到一条消息的代码,你的代码就会出错。更多细节可以参考 Sockets are byte streams, not message streams。如果你的消息只是文本行(就像你的例子),使用 StreamRequestHandler 及其 rfile 属性,而不是 BaseRequestHandler 和它的 request 属性,可以轻松解决这个问题。


你可能想设置 server.allow_reuse_address = True。否则,如果你退出服务器后太快重新启动,它会报错,比如 OSError: [Errno 48] Address already in use

撰写回答