C - 在使用 recv 接收数据时直接写入 while 循环中的数组

C - Writing directly to an array in while loop while receiving data with recv

这是我的代码:

FILE *responseStorage = fopen("response", "w");

if(responseStorage == NULL){
    //error handling
}

int receivedTotal = 0;
while(1){
    ssize_t received = recv(sockfd, buf, BUFSIZ, 0);
    if( received == -1 ){
        //error handling
    }
    if( received == 0 ){
        //end of stream
        break;
    }
    receivedTotal += received;
    if(fwrite(buf, received, 1, responseStorage) != 1){
         //error handling
    }
    memset(buf, '[=10=]', sizeof(buf));
}

fclose(responseStorage);

FILE *responseFile = fopen("response", "r");
char responseArray[receivedTotal];
if(fread(responseArray, receivedTotal, 1, responseFile) == 0){
   //error
}

我正在调用 ssize_t received = recv(sockfd, buf, BUFSIZ, 0);,从服务器接收数据,保存我在 receivedTotal += received; 中接收到的数据量,然后使用 fwrite(buf, received, 1, responseStorage) 将该数据写入我的文件 FILE *responseStorage.在那之后,在流循环结束时,我以 r 模式打开 responseStorage 文件,制作一个大小为 receivedTotalchar responseArray[receivedTotal]; 的数组,并使用 [=19] =] 将该数据从 responeStorage 文件写入 responseArray.

有没有办法直接写入responseArray?我必须稍后验证响应,所以我需要它在一个数组中。我知道我必须动态地为 malloc 数组分配 space。我想避免使用 receivedTotalresponseStorage.

您已经从套接字读取到 buf,因此您所要做的就是将 buf 写入动态分配的字符串而不是 responseStorage。就像您说的那样,您只有 ti handle memory space 来满足您的反应。

执行此操作的低效但非常简单的方法是在每次阅读时重新分配存储空间。您可以为先前响应读取的总和和 buf 中的新字符串分配存储空间,然后将这两个字符串写入新分配的 space。你知道这些字符串的长度之和为空字节+1,所以你不必太担心可用分配space。然而,这是非常昂贵的,因为读取被一遍又一遍地复制。

稍微复杂一点的方法是分配一个可能相当大的主响应字符串缓冲区,跟踪其分配的总长度 space 并使用 strncat 继续连接 buf 直到它的长度超过响应中剩余的 space,+1(用于终止 nil 字节)。当空间不足时,可以调用realloc获取更多内存。 realloc 不是很有效(按照 C 标准),因为它可能需要分配不同的 space,复制现有数据,然后 return 一个新指针。

如果你想变得非常聪明,你可以分配一个大缓冲区,然后 read 发送一个指向缓冲区中下一个可用点偏移量的指针。您可能仍然需要增加缓冲区,但至少您不需要复制它。 buf 然后成为您的响应数组。这是我将演示的实现:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char**argv){
  size_t size = 2;
  size_t len  = 0;
  char* fullbuf = malloc(sizeof(char)*size);
  while(1) {
    ssize_t b = read(STDIN_FILENO, fullbuf + len, sizeof(char) * (size - len-1) );
    if( b < 0) {
      perror("Couldn't read from stdin: ");
      exit(2);
    } else if( b == 0 ){
      break;
    }
    if( b + len + 1 >= size) {
      // time to allocate more memory
      size = size * 2;
      fullbuf = realloc(fullbuf, sizeof(char) * size);
      if( fullbuf == NULL ){
        fprintf(stderr, "Couldn't allocate %zd more bytes of memory\n", size);
        exit(1);
      }
    }
    len += b;
  }
  fullbuf[len] = '[=10=]'; //terminating null space
  printf("%s", fullbuf);
}

对于这个演示,我从标准输入而不是套接字读取,但想法相同。我只读取 buf 中可用的数据。当它已满(但终止字节)时,我将其 space 加倍。请注意,我将 fullbuf 设置为 realloc 的输出 - 它可能是相同的地址,也可能不是。

为了证明它有效,我从相当疯狂的 2 字节缓冲区开始,然后从那里加倍,所以有很多 realloc 调用。我抓取了 32k 的 lorem ipsum 作为输入。

$ du -h file.txt
 32K    file.txt
$ shasum -a 256 file.txt
346f2adbd1fdca6bf3b03fb0a4d2fd0030e3363e9a9c5d1e22747e1bcc316e37  file.txt
$ ./t  < file.txt | shasum -a 256
346f2adbd1fdca6bf3b03fb0a4d2fd0030e3363e9a9c5d1e22747e1bcc316e37  -

太棒了,如果shasums相同,那就意味着我输出了file.txt

在 Windows 中,最有效的方法是使用 VirtualAlloc,保留一个非常大的块并最初提交一页(4096 字节)。如果 BUFSIZ 较大,请提交更多页面以使其适合。将地址分配给 responsiveArray。阅读那里的第一个数据包。对于下一个 recv 调用,继续提交额外的页面并调整 recv 缓冲区地址,确保有最少的 BUFSIZ 空闲字节可用。您将拥有连续的地址 space,没有碎片,没有占用额外的内存,没有重写,也没有写入磁盘。