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

Posted

技术标签:

【中文标题】从套接字读取时出现意外行为【英文标题】:Unexpected behavior when reading from socket 【发布时间】:2022-01-02 07:08:48 【问题描述】:

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

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

功能:

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\n日期:2021 年 11 月 23 日星期二 19:32:01 GMT\r\n服务器:Apache\r\n升级:h2,h2c\r\n连接:升级,关闭\r \n上次修改时间:星期六,2014 年 1 月 11 日 01:32:55 GMT\r\n接受范围:字节\r\n内容长度:3900\r\n缓存控制:max-age=2592000\r\n到期:星期四, 2021 年 12 月 23 日 19:32:01 GMT\r\nContent-Type: image/jpeg\r\n\r\nGIF89",

共 379 个字符。

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

【问题讨论】:

请提供完整的代码minimal reproducible example。具体来说,您如何确定“一次阅读后的缓冲区”?如果您将其打印为字符串,那么它将不适用于二进制数据。 如何检查缓冲区?因为如果你只打印二进制文件并且它是一个小图像,那么就会有一个 NULL 停止打印。 @kaylum 我会努力的。至于你 Q - 我已经检查了调试器中的 buf 值并使用printf("\n Total response bytes: %d\n", (int) strlen((char *) response)); 打印了长度。错了吗? 是的,这是错误的。它将在数据中找到第一个 NUL,仅此而已。它不会显示二进制数据的数量。 请注意,您的 comment 关于使用 printf()strlen() 在处理二进制数据时完全是假的。您需要代码来读取数据中过去的空字节——strlen() 不会,%s 格式的 printf() 也不会。另外,当你得到EOF(由read()返回的零字节表示)时你会怎么做? 【参考方案1】:

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

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

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

// 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 函数之外打印错误消息。
    请记住,NUL 在 GIF 文件中很常见,您最早可以在第 7 个字符(GIF89a 之后)就有一个。

【讨论】:

感谢您的详细解答,我会注意您的笔记!

以上是关于从套接字读取时出现意外行为的主要内容,如果未能解决你的问题,请参考以下文章

从标准输入读取参数时出现意外行为

从套接字读取数据时出现问题

上传文件停止并在套接字异常上读取意外的 EOF

将数据从套接字从C ++服务器发送到Python客户端时出现问题

从android下载文件时出现套接字异常

查询 oracle 多维数据集时出现 java.sql.SqlRecoverableException