我将会写一系列文章,介绍用 muduo 网络库完成常见的 TCP 网络编程任务。目前计划如下:
UNP 中的简单协议,包括 echo、daytime、time、discard 等。
Boost.Asio 中的示例, 包括 timer2~6、chat 等。
Java Netty 中的示例,包括 discard、echo、uptime 等,其中的 discard 和 echo 带流量统计功能。
Python twisted 中的示例,包括 finger01~07
用于测试两台机器的往返延迟的 roundtrip
用于测试两台机器的带宽的 pingpong
云风的串并转换连接服务器 multiplexer,包括单线程和多线程两个版本。
文件传输
一个基于 TCP 的应用层广播 hub
socks4a 代理服务器,包括简单的 TCP 中继(relay)。
一个 Sudoku 服务器的演变,从单线程到多线程,从阻塞到 event-based。
一个提供短址服务的 httpd 服务器
其中前面 7 个已经放到了 muduo 代码的 examples 目录中,下载地址是: http://muduo.googlecode.com/files/muduo-0.1.5-alpha.tar.gz
这些例子都比较简单,逻辑不复杂,代码也很短,适合摘取关键部分放到博客上。其中一些有 一定的代表性与针对性,比如“如何传输完整的文件”估计是网络编程的初学者经常遇到的 问题。请注意,muduo 是设计来开发内网的网络程序,它没有做任何安全方面的加强措施,如果用在公 网上可能会受到攻击,在后面的例子中我会谈到这一点。
本系列文章适用于 Linux 2.6.x (x > 28),主要测试发行版为 Ubuntu 10.04 LTS 和 Debian 6.0 Squeeze,64-bit x86 硬件。
TCP 网络编程本质论
我认为,TCP 网络编程最本质的是处理三个半事件:
连接的建 立,包括服务端接受 (accept) 新连接和客户端成功发起 (connect) 连接。
连接的断开,包 括主动断开 (close 或 shutdown) 和被动断开 (read 返回 0)。
消息到达,文件描述符可读 。这是最为重要的一个事件,对它的处理方式决定了网络编程的风格(阻塞还是非阻塞,如何处理分包 ,应用层的缓冲如何设计等等)。
消息发送完毕,这算半个。对于低流量的服务,可以不必关 心这个事件;另外,这里“发送完毕”是指将数据写入操作系统的缓冲区,将由 TCP 协议 栈负责数据的发送与重传,不代表对方已经收到数据。
这其中有很多难点,也有很多细节需要注意,比方说:
如果要主动关闭连接,如何保证对方 已经收到全部数据?如果应用层有缓冲(这在非阻塞网络编程中是必须的,见下文),那么如何保证先 发送完缓冲区中的数据,然后再断开连接。直接调用 close(2) 恐怕是不行的。
如果主动发起 连接,但是对方主动拒绝,如何定期 (带 back-off) 重试?
非阻塞网络编程该用边沿触发 (edge trigger)还是电平触发(level trigger)?(这两个中文术语有其他译法,我选择了一个电子工 程师熟悉的说法。)如果是电平触发,那么什么时候关注 EPOLLOUT 事件?会不会造成 busy-loop?如 果是边沿触发,如何防止漏读造成的饥饿?epoll 一定比 poll 快吗?