2009-11-10 19 views
27

我正在使用TCP服务器的SocketServer模块。 我在这里遇到了一些问题与recv()功能,因为进入的数据包总是有不同的大小,所以如果我指定recv(1024)(我试着用一个更大的价值和更小的),之后2个或3请求,因为被卡住数据包长度会变小(我认为),然后服务器卡住,直到超时。Python套接字接收 - 传入数据包总是有不同的大小

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

如果客户端在同一源端口发送的倍数请求,但服务器卡,任何帮助将是非常赞赏,谢谢!

回答

31

网络是总是不可预知的。 TCP使得许多这种随机行为对你而言会消失。 TCP所做的一件美妙的事情:它保证字节将以相同的顺序到达。但!它确实而不是保证他们将以相同的方式切碎。您只需不能假设从连接的一端发送的每个send()将导致远端的一个recv()具有完全相同的字节数。

当你说socket.recv(x),你说“不返回,直到你从套接字读取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 
+22

Hans L在下面的评论中是正确的,在python中,request.recv()不是一个有效的调用,因为如果强制参数为bufsize。理想情况下,应该删除或编辑这个答案。 http://docs.python.org/library/socket.html – prashantsunkari 2015-11-17 20:30:18

+0

“这就是所谓的”阻塞I/O“:”我喜欢这种通俗的术语...... – repzero 2017-02-01 23:24:12

2

这就是TCP的本质:协议填满数据包(低层是IP数据包)并发送它们。您可以对MTU(最大传输单位)有一定程度的控制权。

换句话说:你必须制定在TCP之上在您的“有效载荷圈定”的定义骑的协议。 “有效载荷描述”是指你提取协议支持的消息单元的方式。这可以像“每个NULL终止的字符串”一样简单。

113

答案由拉里·黑斯廷斯拥有约插座一些大将军的建议,但也有一对夫妇的错误,因为它涉及到recv(bufsize)方法Python的插座模块中是如何工作的。

所以,以澄清,因为这可能会产生混淆,以别人找这种求助:

  1. recv(bufsize)方法的BUFSIZE参数是不可选的。如果您致电recv()(不含参数),将会出现错误。
  2. recv(bufsize)中的bufferlen是最大值大小。如果可用数量较少,则recv将愉快地返回较少的字节。

查看the documentation了解详情。正如Larry所建议的那样,如果您从客户端收到数据并想知道您何时收到所有数据,那么您可能需要将其添加到协议中。有关确定消息结束的策略,请参阅this recipe

正如该配方所指出的,对于某些协议,客户端将在发送数据完成后断开连接。在这些情况下,您的while True循环应该正常工作。如果客户端确实断开了而不是,则需要找出某种方式来表示您的内容长度,定界消息或实现超时。

如果您可以发布您的确切客户端代码和您的测试协议的描述,我很乐意进一步提供帮助。

+0

我发现的最好的方法是计算出消息/文件/数据中的字节数,然后在消息之前发送消息/文件/数据的长度,作为头部,并使用':'等分隔符。 'recv'直到你通过检测':'得到消息的长度,然后'recv'明确你需要根据头部。如果它是一个文件,则一次对文件的“recv”块进行循环,同时确保将recv的大小除以2直到最后一个字节(如果总字节数%2!= 0)。我使用这种方法来传输大文件(GB值),它很适合进度条。 – DuckPuncher 2017-04-02 00:11:46

14

您可以选择使用recv(x_bytes, socket.MSG_WAITALL),它似乎只适用于Unix,并且会返回到x_bytes

0

我知道这是旧的,但我希望这有助于某人。

使用常规的Python插座我发现,您可以发送和接收使用的sendto和recvfrom数据包的信息

# tcp_echo_server.py 
import socket 

ADDRESS = '' 
PORT = 54321 

connections = [] 
host = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
host.setblocking(0) 
host.bind((ADDRESS, PORT)) 
host.listen(10) # 10 is how many clients it accepts 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    for i in reversed(range(len(connections))): 
     try: 
      data, sender = connections[i][0].recvfrom(1500) 
      return data 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 
    return b'' # return empty if no data found 

def write(data): 
    for i in reversed(range(len(connections))): 
     try: 
      connections[i][0].sendto(data, connections[i][1]) 
     except (BlockingIOError, socket.timeout, OSError): 
      pass 
     except (ConnectionResetError, ConnectionAbortedError): 
      close_socket(connections[i][0]) 
      connections.pop(i) 

# Run the main loop 
while True: 
    try: 
     con, addr = host.accept() 
     connections.append((con, addr)) 
    except BlockingIOError: 
     pass 

    data = read() 
    if data != b'': 
     print(data) 
     write(b'ECHO: ' + data) 
     if data == b"exit": 
      break 

# Close the sockets 
for i in reversed(range(len(connections))): 
    close_socket(connections[i][0]) 
    connections.pop(i) 
close_socket(host) 

客户是类似

# tcp_client.py 
import socket 

ADDRESS = "localhost" 
PORT = 54321 

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 
s.connect((ADDRESS, PORT)) 
s.setblocking(0) 

def close_socket(connection): 
    try: 
     connection.shutdown(socket.SHUT_RDWR) 
    except: 
     pass 
    try: 
     connection.close() 
    except: 
     pass 

def read(): 
    """Read data and return the read bytes.""" 
    try: 
     data, sender = s.recvfrom(1500) 
     return data 
    except (BlockingIOError, socket.timeout, AttributeError, OSError): 
     return b'' 
    except (ConnectionResetError, ConnectionAbortedError, AttributeError): 
     close_socket(s) 
     return b'' 

def write(data): 
    try: 
     s.sendto(data, (ADDRESS, PORT)) 
    except (ConnectionResetError, ConnectionAbortedError): 
     close_socket(s) 

while True: 
    msg = input("Enter a message: ") 
    write(msg.encode('utf-8')) 

    data = read() 
    if data != b"": 
     print("Message Received:", data) 

    if msg == "exit": 
     break 

close_socket(s) 
1

注意确切原因为什么你的代码被冻结的是而不是,因为你设置了太高的request.recv()缓冲区大小。这里解释What means buffer size in socket.recv(buffer_size)

此代码将工作,直到它会收到一个空TCP消息(如果您想打印此空消息,它会显示b''):

while True:  
    data = self.request.recv(1024) 
    if not data: break 

和注释,有没有办法发送空的TCP消息。 socket.send(b'')根本行不通。

为什么?由于只有在键入socket.close()时才发送空信息,因此只要您不会关闭连接,脚本就会循环。 As Hans L这里指出的是一些good methods to end message

相关问题