本書專注於簡單容易了解的程式碼,以及最接近C語言的語法,並且大量應用多執行緒的思路,開發出來的非阻塞式(Non-blocking)網路程式庫,比同級產品快了20%,程式碼卻少了50%,充份展現C++的小而美且快。除了C++之外,本書亦充份介紹目前Scale out網路架構的概念,讓你不但可快速學會C++的網路開發,更可了解目前最新的雲端趨勢。
想學好C++就從全球第一個華人開發的頂級C++網路程式庫Muduo開始。本書主要說明採用現代C++ 在x86-64 Linux 上撰寫多執行緒TCP 網路服務程式的主流標準技術,重點說明多執行緒網路服務器的一種IO 模型,即one loopper thread。這是一種適應性強的模型,也是Linux 下以native 語言撰寫使用者態高性能網路程式最成熟的模式,熟練之後可順利地開發各種常見的服務端網路應用程式。本書以muduo 網路函數庫為例,說明這種程式設計模型的使用方法及注意事項。
C++學習時間長,一旦上手,開發出程式碼的超高速度是Java、C#等其它語言無法比較的。
作者簡介:
陳碩,北京師範大學碩士,擅長C++多執行緒網路程式設計和即時分散式系統架構。曾在摩根史丹利IT部門工作5年,從事即時外匯交易系統開發。現在在美國加州矽谷某互聯網大公司工作,從事大規模分散式系統的可靠性工程。
作者序
前言
本書主要說明採用現代C++ 在x86-64 Linux 上撰寫多執行緒TCP 網路服務程式的主流標準技術,這也是我對過去5 年撰寫生產環境下的多執行緒服務端程式的經驗歸納。本書重點說明多執行緒網路服務器的一種IO 模型,即one loopper thread。這是一種適應性強的模型,也是Linux 下以native 語言撰寫使用者態高性能網路程式最成熟的模式,熟練之後可順利地開發各種常見的服務端網路應用程式。本書以muduo 網路函數庫為例,說明這種程式設計模型的使用方法及注意事項。
muduo 是一個以非阻塞IO 和事件驅動為基礎的現代C++ 網路函數庫,原生支援one loop per thread 這種IO 模型。muduo 適合開發Linux 下的針對業務的多執行緒服務端網路應用程式,其中「針對業務的網路程式設計」的定義見附錄A。「現代C++」指的不是C++11 新標準,而是2005 年TR1 發佈之後的C++ 語言和函數庫。與傳統C++ 相比,現代C++ 的變化主要有兩方面:資源管理(見第 1 章)與事件回呼(見第11-24 頁)。
本書不是多執行緒程式設計教學,也不是網路程式設計教學,更不是C++ 教學。讀者應該已經大致讀過《現代作業系統》、《UNIX 環境進階程式設計》、《UNIX 網路程式設計》、《C++ Primer》或與之內容相近的書籍,熟悉基本概念,並熟練 Pthreads 和 Sockets API 的正常用法。本書基本不談C++11,因為目前主流的Linux 服務端發行版本的g++ 版本都還停留在4.4,C++11 進入實用尚需一段時日。
本書適用的硬體環境是主流 x86-64 伺服器,多路多核心CPU、幾十GB 記憶體、GB 乙太網互連。除了第 5 章講診斷記錄檔之外,本書不涉及檔案IO。
本書分為四大部分,第1 部分「C++ 多執行緒系統程式設計」檢查多執行緒下的物件生命期管理、執行緒同步方法、多執行緒與C++ 的結合、高效的多執行緒記錄檔等。第2 部分「muduo 網路函數庫」介紹使用現成的非阻塞網路函數庫撰寫網路應用程式的方法,以及 muduo 的設計與實現。第3 部分「專案實作經驗談」介紹分散式系統的專案化開發方法和C++ 在專案實作中的功能特性取捨。第4 部分「附錄」分享網路程式設計和C++ 語言的學習經驗。
本書的宗旨是貴精不貴多。熟練兩種基本的同步基本操作就可以滿足各種多執行緒同步的功能需求,還能寫出更好用的同步設施。熟練一種處理程序間通訊方式和一種多執行緒網路程式設計模型就足以應對日常開發工作,撰寫執行於公司內網環境的分散式服務系統。(本書不涉及分散式儲存系統,也不涉及UDP。)
術語與排版範例
本書大量使用英文術語,甚至有少量英文引用。設計模式的名字一律用英文, 例如Observer、Reactor、Singleton。在中文術語不夠突出時, 也會使用英文, 例如class、heap、event loop、STL algorithm 等。注意幾個中文C++ 術語:物件實體(instance)、函數多載決議(resolution)、樣板實例化(instantiation)、覆載(override)虛擬函數、反參考(dereference)指標。本書中的英文可數名詞一般不用複數形式,例如兩個 class,6 個 syscall;但有時會用(s) 強調中文名詞是複數。fd 是檔案描述符號(file descriptor)的縮寫。「CPU 數目」一般指的是核心(core)的數目。容量單位kB、MB、GB 表示的位元組數分別為103、106、109,在特別強調準確數值時,會分別用 KiB、MiB、GiB 表示210、220、230 位元組。用諸如 §11.5 表示本書第 11.5 節,第42 行 表示上下文中出現的第 42 行程式。[JCP]、[CC2e] 等是參考文獻,見書末清單。
程式
本書的範例程式以開放原始碼專案的形式發佈在GitHub 上,網址是 http://github.com/ chenshuo/recipes/ 和 http://github.com/chenshuo/muduo/。本書搭配頁面提供全部原始程式碼包裝下載,正文中出現的類似 recipes/thread 的路徑是壓縮檔內的相對路徑,讀者不難找到其對應的GitHub URL。本書參考程式的形式如下,左側數字是檔案的行號,右側的「muduo/base/Types.h」是檔案路
徑1。例如下面這幾行程式是 muduo::string 的 typedef。
15 namespace muduo
16 {
17
18 #ifdef MUDUO_STD_STRING
19 using std::string;
20 #else // !MUDUO_STD_STRING
21 typedef __gnu_cxx::__sso_string string;
22 #endif
本書假設讀者熟悉diff -u 指令的輸出格式,用於表示程式的改動。
本書正文中出現的程式有時為了照顧排版而略有改寫,例如改變縮排規則,去掉單行條件陳述式前後的大括號等。就程式設計風格而論,應以原始程式碼中的程式為準。
作者聯繫方式
電子郵件:giantchen@gmail.com
主頁:http://chenshuo.com/book (包含勘誤表和URL 清單)
微博:http://weibo.com/giantchen
部落格:http://blog.csdn.net/Solstice
程式:http://github.com/chenshuo
豆瓣:http://book.douban.com/subject/20471211
前言
本書主要說明採用現代C++ 在x86-64 Linux 上撰寫多執行緒TCP 網路服務程式的主流標準技術,這也是我對過去5 年撰寫生產環境下的多執行緒服務端程式的經驗歸納。本書重點說明多執行緒網路服務器的一種IO 模型,即one loopper thread。這是一種適應性強的模型,也是Linux 下以native 語言撰寫使用者態高性能網路程式最成熟的模式,熟練之後可順利地開發各種常見的服務端網路應用程式。本書以muduo 網路函數庫為例,說明這種程式設計模型的使用方法及注意事項。
muduo 是一個以非阻塞IO 和事件驅動為基礎的現代C++ 網路函數庫,原生支...
目錄
第1 部分 C++ 多執行緒系統程式設計
Chapter 01 符合執行緒安全的物件生命期管理
1.1 當解構函數遇到多執行緒
1.1.1 符合執行緒安全的定義
1.1.2 MutexLock 與 MutexLockGuard
1.1.3 一個符合執行緒安全的 Counter 範例
1.2 物件的建立很簡單
1.3 銷毀太難
1.3.1 mutex 不是辦法
1.3.2 作為資料成員的 mutex 不能保護解構
1.4 符合執行緒安全的 Observer 有多難
1.5 原始指標有何不妥
1.6 神器 shared_ptr/weak_ptr
1.7 插曲:系統地避免各種指標錯誤
1.8 應用到 Observer 上
1.9 再論 shared_ptr 的符合執行緒安全
1.10 shared_ptr 技術與陷阱
1.11 物件集區
1.11.1 enable_shared_from_this
1.11.2 弱回呼
1.12 替代方案
1.13 心得與小結
1.14 Observer 之謬
Chapter 02 執行緒同步精要
2.1 互斥器(mutex)
2.1.1 只使用非遞迴的 mutex
2.1.2 鎖死
2.2 條件變數(condition variable)
2.3 不要用讀寫鎖和號誌
2.4 封裝 MutexLock、MutexLockGuard、Condition
2.5 符合執行緒安全的 Singleton 實現
2.6 sleep(3) 不是同步基本操作
2.7 歸納與總結
2.8 借 shared_ptr 實現copy-on-write
Chapter 02 多執行緒伺服器的適用場合與常用程式設計模型
3.1 處理程序與執行緒
3.2 單執行緒伺服器的常用程式設計模型
3.3 多執行緒伺服器的常用程式設計模型
3.3.1 one loop per thread
3.3.2 執行緒池
3.3.3 推薦模式
3.4 處理程序間通訊只用TCP
3.5 多執行緒伺服器的適用場合
3.5.1 必須用單執行緒的場合
3.5.2 單執行緒程式的優缺點
3.5.3 適用多執行緒程式的場景
3.6 「多執行緒伺服器的適用場合」例釋與答疑
Chapter 04 C++ 多執行緒系統程式設計精要
4.1 基本執行緒基本操作的選用
4.2 C/C++系統函數庫的符合執行緒安全性
4.3 Linux 上的執行緒標識
4.4 執行緒的建立與銷毀的守則
4.4.1 pthread_cancel 與C++
4.4.2 exit(3) 在C++ 中不是符合執行緒安全的
4.5 善用__thread 關鍵字
4.6 多執行緒與IO
4.7 用RAII 包裝檔案描述符號
4.8 RAII 與fork()
4.9 多執行緒與fork()
4.10 多執行緒與signal
4.11 Linux 新增系統呼叫的啟示
Chapter 05 高效的多執行緒記錄檔
5.1 功能需求
5.2 效能需求
5.3 多執行緒非同步記錄檔
5.4 其他方案
第2 部分 muduo 網路函數庫
Chapter 06 muduo 網路函數庫簡介
6.1 由來
6.2 安裝
6.3 目錄結構
6.3.1 程式結構
6.3.2 實例
6.3.3 執行緒模型
6.4 使用教學
6.4.1 TCP 網路程式設計本質論
6.4.2 echo 服務的實現
6.4.3 七步實現finger 服務
6.5 效能評測
6.5.1 muduo 與 Boost.Asio、libevent2 的輸送量比較
6.5.2 比較 muduo 與 libevent2 的事件處理效率
6.5.3 muduo 與 Nginx 的輸送量比較
6.5.4 muduo 與 ZeroMQ 的延遲比較
6.6 詳解muduo 多執行緒模型
6.6.1 數獨求解伺服器
6.6.2 常見的平行處理網路服務程式設計方案
Chapter 07 muduo 程式設計範例
7.1 五個簡單TCP 範例
7.2 檔案傳輸
7.3 Boost.Asio 的聊天伺服器
7.3.1 TCP 分派封包
7.3.2 訊息格式
7.3.3 轉碼器 LengthHeaderCodec
7.3.4 服務端的實現
7.3.5 用戶端的實現
7.4 muduo Buffer 類別的設計與使用
7.4.1 muduo 的 IO 模型
7.4.2 為什麼 non-blocking 網路程式設計中應用層 buffer 是必需的
7.4.3 Buffer 的功能需求
7.4.4 Buffer 的資料結構
7.4.5 Buffer 的操作
7.4.6 其他設計方案
7.4.7 效能是不是問題
7.5 一種自動反射訊息類型的Protobuf 網路傳輸方案
7.5.1 網路程式設計中使用 Protobuf 的兩個先決條件
7.5.2 根據 type name 反射自動建立 Message 物件
7.5.3 Protobuf 傳輸格式
7.6 在muduo 中實現Protobuf 轉碼器與訊息分發器
7.6.1 什麼是轉碼器(codec)
7.6.2 實現 ProtobufCodec
7.6.3 訊息分發器(dispatcher)有什麼用
7.6.4 ProtobufCodec 與 ProtobufDispatcher 的綜合運用
7.6.5 ProtobufDispatcher 的兩種實現
7.6.6 ProtobufCodec 和 ProtobufDispatcher 有何意義
7.7 限制伺服器的最大平行處理連接數
7.7.1 為什麼要限制平行處理連接數
7.7.2 在 muduo 中限制平行處理連接數
7.8 計時器
7.8.1 程式中的時間
7.8.2 Linux 時間函數
7.8.3 muduo 的計時器介面
7.8.4 Boost.Asio Timer 範例
7.8.5 Java Netty 範例
7.9 測量兩台機器的網路延遲和時間差
7.10 用timing wheel 踢掉空閒連接
7.10.1 timing wheel 原理
7.10.2 程式實現與改進
7.11 簡單的訊息廣播服務
7.12 「串並轉換」連接伺服器及其自動化測試
7.13 socks4a 代理伺服器
7.13.1 TCP 中繼器
7.13.2 socks4a 代理伺服器
7.13.3 N:1 與 1:N 連接轉發
7.14 短網址服務
7.15 與其他函數庫整合
7.15.1 UDNS
7.15.2 c-ares DNS
7.15.3 curl
7.15.4 更多
Chapter 08 muduo 網路函數庫設計與實現
8.0 什麼都不做的EventLoop
8.1 Reactor 的關鍵結構
8.1.1 Channel class
8.1.2 Poller class
8.1.3 EventLoop 的改動
8.2 TimerQueue 計時器
8.2.1 TimerQueue class
8.2.2 EventLoop 的改動
8.3 EventLoop::runInLoop() 函數
8.3.1 加強 TimerQueue 的符合執行緒安全性
8.3.2 EventLoopThread class
8.4 實現TCP 網路函數庫
8.5 TcpServer 接受新連接
8.5.1 TcpServer class
8.5.2 TcpConnection class
8.6 TcpConnection 中斷連接
8.7 Buffer 讀取資料
8.7.1 TcpConnection 使用 Buffer 作為輸入緩衝
8.7.2 Buffer::readFd()
8.8 TcpConnection 發送資料
8.9 增強TcpConnection
8.9.1 SIGPIPE
8.9.2 TCP No Delay 和 TCP keepalive
8.9.3 WriteCompleteCallback 和 HighWaterMarkCallback
8.10 多執行緒TcpServer
8.11 Connector
8.12 TcpClient
8.13 epoll
8.14 測試程式一覽
第3 部分 專案實作經驗談
Chapter 09 分散式系統專案實作
9.1 我們在技術浪潮中的位置
9.1.1 分散式系統的本質困難
9.1.2 分散式系統是個險惡的問題
9.2 分散式系統的可用性淺說
9.2.1 分散式系統的軟體不要求 7×24 可靠
9.2.2 「能隨時重新啟動處理程序」作為程式設計目標
9.3 分散式系統中心跳協定的設計
9.4 分散式系統中的處理程序標識
9.4.1 錯誤做法
9.4.2 正確做法
9.4.3 TCP 協定的啟示
9.5 建構易於維護的分散式程式
9.6 為系統演化做準備
9.6.1 可擴充的訊息格式
9.6.2 反面教材:ICE 的訊息包裝格式
9.7 分散式程式的自動化回歸測試
9.7.1 單元測試的能與不能
9.7.2 分散式系統測試的要點
9.7.3 分散式系統的抽象觀點
9.7.4 一種自動化的回歸測試方案
9.7.5 其他用處
9.8 分散式系統部署、監控與處理程序管理的幾重境界
9.8.1 境界1:全手動操作
9.8.2 境界2:使用零散的自動化指令稿和協力廠商元件
9.8.3 境界3:自製機群管理系統,集中化設定
9.8.4 境界4:機群管理與 naming service 結合
Chapter 10 C++ 編譯連結模型精要
10.1 C 語言的編譯模型及其成因
10.1.1 為什麼C 語言需要前置處理
10.1.2 C 語言的編譯模型
10.2 C++的編譯模型
10.2.1 單遍編譯
10.2.2 正向宣告
10.3 C++連結(linking)
10.3.1 函數多載
10.3.2 inline 函數
10.3.3 樣板
10.3.4 虛擬函數
10.4 工程專案中標頭檔的使用規則
10.4.1 標頭檔的害處
10.4.2 標頭檔的使用規則
10.5 工程專案中函數庫檔案的組織原則
10.5.1 動態函數庫是有害的
10.5.2 靜態程式庫也好不到哪兒去
10.5.3 原始程式編譯是王道
Chapter 11 反思 C++ 物件導向與虛擬函數
11.1 樸實的C++設計
11.2 程式庫的二進位相容性
11.2.1 什麼是二進位相容性
11.2.2 有哪些情況會破壞函數庫的 ABI
11.2.3 哪些做法多半是安全的
11.2.4 反面教材:COM
11.2.5 解決辦法
11.3 避免使用虛擬函數作為函數庫的介面
11.3.1 C++ 程式庫的作者的生存環境
11.3.2 虛擬函數作為函數庫的介面的兩大用途
11.3.3 虛擬函數作為介面的弊端
11.3.4 假如 Linux 系統呼叫以 COM 介面方式實現
11.3.5 Java 是如何應對的
11.4 動態函數庫介面的推薦做法
11.5 以boost::function 和boost::bind 取代虛擬函數
11.5.1 基本用途
11.5.2 對程式庫的影響
11.5.3 對物件導向程式設計的影響
11.6 iostream 的用途與侷限
11.6.1 stdio 格式化輸入輸出的缺點
11.6.2 iostream 的設計初衷
11.6.3 iostream 與標準函數庫其他元件的互動
11.6.4 iostream 在使用方面的缺點
11.6.5 iostream 在設計方面的缺點
11.6.6 一個 300 行的 memory buffer output stream
11.6.7 現實的 C++ 程式如何做檔案 IO
11.7 值語義與資料抽象
11.7.1 什麼是值語義
11.7.2 值語義與生命期
11.7.3 值語義與標準函數庫
11.7.4 值語義與C++ 語言
11.7.5 什麼是資料抽象
11.7.6 資料抽象所需的語言設施
11.7.7 資料抽象的實例
Chapter 12 C++ 經驗談
12.1 用互斥來交換變數是錯誤的
12.1.1 編譯器會分別產生什麼程式
12.1.2 為什麼短的程式不一定快
12.2 不要多載全域::operator new()
12.2.1 記憶體管理的基本要求
12.2.2 多載 ::operator new() 的理由
12.2.3 ::operator new() 的兩種多載方式
12.2.4 現實的開發環境
12.2.5 多載 ::operator new() 的困境
12.2.6 解決辦法:取代 malloc()
12.2.7 為單獨的 class 多載 ::operator new() 有問題嗎
12.2.8 有必要自行訂製記憶體分配器嗎
12.3 有號整數的除法與餘數
12.3.1 語言標準怎麼說
12.3.2 C/C++ 編譯器的表現
12.3.3 其他語言的規定
12.3.4 指令碼語言解譯器程式0
12.3.5 硬體實現
12.4 在單元測試中mock 系統呼叫
12.4.1 系統函數的依賴植入
12.4.2 連結期墊片(link seam)
12.5 慎用匿名namespace
12.5.1 C 語言的 static 關鍵字的兩種用法
12.5.2 C++ 語言的 static 關鍵字的四種用法
12.5.3 匿名 namespace 的不利之處
12.5.4 替代辦法
12.6 採用有利於版本管理的程式格式
12.6.1 對 diff 人性化的程式格式
12.6.2 對 grep 人性化的程式風格
12.6.3 一切為了效率
12.7 再探std::string
12.7.1 直接拷貝(eager copy)
12.7.2 寫時複製(copy-on-write)
12.7.3 短字串最佳化(SSO)
12.8 用STL algorithm 輕鬆解決幾道演算法面試題
12.8.1 用 next_permutation() 產生排列與組合
12.8.2 用 unique() 去除連續重複空白
12.8.3 用 {make,push,pop}_heap() 實現多路歸併
12.8.4 用 partition() 實現「重排陣列,讓奇數位於偶數前面」
12.8.5 用 lower_bound() 尋找IP 位址所屬的城市
第4部分 附錄
Appendix A 談一談網路程式設計學習經驗
A.1 網路程式設計的一些「胡思亂想」
A.1.1 網路程式設計是什麼
A.1.2 學習網路程式設計有用嗎
A.1.3 在什麼平台上學習網路程式設計
A.1.4 可攜性重要嗎
A.1.5 網路程式設計的各種工作角色
A.1.6 針對業務的網路程式設計的特點
A.1.7 幾個術語
A.1.8 7×24 重要嗎,記憶體碎片可怕嗎
A.1.9 協定設計是網路程式設計的核心
A.1.10 網路程式設計的三個層次
A.1.11 最主要的三個實例
A.1.12 學習Sockets API 的利器:IPython
A.1.13 TCP 的可用性有多高
A.2 三本必看的書
Appendix B 從《C++ Primer(第4 版)》入手學習C++
B.1 為什麼要學習C++
B.2 學習C++只需要讀一本大部頭
B.3 繼續前進
B.4 評注版使用說明
Appendix C 關於Boost 的看法
Appendix D關於 TCP 平行處理連接的幾個思考題與試驗
第1 部分 C++ 多執行緒系統程式設計
Chapter 01 符合執行緒安全的物件生命期管理
1.1 當解構函數遇到多執行緒
1.1.1 符合執行緒安全的定義
1.1.2 MutexLock 與 MutexLockGuard
1.1.3 一個符合執行緒安全的 Counter 範例
1.2 物件的建立很簡單
1.3 銷毀太難
1.3.1 mutex 不是辦法
1.3.2 作為資料成員的 mutex 不能保護解構
1.4 符合執行緒安全的 Observer 有多難
1.5 原始指標有何不妥
1.6 神器 shared_ptr/weak_ptr
1.7 插曲:系統地避免各種指標錯誤
1.8 應用到 Observer 上
1.9 再論 shared_ptr 的符合執行緒安全
...