我有一个用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
在第二个recv中,文件的内容和“结束文件”字符串一起发送
在服务器代码中,缓冲区的大小为4096.我在尝试发送小于4096k的文件时首先注意到这个错误。 如何确保服务器独立接收字符串?
答案 0 :(得分:9)
使用套接字编程,即使您执行2次独立发送,也不意味着另一方将接收它们作为2个独立的recv。
一个适用于字符串和二进制数据的简单解决方案是:首先发送消息中的字节数,然后发送消息。
以下是每条消息应该做的事情,无论是文件还是字符串:
发件人方:
接收方:
除了上面提到的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/。这显示了如何简单可靠地传输文件:使用两个套接字。一个用于命令,另一个用于数据。