之前我们使用 read,write 及其他们的变体(recv, send) 函数读写 I/O,这些函数都是围绕着描述符工作的。我们将这一类 I/O 称为 Unix I/O.

我们也可以使用标准 I/O 函数库来读写 I/O,它由 ANSI C 标准进行规范,比如 fputs, fgets 等等。

当然了,fputs, fgets 这一类函数也可以用于套接字,不过这需要从描述符创建一个标准 I/O 流,主要使用函数 fdopen. 有一个与 fdopen 函数功能相反的函数是 fileno,它从标准 I/O 流创建出一个文件描述符。

FILE *fdopen(int fd, const char *mode);
int fileno(FILE *stream);

1. 使用标准 I/O 改写 TCP 回射服务器

void doServer(int sockfd) {
int ret;
char buf[4096];
FILE *fpin, *fpout;
// 根据描述符创建标准 I/O 流
fpin = fdopen(sockfd, "r");
fpout = fdopen(sockfd, "w");

while(fgets(buf, 4096, fpin) != NULL) {
// 转换成大写
toUpper(buf, strlen(buf));
ret = fputs(buf, fpout);
if (ret == EOF) {
puts("fputs error");
}
}
// 根据命令行参数控制
if

2. 程序路径

如果你已经 clone 过这个代码了,请使用 ​​git pull​​​ 更新一下。本节程序所使用的程序路径是 ​​unp/program/advcio/stdin/echo.cc​​.

3. 实验结果

下面两个实验中,在客户端中随便输入数据,然后按 CTRL D 退出。

  • 启动服务器,不开启缓冲区刷新
./echo -s


72-套接字与标准I/O_unix


图1 服务器未刷新缓冲,客户端运行情况


我们看到客户端并没有收到回射的数据。

  • 启动服务器,并开启缓冲区刷新
./echo -s


72-套接字与标准I/O_套接字_02


图2 服务器子进程退出时刷新了缓冲区,客户端收到回射数据


4. 实验结果分析

从图 1 和图 2 中我们看到,这两个结果并不是我们想要的。图 1 中根本就没有得到正确的结果,图2 虽然最后也得到了数据,但是也是在客户端准备退出的时候才收到。

出现这种情况的原因在于标准 I/O 是带有缓冲的。标准 I/O 函数库有三类缓冲:

  • 完全缓冲(fully buffering),这意味着只在出现下列情况时才发生 I/O: 缓冲区满,进程显式调用 fflush,进程调用 exit 终止自身(这一种和图 1 中的现象并不一致)。
  • 行缓冲(line buffering),这意味着出现下列情况时才发生 I/O: 碰到一个换行符,进程调用 fflush,或进程调用 exit 终止自身。
  • 不缓冲(unbuffering),意味着每次调用标准 I/O 输出函数都发生 I/O.

标准 I/O 函数库的大多数 Unix 实现使用如下规则:

  • 标准错误输出总是不缓冲。
  • 标准输入和标准输出完全维修部,除非它们指代终端设备(这种中下它们行缓冲)。
  • 所有其他 I/O 流都是完全缓冲,除非它们指代终端设备(这种情况下它们行缓冲)

既然套接字不是终端设备,因此它是完全缓冲的。

unp 建议:

避免在套接字上使用标准 I/O 函数库。

5. 总结

  • 知道如何在套接字上使用标准 I/O 函数库
  • 避免在套接字上使用标准 I/O 函数库