图像包含二进制数据。256 个不同的 8 位模式中的任何一个都可能出现在图像中,特别是包括空字节、0x00 或'\0'
. b
在某些系统(尤其是 Windows)上,您需要使用标准 I/O 调用中的字母来区分文本文件和二进制文件fopen()
(在 Unix 和 Windows 上都可以正常工作)。鉴于二进制数据可以包含空字节,您不能使用strcpy()
et al 来复制数据块,因为str*()
函数在第一个空字节处停止复制。因此,您必须使用mem*()
具有起始位置和长度的函数或等效函数。
应用于您的代码,打印二进制文件httpData
将%s
无法正常工作;将%s
在第一个空字节处停止。由于您已经用于stat()
验证文件的存在,因此您也有文件的大小。假设您不必处理动态更改的文件,这意味着您可以分配httpData
正确的大小。您还可以将大小传递给阅读代码。这也意味着读取代码可以使用fread()
,写入代码可以使用fwrite()
,节省了逐个字符的 I/O。
因此,我们可能有一个函数:
int readHTTPData(const char *filename, size_t size, char *httpData)
{
FILE *fp = fopen(filename, "rb");
size_t n;
if (fp == 0)
return E_FILEOPEN;
n = fread(httpData, size, 1, fp);
fclose(fp);
if (n != 1)
return E_SHORTREAD;
fputs("httpData = ", stdout);
fwrite(httpData, size, 1, stdout);
putchar('\n');
return 0;
}
该函数在成功时返回 0,在失败时返回一些预定义的(负数?)错误号。由于内存分配是在调用例程之前完成的,因此非常简单:
- 打开文件;如果失败则报告错误。
- 在单个操作中读取文件。
- 关闭文件。
- 如果读取没有获得所有预期的数据,则报告错误。
- 报告读取的数据(仅调试 - 并将二进制数据打印到标准输出 raw 不是世界上最好的主意,但它与问题中的代码的作用相似)。
- 报告成功。
在原始代码中,有一个循环:
int i = 0;
...
while(!feof(file)) {
fscanf(file, "%c", &httpData[i]);
i++;
}
这个循环有很多问题:
- 您不应该使用
feof()
来测试是否有更多数据要读取。它报告是否已给出EOF 指示,而不是是否将给出。
- 因此,当最后一个字符被读取时,
feof()
报告“假”,但fscanf()
尝试读取下一个(不存在的)字符,将其添加到缓冲区(可能作为字母,例如 ÿ、y-变音符号、0xFF、 U+00FF,带分音符号的拉丁文小写字母 Y)。
- 该代码不检查读取了多少个字符,因此它没有防止缓冲区溢出的保护。
- 与.
fscanf()
_getc()
这是代码的更接近正确的版本,假设这size
是分配给httpData
.
int i = 0;
int c;
while ((c = getc(file)) != EOF && i < size)
httpData[i++] = c;
您可以检查是否在您期望的时候获得 EOF。请注意,fread()
代码在函数内部进行大小检查fread()
。此外,按照我编写参数的方式,这是一个全有或全无的命题——要么size
读取所有字节,要么将所有内容视为缺失。如果您想要字节计数并且愿意容忍或处理短读取,您可以反转大小参数的顺序。fwrite()
如果您想确保所有内容都已写入,您也可以检查 return from ,但人们往往不太注意检查输出是否成功。(不过,检查您是否得到了预期的输入几乎总是至关重要的——不要吝啬输入检查。)
在某些时候,对于纯文本数据,您需要考虑 CRLF 与 NL 行尾。文本文件会自动处理;二进制文件没有。如果要传输的数据是image/png
或类似的,您可能不需要担心这一点。如果你在 Unix 上处理text/plain
,你可能不得不担心 CRLF 行结尾(但我不是这方面的专家——我最近没有做过低级 HTTP 的东西(不是在这个千年),所以规则可能已经改变)。