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

时间:2014-09-08 18:27:50

标签: python sockets python-3.x client-server

这是一个简单的客户端 - 服务器示例,其中服务器返回客户端发送的任何内容,但是相反。

服务器:

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()

我从我在Google上找到的一个例子中获取了服务器,但客户端是从头开始编写的。我们的想法是拥有一个可以无限期地从服务器发送和接收数据的客户端。

向客户端发送第一条消息。但是当我尝试发送第二条消息时,我收到了这个错误:

  

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

我做错了什么?

1 个答案:

答案 0 :(得分:3)

对于TCPServer,处理程序的handle方法被调用一次以处理整个会话。这可能在文档中并不完全清楚,但socketserver与stdlib中的许多库一样,意味着作为明确的示例代码以及直接使用,这就是文档链接到{{3你可以清楚地看到每个连接只调用handle一次(the source被定义为只在套接字上调用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只允许您一次处理一个客户端连接。正如TCPServer.get_request所说:

  

这四个类同步处理的请求;必须在下一个请求开始之前完成每个请求。如果每个请求需要很长时间才能完成,这是不合适的,因为它需要大量计算,或者因为它返回了客户端处理速度慢的大量数据。解决方案是创建一个单独的进程或线程来处理每个请求; ForkingMixInThreadingMixIn混合类可用于支持异步行为。

这些mixin类在本节的其余部分进一步描述,introduction in the docsfarther down the page,最后有一个很好的例子。文档没有说清楚,但如果你需要在你的处理程序中做任何CPU密集型工作,你需要ForkingMixIn;如果您需要在处理程序之间共享数据,则需要ThreadingMixIn;否则你选择的并不重要。

请注意,如果您尝试处理大量同时发生的客户端(超过几十个),则分叉和线程都不合适 - 这意味着TCPServer并不合适。对于这种情况,您可能需要at the bottom或第三方库(Twisted,gevent等)。


调用str(self.data)是一个坏主意。您将获得字节字符串的源代码兼容表示,如b'spam\n'。你想要的是解码字节串到等效的Unicode字符串:self.data.decode('utf8')


无法保证一方的每个sendall都会与另一方的recv匹配。 TCP是一个字节流,而不是消息流;完全可以在一个recv中获得半个消息,在下一个消息中获得两个半消息。当在localhost上使用单个连接进行测试时系统处于轻负载状态时,它可能看起来“工作”,但是一旦您尝试部署任何假定每个recv只获得一条消息的代码,您的代码会打破。有关详细信息,请参阅asyncio。请注意,如果您的邮件只是文本行(就像您的示例中所示),请使用StreamRequestHandler及其rfile属性,而不是BaseRequestHandler及其request属性,很容易解决这个问题。


您可能想要设置server.allow_reuse_address = True。否则,如果您退出服务器并再次快速重新启动它,则会因OSError: [Errno 48] Address already in use之类的错误而失败。