从套接字读取时出现意外行为
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 函数之外打印错误消息。
- 请记住,NUL 在 GIF 文件中很常见,您最早可以在第 7 个字符(紧接
GIF89a
之后)使用 NUL。
我编写了以下函数,通过套接字从服务器读取 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 函数之外打印错误消息。
- 请记住,NUL 在 GIF 文件中很常见,您最早可以在第 7 个字符(紧接
GIF89a
之后)使用 NUL。