从套接字读取:保证至少获取x字节吗?

6 投票
8 回答
8423 浏览
提问于 2025-04-15 13:29

我遇到了一个很少见的bug,似乎是在读取一个socket的时候出现的。

在读取数据的过程中,有时候我只收到1到3个字节,而这个数据包其实是比这大得多的。

我从管道编程中学到,通常只要发送方提供足够的数据,我总是能收到至少512个字节。

而且我的发送方每次发送数据时至少会发送4个字节,所以我原本以为在传输开始的时候至少会收到4个字节的。

在99.9%的情况下,我的这个想法是对的……但确实有一些非常少见的情况,收到的字节数少于4个。我觉得这真是太奇怪了,为什么网络系统会这样做呢?

有没有人知道更多的情况?

这是我用来读取数据的代码:

mySock, addr = masterSock.accept()
mySock.settimeout(10.0)
result = mySock.recv(BUFSIZE)
# 4 bytes are needed here ...
...
# read remainder of datagram
...

发送方通过一次send调用发送完整的数据报。

补充说明:整个过程是在本地计算机上进行的,所以没有涉及复杂的网络应用(比如路由器等)。BUFSIZE至少是512,发送方每次发送至少4个字节。

8 个回答

5

你问的“从套接字读取:是否保证至少能获取到x字节?”这个问题,简单的回答是。看看这些套接字方法的文档说明:

>>> import socket
>>> s = socket.socket()
>>> print s.recv.__doc__
recv(buffersize[, flags]) -> data

Receive up to buffersize bytes from the socket.  For the optional flags
argument, see the Unix manual.  When no data is available, block until
at least one byte is available or until the remote end is closed.  When
the remote end is closed and all data is read, return the empty string.
>>> 
>>> print s.settimeout.__doc__
settimeout(timeout)

Set a timeout on socket operations.  'timeout' can be a float,
giving in seconds, or None.  Setting a timeout of None disables
the timeout feature and is equivalent to setblocking(1).
Setting a timeout of zero is the same as setblocking(0).
>>> 
>>> print s.setblocking.__doc__
setblocking(flag)

Set the socket to blocking (flag is true) or non-blocking (false).
setblocking(True) is equivalent to settimeout(None);
setblocking(False) is equivalent to settimeout(0.0).

从这里可以看出,recv()并不一定会返回你请求的字节数。而且,由于你调用了settimeout(10.0),在超时快到的时候,有可能只收到了一部分数据,而不是全部。在这种情况下,recv()会返回它读取到的数据——这可能少于你请求的字节数(不过一直少于4字节的情况似乎不太可能)。

你在问题中提到datagram,这意味着你可能在使用无连接的UDP套接字(而不是TCP)。这两者的区别可以在这里找到。你提供的代码没有显示如何创建套接字,所以我们只能猜测,不过这个细节可能很重要。如果你能提供更完整的代码示例,可能会更有帮助。

如果这个问题可以重复出现,你可以尝试禁用超时(顺便说一下,你似乎没有处理这个问题),看看这样是否能解决问题。

9

据我所知,这种行为是完全合理的。网络套接字在传输数据时,可能会把你的数据分成小块,实际上很可能会这样做。所以你需要准备好处理这种情况,使用合适的缓冲技术。

另一方面,如果你是在本地计算机上传输数据,而你确实只收到了4个字节,这很可能意味着你的代码里有其他地方出了问题。

编辑:一个建议 - 试着启动一个数据包嗅探器,看看传输的数据包是否会是完整的;这可能会帮助你判断问题出在客户端还是服务器端。

16

我猜你是在使用TCP。TCP是一种基于流的协议,它并不知道数据包或消息的边界。

这意味着当你读取数据时,可能会得到比你请求的字节数要少的内容。比如说,如果你的数据是128k,第一次读取时可能只会得到24k,这样你就需要再读取一次才能获取剩下的数据。

下面是一个C语言的例子:

int read_data(int sock, int size, unsigned char *buf) {
   int bytes_read = 0, len = 0;
   while (bytes_read < size && 
         ((len = recv(sock, buf + bytes_read,size-bytes_read, 0)) > 0)) {
       bytes_read += len;
   }
   if (len == 0 || len < 0) doerror();
   return bytes_read;
}

撰写回答