Python socket 接收 - 来自包的大小总是不同
我正在使用SocketServer模块来搭建一个TCP服务器。现在我遇到了一些问题,主要是关于recv()
这个函数。因为我收到的数据包大小总是不同的,所以当我指定recv(1024)
(我也试过更大或更小的值)时,服务器在处理2到3个请求后就会卡住。我觉得是因为收到的数据包长度比我指定的要小,然后服务器就一直卡在那里,直到超时。
class Test(SocketServer.BaseRequestHandler):
def handle(self):
print "From:", self.client_address
while True:
data = self.request.recv(1024)
if not data: break
if data[4] == "\x20":
self.request.sendall("hello")
if data[4] == "\x21":
self.request.sendall("bye")
else:
print "unknow packet"
self.request.close()
print "Disconnected", self.client_address
launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test)
launch.allow_reuse_address= True;
launch.serve_forever()
如果客户端通过同一个源端口发送多个请求,但服务器却卡住了,任何帮助都非常感谢,谢谢!
7 个回答
你也可以用 recv(x_bytes, socket.MSG_WAITALL)
这个方法,它好像只在Unix系统上有效,并且会准确返回 x_bytes
指定的字节数。
Larry Hastings的回答提供了一些关于套接字的很好的建议,但在Python的socket模块中,recv(bufsize)
方法的工作原理上有几个错误。
为了澄清这一点,因为这可能会让其他寻求帮助的人感到困惑:
recv(bufsize)
方法中的bufsize参数是必需的。 如果你调用recv()
(没有参数),会出现错误。recv(bufsize)
中的bufferlen是一个最大大小。如果可用的数据少于这个大小,recv会返回更少的字节。
详细信息请查看文档。
现在,如果你正在从客户端接收数据,并想知道何时接收完所有数据,你可能需要在你的协议中添加一些内容——正如Larry所建议的那样。可以参考这个方案,了解如何判断消息结束。
正如那个方案所指出的,对于某些协议,客户端在发送完数据后会直接断开连接。在这种情况下,你的while True
循环应该可以正常工作。如果客户端没有断开连接,你需要想办法来标记内容的长度、分隔消息,或者实现超时机制。
如果你能提供你的具体客户端代码和测试协议的描述,我很乐意进一步帮助你。
注意:正如评论中提到的,Python中不允许在调用recv()时不传参数,因此这个回答可以忽略。
原始回答:
网络是永远不可预测的。TCP协议能帮你处理很多这种随机行为。TCP有一个很棒的功能:它保证数据包会按顺序到达。但!它并不保证数据包会以相同的方式被拆分。你不能假设从连接的一端发送的每一个send()都会在另一端对应一个recv(),并且字节数完全相同。
当你说socket.recv(x)
时,你是在说“在从socket读取到x个字节之前不要返回”。这叫做“阻塞I/O”:你会一直等待,直到你的请求被满足。如果你的协议中的每个消息都是1024个字节,调用socket.recv(1024)
会很好用。但听起来情况并非如此。如果你的消息是固定字节数,只需将这个数字传给socket.recv()
就可以了。
但如果你的消息长度不一样呢?你需要做的第一件事是:停止用明确的数字调用socket.recv()
。将这个:
data = self.request.recv(1024)
改成这个:
data = self.request.recv()
意味着recv()
会在获取到新数据时总是返回。
但现在你有了一个新问题:你怎么知道发送方什么时候发送了完整的消息?答案是:你不知道。你需要将消息的长度作为协议的一个明确部分。最好的方法是:在每条消息前加上一个长度,长度可以是固定大小的整数(请使用socket.ntohs()
或socket.ntohl()
进行网络字节序转换!),或者是一个字符串后面跟着某个分隔符(比如'123:')。第二种方法通常效率较低,但在Python中更容易处理。
一旦你将这个添加到你的协议中,你需要修改你的代码来处理recv()
随时返回任意数量的数据。下面是一个如何做到这一点的例子。我尝试用伪代码或注释来告诉你该怎么做,但不太清晰。所以我用长度前缀作为以冒号结束的数字字符串明确写了出来。给你:
length = None
buffer = ""
while True:
data += self.request.recv()
if not data:
break
buffer += data
while True:
if length is None:
if ':' not in buffer:
break
# remove the length bytes from the front of buffer
# leave any remaining bytes in the buffer!
length_str, ignored, buffer = buffer.partition(':')
length = int(length_str)
if len(buffer) < length:
break
# split off the full message from the remaining bytes
# leave any remaining bytes in the buffer!
message = buffer[:length]
buffer = buffer[length:]
length = None
# PROCESS MESSAGE HERE