如今的服務器支撐上百萬個併發 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