程序清单 15-8 对两个数求和的简单过滤程序

$ cc err.c figure15-8.c -o add2

当一个程序产生某个过滤程序的输入，同时又读取该过滤程序的输出时，则
该过滤程序就成为协同进程（coprocess）。

比如：

                         协同进程（coprocess）
                               v
                               v
                               v
|--------------| stdin  |--------------|
| awk          |------> |         less |
|              |        |              |
|              |        |              |
|              | stdout |              |
|              |<------ |              |
|--------------|        |--------------|

此程序是一个简单的协同进程，它从从其标准输入读两个数，计算它们的和，
然后将结果写至标准输出。（协同进程通常会做较此更有意义的工作。设计
本例的目的是帮助了解将进程连接起来所需的各种管道设施。）
程序清单 15-9 驱动 add2 过滤程序的程序

$ cc err.c figure15-8.c -o add2
$ cc err.c figure15-9.c

在此程序中创建了两个管道，父、子进程各自关闭它们不需要使用的端口。两个
管道一个用做协同进程的标准输入，另一个则用做它的标准输出。子进程调用
dup2() 使管道描述符移至其标准输入和标准输出，然后调用 execl()。

进而言之，在程序等待输入时，若先杀死 add2 协同进程，然后输入两个数，接
着对管道进行写操作，此时，由于管道已无读进程，于是调用信号处理程序。
程序清单 15-10 对两个数求和的过滤程序，使用标准 I/O

$ cc err.c figure15-10.c -o add2
$ cc err.c figure15-9.c
$ ./a.out

若 figure15-9.c 调用此新的协同进程，则它不再工作。问题出在系统默认的标
准 I/O 缓冲机制上。当调用 figure15-10.c 所示的程序时(也就是本程序)，对
标准输入的第一个 fgets() 引起标准 I/O 库分配一个缓冲区，并选择缓冲区的
类型。因为标准输入是个管道，所以标准 I/O 库由系统默认是全缓冲的。对标准
输出也作同样的处理。

当 add2 从其标准输入读取而发生阻塞时，figure15-9.c 程序从管道读时也发生
阻塞，于是产生了死锁。

为此，更改将要运行的协同进程的缓冲类型，在 figure15-10.c 的 while 循环
之前加上下面 4 行：

if (setvbuf(stdin, NULL, _IOLBF, 0) != 0)
  err_sys("setvbuf error");
if (setvbuf(stdout, NULL, _IOLBF, 0) != 0)
  err_sys("setvbuf error");

这些代码使得当有一行可用时，fgets() 就返回，并使得当输出一换行符
时，printf() 立刻执行 fflush() 操作。对 setvbuf() 进行的这些显式调用使
得 figure15-10.c 能正常工作。

对这种问题的一般解决方法是使被调用的协同进程认为它的标准输入和输出都被
连接到一个终端。这使得协同进程中的标准 I/O 例程对这两个 I/O 流进行行缓
冲，这类似于前面所做的显式 setvbuf() 调用。也可以用伪终端实现这一点。
程序清单 15-11 打印各种不同类型的数据所存放的位置

$ cc err.c figure15-11.c

内核将以地址 0 连接的共享存储段放在什么位置上与系统密切相关。此程序打印
一些信息，它们与特定系统将各种不同的数据放在什么位置有关。
程序清单 15-12 在父、子进程间使用 /dev/zero 存储映射 I/O 的 IPC

$ cc err.c figure15-3.c figure15-12.c

在读设备 /dev/zero 时，该设备是 0 字节的无限资源。它也接收写向它的
任何数据，但又忽略这些数据。我们对此设备作为 IPC 的兴趣在于，当对其
进行存储映射时，它具有一些特殊的性质：

- 创建一个未名存储区，其长度是 mmap() 的第二个参数，将其向上取整为系统
  的最近页长

- 存储区都初始化为 0

- 如果多个进程的共同祖先进程对 mmap() 指定了 MAP_SHARED 标志，则这些进
  程可共享此存储区

使用 /dev/zero 的优点：在调用 mmap() 创建映射区之前，无需存在一个实际文
件。映射 /dev/zero 自动创建一个指定长度的映射区。

这种技术的缺点：它只在相关进程间起作用。

在相关进程之间使用线程可能更为简单、有效。注意，无论使用哪一种技术，都
需对共享数据进行同步访问。
程序清单 16-1 打印主机和服务信息

$ cc err.c figure16-1.c
$ ./a.out google.com 0
程序清单 16-2 支持重试的连接
程序清单 16-3 服务器初始化套接字端点
figure16-1.c: 使输出更加容易阅读
程序清单 16-4 用于获取服务器 uptime 的客户端命令

这个程序连接服务器，读取服务器发送过来的字符串并将其打印到标准输出。

编译方法：
    $ cc err.c figure16-2.c figure16-4.c -o ruptime

使用方法：
    $ ./ruptime server-name
程序清单 16-5 提供系统 uptime 的服务器程序

编译方法：
    $ cc err.c figure13-1.c figure16-3.c figure16-5.c -o ruptimed

编译完成之后，还需要设置 /etc/services 这个文件，一定要确保端口号 8888 没有被使用
    $ grep 8888 /etc/services # 这个命令没有输出才能使用 8888 作为端口号
    $ sudo cp -v /etc/services{,.bak}
    $ sudo sh -c 'echo "ruptime 8888/tcp" >> /etc/services'

参考链接：
    http://blog.csdn.net/andyxie407/article/details/1672325
程序清单 16-6 用于显示命令直接写到套接字的服务器程序

编译方法：
    $ cc err.c figure13-1.c figure16-3.c figure16-6.c -o ruptimed

注意：应该修改 /etc/services 文件，参考 figure16-5.c
figure16-6.c: 在 main() 中去除 #ifdef 这个的预处理指令

使用 get_hostname_max() 这个界面统一获取需要的主机名最大的字符数。
figure16-6.c: 将编码风格改为跟书本的一样
程序清单 16-7 采用数据报服务的客户端命令

编译方法：
    $ cc err.c figure16-7.c -o ruptime2

使用方法：
    $ ./ruptime2 $(hostname)
程序清单 16-8 基于数据报提供系统 uptime 的服务器程序

编译方法：
    $ cc err.c figure13-1.c figure16-3.c figure16-8.c -o ruptimed

使用方法：
    $ ./ruptimed

注意：应该修改 /etc/services 文件，参考 figure16-5.c
figure16-8.c: 使用 get_hostname_max() 替代预编译指令 #ifndef
figure16-4.c: 编排代码，使之方便阅读
程序清单 16-9 采用地址复用初始化套接字端点
figure16-9.c: 减少冗余代码，使之变得简洁