从套接字读取时出现意外行为

Unexpected behavior when reading from socket

我编写了以下函数,通过套接字从服务器读取 http 响应。我在阅读像 this page 这样的文本页面时没有问题,但是当我尝试阅读图像时:

即使读取 returns 正确的字节数,读取也会继续,但不会向缓冲区添加数据。

函数:

unsigned char *read_unknown_size(int fd) {
    int available_buf_size = 1000, tot_read = 0, curr_read_size;
    unsigned char *buf = calloc(available_buf_size, 1), *tmp_ptr;
    if (buf) {
        while ((curr_read_size = (int) read(fd, buf + tot_read, available_buf_size - tot_read)) != 0) {
            if (curr_read_size == -1) {
                perror("failed to read\n");
                //todo free mem
                exit(EXIT_FAILURE);
            } else {
                tot_read += curr_read_size;
                if (tot_read >= available_buf_size) { //the buffer is full
                    available_buf_size *= 2;
                    tmp_ptr = realloc(buf, available_buf_size + tot_read);
                    if (tmp_ptr) {
                        buf = tmp_ptr;
                        memset(buf+tot_read, 0, available_buf_size - tot_read);
                    }
                    else {
                        fprintf(stderr,"realloc failed\n");
                        exit(EXIT_FAILURE);
                    }
                }
            }
        }
    } else {
        fprintf(stderr,"calloc failed\n");
        exit(EXIT_FAILURE);
    }
    return buf;
}

一次读取大小为 1000 后的缓冲区:

0x563a819da130 "HTTP/1.1 200 OK\r\nDate: Tue, 23 Nov 2021 19:32:01 GMT\r\nServer: Apache\r\nUpgrade: h2,h2c\r\nConnection: Upgrade, close\r\nLast-Modified: Sat, 11 Jan 2014 01:32:55 GMT\r\nAccept-Ranges: bytes\r\nContent-Length: 3900\r\nCache-Control: max-age=2592000\r\nExpires: Thu, 23 Dec 2021 19:32:01 GMT\r\nContent-Type: image/jpeg\r\n\r\nGIF89", <incomplete sequence 5>

共379个字符。

编辑: 读取数据后,我正在将其写入一个新文件,文本页面可以正常工作,但我无法打开图像。

我相信 read_unknown_size 正在工作,但调用者只是使用 printf("%s", buf) 或类似的方法打印缓冲区直到第一个 NUL 字符。[1] 这是错误的,原因有二:

  • 如果读取的数据中包含NUL,则会很快停止输出。
  • 如果读取的数据不包含 NUL,它将读取超出缓冲区的末尾。

调用方需要准确输出缓冲区中的字符数。但是,调用者无法确定缓冲区中有多少个字符。因此,为了对函数的结果做任何有用的事情,函数需要 return 不仅仅是缓冲区,还有它读取的字符数。

// Reads until EOF is encountered.
// Returns 0 on success.
// Returns -1 and sets errno on error.
int read_rest(int fd, unsigned char **buf_ptr, size_t *total_read_ptr) {
   unsigned char *buf        = NULL;
   size_t         buf_size   = 0;
   size_t         total_read = 0;

   while (1) {
      if ( total_read == buf_size ) {
         buf_size *= 2;  // Refine this.
         unsigned char *tmp = realloc(buf, buf_size);
         if (!tmp)
            goto ERROR;

         buf = tmp;
      }

      ssize_t chunk_size = read(fd, buf + total_read, buf_size - total_read);
      if ( chunk_size < 0 )
         goto ERROR;

      if ( chunk_size == 0 ) {
         unsigned char *tmp = realloc(buf, total_read);
         if (tmp)
            buf = tmp;

         *buf_ptr        = buf;
         *total_read_ptr = total_read;
         return 0;
      }

      total_read += chunk_size;
   }

ERROR:
   free(buf);
   *buf_ptr        = NULL;
   *total_read_ptr = 0;
   return -1;
}

调用者示例:

unsigned char *buf;
size_t         size;

if ( read_rest(in_fd, &buf, &size) == -1 ) {
   perror("Can't read from socket");
   exit(EXIT_FAILURE);
}

现在您有足够的信息来打印缓冲区的内容(例如使用 write)。

// Returns 0 on success.
// Returns -1 and sets errno on error.
int write_full(int fd, const unsigned char *buf, size_t count) {
   while ( count > 0 ) {
      ssize_t chunk_size = write(fd, buf, count);
      if ( chunk_size < 0 )
         return -1;

      buf   += chunk_size;
      count -= chunk_size;
   }

   return 0;
}

调用者示例:

if ( write_full(out_fd, buf, size) == -1 ) {
   perror("Can't write to file");
   exit(EXIT_FAILURE);
}

原代码评论:

  • 使用强制转换前请三思。使用 (int)read(...) 没有意义。这是不正确的。
  • 最好在发生错误时包括实际错误(如 perror 所做的那样)。
  • 最好在 I/O 函数之外打印错误消息。

  1. 请记住,NUL 在 GIF 文件中很常见,您最早可以在第 7 个字符(紧接 GIF89a 之后)使用 NUL。