socket.send和socket.sendall()的区别

4

我已经编写了两个程序来测试这个p23_server.py,一个是服务器端的p23_server.py,另一个是客户端的p23_client.py:

p23_server.py

#p23_server.py
import socket

HOST = '10.0.2.15'
PORT = 12345

server = socket.socket()
server.bind((HOST,PORT))
server.listen(1)
(client,addr) = server.accept()
while True:
    data = client.recv(32)
    if not data:
        break
    print(data.decode('utf-8'))
server.close()

p23_client.py

#p23_client.py
import socket
import sys

HOST = '10.0.2.15'
PORT = 12345

string = sys.argv[1]
data_to_send = string.encode('utf-8')


s = socket.socket()
s.connect((HOST,PORT))
#s.sendall(data_to_send)
s.send(data_to_send)
s.close()

我运行了p23_server.py,然后执行了以下命令:

wahalez@wahalez:~/dev/python$ python p23_client.py $(python -c 'for i in range(1024): print("a",end="")')

以运行客户端并查看服务器输出。

我使用socket.send()和socket.sendall()函数分别执行了一次。结果相同。问题是为什么?send不应该只发送一次数据,服务器接收32个字节就完成了吗?

与send()不同,此方法继续从字节发送数据,直到所有数据已发送或出现错误。成功时返回None。出现错误时,会引发异常,并且无法确定是否成功发送了多少数据。

为什么这两个函数产生相同的结果?


你能澄清一下你说的“不应该只发送一次数据,然后服务器接收32字节就结束了”是什么意思吗?send和recv函数中的数据大小并没有严格的关联,例如你可以一次性发送64个字节,并且两次接收32个字节。 - MisterMiyagi
这正是我想表达的。那么有什么区别呢?我试图发送各种长度的数据,但结果仍然相同... - user9872569
你可以在程序内部生成消息,而不是通过stdin传递消息吗?或者只传递所需的“大小”,但在程序中生成消息内容呢? - MisterMiyagi
@MisterMiyagi 你示例中的消息是1024字节,完全适应标准的4096缓冲区。发送缓冲区的大小要大得多,对于我的机器来说,SO_SNDBUF默认为16k。 - Masklinn
1
@Masklinn 我并不是想暗示它们不能更大(我知道它们通常会更大)。由于它们的实际大小取决于系统,我只是想给出一些(旧的)默认值,作为 OP 可能使用的所有系统的下限。关键是,1024 对于这样的测试来说太小了 - MisterMiyagi
显示剩余4条评论
3个回答

4

通常情况下,send() 函数可以在一次调用中发送所有数据,所以只有极少数情况需要多次调用。

然而理论上来说,当你使用 send() 函数时,服务器可能无法接收到所有数据。

为了突出区别,以下是sendall的伪代码实现:

def sendall(data):
    already_sent = 0
    while already_sent < len(data):
        already_sent += send(data[already_sent:])

澄清一下,这与数据包大小无关。 - NadavS
那只是理论上的吗?那我应该使用send all而不是send,以确保我发送完整的数据吗? - user9872569
1
默认情况下,我使用sendall,并且否则检查send的返回值。 - NadavS

1
文档很清晰:

socket.send(bytes[, flags])
向套接字发送数据。套接字必须连接到远程套接字。可选的标志参数与上面的recv()方法具有相同的含义。返回发送的字节数。应用程序负责检查所有数据是否已发送;如果只传输了一部分数据,则应用程序需要尝试传递剩余的数据。有关此主题的更多信息,请参阅Socket编程HOWTO。

从版本3.5开始更改:如果系统调用被中断并且信号处理程序没有引发异常,则该方法现在会重试系统调用,而不是引发InterruptedError异常(请参见PEP 475以获取原理)。

socket.sendall(bytes[, flags])
向套接字发送数据。套接字必须连接到远程套接字。可选的标志参数与上面的recv()方法具有相同的含义。与send()不同,此方法继续从字节中发送数据,直到所有数据都已发送或发生错误。成功时返回None。发生错误时,将引发异常,并且无法确定已成功发送多少数据(如果有)。

简而言之,send返回发送的字节数,这可能小于所请求的数量。 sendall在成功传输所有数据时返回None,并在出现错误时引发异常。


我也查了文档,但还是有点困惑。似乎.sendall()能做的只是.send()能做的一部分,如果无法确定成功发送了多少数据。那么使用socket.sendall()有什么意义呢? - Paw in Data
1
@Paw .sendall()内部循环发送数据,直到所有数据都发送完毕。如果发送失败,无法知道发送了多少字节。一般情况下,如果您希望确保整个缓冲区被发送,并且不想检查返回值并传输缓冲区的剩余部分,可以使用.sendall()`(参见NadavS的答案)。这是一个方便的函数。 - Mark Tolonen

0
为什么这两个函数产生相同的结果?
因为你发送的数据很少,要么完全成功,要么完全失败。 sendall 的作用是简单地循环并通过 send 发送负载,直到所有内容都被发送。如果所有内容都适合第一个send,那么它们之间没有区别。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接