从套接字读取时出现意外行为
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] 这对两个人来说是错误的原因:
调用者需要准确输出缓冲区中的字符数。但是,调用者无法确定缓冲区中有多少字符。因此,为了对函数的结果做任何有用的事情,函数不仅需要返回缓冲区,还需要返回它读取的字符数。
// 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
之后)就有一个。
【讨论】:
感谢您的详细解答,我会注意您的笔记!以上是关于从套接字读取时出现意外行为的主要内容,如果未能解决你的问题,请参考以下文章