如今的服务器支撑上百万个并发 TCP 链接已经不是新闻(余锋2010年的演讲,ideawu 的 iComet 开源项目,WhatsApp 作到了 2.5M)。实现 C1000k 的常规作法是调整内核参数,提升文件数,下降每一个链接的内存消耗(参考 ideawu 的博客)。html
在今年的 BSDCan2014 会议上, Patrick Kelsey 介绍了把 FreeBSD 9.x 的 TCP/IP 协议栈移植到了用户态(slides, github.com/pkelsey/libuinet),并用于 WANProxy 项目。在用户态运行 TCP/IP 协议栈意味着并发 TCP 链接再也不占用系统文件数,只占内存,解决了 C1000k 的一大瓶颈,内核只要提供一个收发网络 packet 的接口就行(例如 netmap)。git
内核的网络协议栈强调通用性,主要是为吞吐量优化(性能指标一般是 MB/s 或 packets per second),顺带兼顾大量并发链接。为了支持 C1000k,要调整内核参数让每一个链接少占资源,这与内核代码的设计初衷是违背的。github
用户态协议栈捅破了这层窗户纸,能够根据应用的特色来剪裁协议栈功能。优化也更直接,再也不是调黑盒参数组合,而是直接上 profiling,根据结果修改应用程序和协议栈的代码。编程
用户态协议栈的吞吐量比不上内核,不过对 C1000k 的应用场合(例如 comet)应该不成问题。vim
我用 muduo 作了一次 C1000k 的实验,用的是传统方案,没有用 libuinet。在一台 16GB 内存的 Dell WS490 旧工做站上建立了 50万个 TCP 链接,提供 echo 服务。系统可用内存减小了 5286MiB,即每一个链接 10.8KiB(其中服务进程占用了 1421MiB 内存,即每一个链接 2.9KiB,其他 8KiB 左右是内核协议栈的开销)。客户端是一台 8GB 内存的 i5-2500,内存消耗也是 5GB 多,所以此次实验只试到了 C500k。客户机绑定了 10 个 IP,每一个 IP 上发出 5 万 TCP 链接,运行 pingpong 协议,每一个链接轮流收发 64 字节的消息。测得 QPS 大约是 11k,服务器的 CPU 占用率约为 60%(单线程)。profile 显示 CPU 的主要开销在内核中,我对这个结果基本满意。数组
受 libuinet 启发,我把 4.4BSD-Lite2 的网络协议栈也移植到了 Linux 用户态(github.com/chenshuo/4.4BSD-Lite2),方便《TCP/IP 详解 第2卷》的读者跟踪调试其代码。如下是 Eclipse CDT 单步跟踪的截图。服务器
也能够用各类现成的工具来分析函数的调用关系:网络
我在《谈一谈网络编程学习经验》中说这本书的“代码只能看,不能上机运行,也不能改动试验”现在再也不成立了。并发
我在《关于 TCP 并发链接的几个思考题与试验》中用 TAP/TUN 做为本身写的协议栈的对外接口,对 4.4BSD-Lite2 也可如法炮制,让 20 年前的 TCP/IP 协议栈与如今的机器通讯。除了与本机通讯,还能够经过 NAT 转发,让 4.4BSD-Lite2 连上如今的 Internet。(sudo iptables -t nat -A PREROUTING -p tcp --dport 2009 -i eth0 -j DNAT --to 192.168.0.2:2009)eclipse