1

我正在使用新设计的协议实现一台服务器。根据协议,客户端发送标头,后跟数据。标头包含元信息,包括数据大小

我们提供示例客户端,但客户端程序也可以由第三方编写。因此,我们不能完全依赖标头中提供的数据字段的大小。

现在,我面临一个recv()系统调用问题。

      #define SOCKET_CHUNK_SIZE 4096
      void * value;

 1    value = (void *) malloc(hdr.size);
 2    total_bytes_read = 0;
 3    while(total_bytes_read < hdr.size) {
 4        n = recv(newsockfd, value + total_bytes_read, SOCKET_CHUNK_SIZE, 0);
 5
 6        //fprintf(stderr, " %ld + %d = %ld\n", total_bytes_read, n, total_bytes_read + n);
 7
 8        total_bytes_read += n;
 9
10        if(n == 0 || n < SOCKET_CHUNK_SIZE)
11            break;
12        if(n < 0)
13            send_error_response(newsockfd);
14    }
15
16    fprintf(stderr, "%ld", total_bytes_read);

这对于少量数据(如 9420 字节)非常有效,但对于大量数据则失败。

观察:

让客户端发送一些大量的数据,比如 604697 字节(hdr.size):

  1. recv()只能读取 65280 字节。即第 16 行的 fprintf 打印 65280。(我在我的机器上检查了 SSIZE_MAX,它是 2147483647,所以它比 SOCKET_CHUNK_SIZE 大得多)

  2. 我尝试在调用中使用 MSG_DONTWAIT 标志recv(),但结果相同。

  3. 我尝试使用read()系统调用代替recv(),结果是一样的。

  4. 当我取消注释第 6 行时,它工作得很好!!(但这一行(和第 16 行)仅用于调试目的。我不能将其保留在最终版本中)

  5. 如果我在 中使用 MSG_WAITALL 标志recv(),它可以工作,但是在读取最后一个块时阻塞,因为最后一个块大小小于 SOCKET_CHUNK_SIZE (604697 = 147 * 4096 + 2585)。因此,除非我依赖于客户端标头中提供的大小并在recv().

客户端提供的数据也可以是二进制的,因此我们不能将某种指示作为数据结束。

欢迎任何有想法/解决方案的人。正如我所提到的,我们有解决方案 - 依赖于客户端标头 - 但只有当我找不到任何其他方法时,我才会更喜欢它。

拉维

4

1 回答 1

2

几乎所有的观察结果都是完全可以解释的:

  1. 我想跳过那个,因为我不确定我是否正确,因为我现在认为它是次要错误。
  2. 不保证 recv 会填满整个缓冲区(除非您设置 MSG_WAITALL)。它在接收到一些字节后返回。因此,如果您不设置 MSG_WAITALL,第 10 行中您的条件的第二部分将阻止您接收更多数据。设置 MSG_WAITALL 使 recv 仅在整个缓冲区被填满后才返回(在您的情况下为 SOCKET_CHUNK_SIZE)。由于您的有效负载大小并不总是 SOCKET_CHUNK_SIZE 的倍数,因此您的最后一个 recv 调用将挂起,直到连接终止。
  3. 这是由于上面提到的第 10 行中的条件。
  4. 坚持使用recv,这是正确的方法
  5. 我猜取消注释第 6 行会改变执行时间,因为偶然比 SOCKET_CHUNK_SIZE 更多的数据到达套接字。

因此,从我的角度来看,最好的办法是使用没有 MSG_WAITALL 标志的 recv 并接受接收小于 SOCKET_CHUNK_SIZE 的块。

于 2013-07-15T05:25:00.913 回答