在这个Python 3客户端-服务器示例中,客户端无法发送多个消息
这是一个简单的客户端-服务器示例,服务器会把客户端发送的内容返回,但会把内容反转过来。
服务器:
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 个回答
对于 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
只能处理一个客户端连接。如果你查看 文档的介绍,会发现:
这四个类是 同步 处理请求的;每个请求必须完成后才能开始下一个请求。如果每个请求需要很长时间才能完成,比如需要大量计算,或者返回的数据量很大,客户端处理得很慢,这种方式就不太合适。解决办法是为每个请求创建一个单独的进程或线程;可以使用
ForkingMixIn
和ThreadingMixIn
这两个混入类来支持异步行为。
这些混入类在介绍的后面部分有进一步的说明,页面下方和 底部也有相关内容,最后还有一个不错的示例。文档没有明确说明,但如果你需要在处理程序中进行任何 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
。