python中的套接字故障

时间:2009-03-15 13:34:19

标签: python sockets

我有一个用C编写的服务器,我想在python中编写一个客户端。当python客户端想要发送文件,然后是文件的内容和字符串“end some_file”时,它将发送一个字符串“send some_file”。这是我的客户代码:


file = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
send_str = "send %s" % file
end_str = "end %s" % file
sock.send(send_str)
sock.send("\n")
sock.send(open(file).read())
sock.send("\n")
sock.send(end_str)
sock.send("\n")

问题在于:

  • 服务器从recv

  • 接收“send some_file”字符串
  • 在第二个recv中,文件的内容和“结束文件”字符串一起发送

在服务器代码中,缓冲区的大小为4096.我在尝试发送小于4096k的文件时首先注意到这个错误。 如何确保服务器独立接收字符串?

4 个答案:

答案 0 :(得分:9)

使用套接字编程,即使您执行2次独立发送,也不意味着另一方将接收它们作为2个独立的recv。

一个适用于字符串和二进制数据的简单解决方案是:首先发送消息中的字节数,然后发送消息。

以下是每条消息应该做的事情,无论是文件还是字符串:

发件人方:

  • 发送4个字节,其中包含以下发送中的字节数
  • 发送实际数据

接收方:

  • 从接收器端执行一个循环,阻止读取4个字节
  • 然后在读取时为前4个字节中指定的字符数做一个块以获取数据。

除了上面提到的4字节长度标头之外,您还可以添加一个常量大小的命令类型标头(再次整数),它描述了以下recv中的内容。

你也可以考虑使用像HTTP这样的协议,它已经为你做了很多工作,并且有很好的包装库。

答案 1 :(得分:1)

我可以通过两种更简单的方式来解决这个问题。两者都涉及客户端和服务器行为的一些变化。

首先是使用填充。假设您正在发送文件。你要做的是读取文件,将其编码为更简单的格式,如Base64,然后发送足够的空格字符来填充4096字节“块”的其余部分。你会做的是这样的事情:

from cStringIO import StringIO
import base64
import socket
import sys

CHUNK_SIZE = 4096 # bytes

# Extract the socket data from the file arguments
filename = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
# Make the socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
# Prepare the message to send
send_str = "send %s" % (filename,)
end_str = "end %s" % (filename,)
data = open(filename).read()
encoded_data = base64.b64encode(data)
encoded_fp = StringIO(encoded_data)
sock.send(send_str + '\n')
chunk = encoded_fp.read(CHUNK_SIZE)
while chunk:
    sock.send(chunk)
    if len(chunk) < CHUNK_SIZE:
        sock.send(' ' * (CHUNK_SIZE - len(chunk)))
    chunk = encoded_fp.read(CHUNK_SIZE)
sock.send('\n' + end_str + '\n')

这个例子似乎有点涉及,但它将确保服务器可以继续以4096字节的块读取数据,而它所要做的就是对另一端的数据进行Base64解码(C库为其可用here .Base64解码器忽略额外的空格,格式可以处理二进制文件和文本文件(例如,如果文件包含“结束文件名”行会发生什么?这会使服务器混淆)。

另一种方法是使用文件的长度为文件的发送添加前缀。因此,例如,您可以说send filename而不是发送send 4192 filename来指定文件的长度为4192字节。客户端必须根据文件的长度构建send_str(读入上面代码中的data变量),并且不需要使用Base64编码,因为服务器不会尝试解释发送文件正文中出现的任何end filename语法。这就是HTTP中发生的事情; Content-length HTTP标头用于指定发送数据的时长。示例客户端可能如下所示:

import socket
import sys

# Extract the socket data from the file arguments
filename = sys.argv[1]
host = sys.argv[2]
port = int(sys.argv[3])
# Make the socket
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect((host,port))
# Prepare the message to send
data = open(filename).read()
send_str = "send %d %s" % (len(data), filename)
end_str = "end %s" % (filename,)
sock.send(send_str + '\n')
sock.send(data)
sock.send('\n' + end_str + '\n')

无论哪种方式,您都必须对服务器和客户端进行更改。最后,在C中实现一个基本的HTTP服务器(或获得一个已经实现的服务器)可能会更容易,因为这似乎就是你在这里所做的。编码/填充解决方案很快但创建了大量冗余发送的数据(因为Base64通常会导致发送的数据量增加33%),长度前缀解决方案也很容易从客户端进行,但可能更难服务器。

答案 2 :(得分:0)

可能使用

sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

将帮助发送每个数据包,因为这会禁用Nagle's algorithm,因为大多数TCP堆栈使用它来连接几个小型数据包(默认情况下我相信)

答案 3 :(得分:-3)

TCP / IP数据被缓冲,或多或少随机。

它只是字节的“流”。如果你愿意,你可以读它,好像用'\ n'字符分隔。但是,它没有被分成有意义的块;也不是。它必须是连续的字节流。

你是如何用C语言阅读的?你正在阅读'\ n'吗?或者你只是简单地阅读缓冲区中的所有内容?

如果您正在阅读缓冲区中的所有内容,您应该会看到这些行或多或少地随机缓存。

但是,如果你读到'\ n',你会一次看到每一行。

如果您希望这确实有效,请阅读http://www.w3.org/Protocols/rfc959/。这显示了如何简单可靠地传输文件:使用两个套接字。一个用于命令,另一个用于数据。

相关问题