之前我们使用 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 (g_option.flush) fflush(fpout); }
2. 程序路径
代码托管在 gitos 上,请使用下面的命令获取:
git clone https://git.oschina.net/ivan_allen/unp.git
如果你已经 clone 过这个代码了,请使用 git pull
更新一下。本节程序所使用的程序路径是 unp/program/advcio/stdin/echo.cc
.
3. 实验结果
下面两个实验中,在客户端中随便输入数据,然后按 CTRL D 退出。
- 启动服务器,不开启缓冲区刷新
./echo -s -h mars
图1 服务器未刷新缓冲,客户端运行情况
我们看到客户端并没有收到回射的数据。
- 启动服务器,并开启缓冲区刷新
./echo -s -h mars --flush
图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 函数库