C++服務器開發精髓 ( 簡體 字) |
作者:張遠龍 | 類別:1. -> 程式設計 -> C++ -> C++ |
譯者: |
出版社:電子工業出版社 | 3dWoo書號: 54754 詢問書籍請說出此書號!【有庫存】 NT售價: 840 元 |
出版日:6/1/2021 |
頁數:752 |
光碟數:0 |
|
站長推薦: |
印刷:黑白印刷 | 語系: ( 簡體 版 ) |
|
加入購物車 │加到我的最愛 (請先登入會員) |
ISBN:9787121412639 |
作者序 | 譯者序 | 前言 | 內容簡介 | 目錄 | 序 |
(簡體書上所述之下載連結耗時費功, 恕不適用在台灣, 若讀者需要請自行嘗試, 恕不保證) |
作者序: |
譯者序: |
前言:為什么寫作本書
筆者自學生時代便開始接觸 C++,工作以后先后負責過 C++客戶端和服務端的開發工作。時至今日,C++仍然是筆者最喜歡的編程語言。在筆者看來,C++一旦學成,奇妙無窮,還可以快速學習其他編程語言和技術。
本書講解了筆者近十年來使用 C++的一些經驗和技巧,著重講解基于 C++的操作系統原理和服務器開發技術,希望讀者通過學習本書,可以了解如何學習 C++,以及如何成為一名合格的C++開發者。
C/C++的當前應用領域
需要注意的是,本書不細分C語言與C++的區別。在通常情況下,我們可以將C++看作C語言的一個超集。C++雖然從功能層面來看,離C語言越來越“遠”,但從語法層面來看,其大多數語法與C語言基本一致。對于C++面向對象的特性,如果仔細探究的話,我們會發現C++類方法的具體語法還是C語言的過程式語法,雖然這種現狀正在不斷改變。
C語言目前主要用于操作系統類偏底層的應用開發,比如Windows、Linux這樣的大型商業操作系統,以及嵌入式操作系統、嵌入式設備。有些開源軟件也會選擇C語言進行開發,主要是考慮程序執行效率和生成的可執行文件的體積(C代碼生成的可執行文件體積相對較小),當然,其中不乏一些歷史技術選型的原因,比如Redis、libevent、Nginx等。
在將高級語言翻譯成機器二進制碼時,C++編譯器生成了大量的額外機器碼,而這種機器碼相對于C語言來說不是必需的。例如,對于一個C++類的實例方法,編譯器在生成這個方法的機器碼時,會將函數的第1個參數設置為對象的this指針地址,以此來實現對象與函數的綁定。正因如此,許多開發者都會優化和調整編譯器生成的匯編代碼。
C++當前的常見應用領域有:?我們目前見到的各種桌面應用軟件,尤其是Windows桌面軟件,例如QQ、安全類殺毒類軟件、瀏覽器等;?一些基礎軟件和高級語言的運行時環境,例如大型數據庫軟件、Java虛擬機、C#的CLR、Python編譯器和運行時環境等;?業務型應用軟件的后臺,例如大型網絡游戲的服務端和一些企業內部的應用系統等。
C++與操作系統
雖然Java、Python等的SDK或運行時環境最終也會調用操作系統API,但其自帶的SDK或者運行時環境都提供了常見的操作系統功能。而C++的運行時環境一般是操作系統自身,因此C++是離操作系統更近的一種編程語言,執行效率更高。
但是,C++的整套語法不具備“功能完備性”,在大多數情況下,單純地使用其本身提供的功能無法創建出任何有意義的程序,還必須借助操作系統API來實現。例如,C++本身不直接提供網絡通信功能的SDK,必須借助操作系統提供的套接字API才能實現網絡通信;而對于Java來說,JDK自帶的java.net、java.io等包則提供了完整的網絡通信功能。所以,熟悉操作系統相關原理和API是用好C++的前提,這也是C++難學、對新手不友好的主要原因之一。
不過,隨著C++標準和版本的不斷迭代,這種現狀正在改變:在C++標準庫中引入了越來越多的功能,避免直接調用操作系統API。
不管怎樣,應用直接使用操作系統API,程序執行效率高,控制力度大,開發能力僅僅限制于操作系統本身,這是 C++的優勢之一。比如對于 Java,假設操作系統提供了某個功能,但Java虛擬機不提供該功能,則開發人員也無法使用該功能。
編程大師Charles Petzold曾說過,操作系統是一個非常復雜的系統,在API之上加一層編程語言并不能消除其復雜性,最多將復雜性隱藏起來而已,而懂得系統API能讓我們更快地掙脫困境。
如何看待C++ 11/14/17/20標準
C++既支持面向對象設計(OOP),也支持以模板語法為代表的泛型編程(GP)。從最初業界和開發者翹首以盼的C++ 11標準開始,歷經C++ 14、C++ 17,到今天的C++ 20,版本差別越來越大,原來需要使用的第三庫的功能也被陸續添加到C++標準庫中。C++標準不斷發展,遵循C++最新標準的編譯器層出不窮,C++變化越來越大、越來越快。
對于C++ 11、C++ 14、C++ 17乃至C++ 20的學習,筆者建議以實用為主,不必太糾結新標準中的一些高級特性和復雜模板,更應該學習其中實用的語法和工具庫。
如何學好C++和后端開發
首先,我們應該打好基礎。我們要熟練使用C++,還要結合具體的操作系統學習C++,熟悉某操作系統的API函數,以及與系統API關聯的各類技術,比如各種進程與線程函數、多線程資源同步函數、文件操作函數、系統時間函數、內存分配與管理函數、網絡編程、PE或ELF文件的編譯、鏈接原理等。
如果已打好基礎,就可以找一些高質量的開源項目去實戰。最好找一些沒有復雜業務的開源項目,或者是自己熟悉其業務的開源項目(如IM系統)。如果不熟悉其業務,那么不但要學習其業務(軟件功能),還要學習其源碼,最終兩者難以兼顧。
因此,在學習這些項目之前,應該先確定自己的學習目的。如果學習目的是學習和借鑒這款軟件的架構設計,那么建議先進行整體把握,不要一開始就迷失在細枝末節中,這叫作“粗讀”。如果學習目的是學習開源軟件在一些細節上的處理方法,那么可以有針對性地閱讀自己感興趣的模塊,深入每一行代碼。當然,學習適合自己當前階段的項目源碼才是最好的。
學習的過程一般是接觸、熟悉、模仿、創造。不管對什么開源項目,在沒有任何思路或者解決方案時,我們都應該先接觸、熟悉、不斷模仿,做到至少心中有一套對某場景的解決方案,再來談創新、批判及改造。
筆者在學習陌生的開源項目時,喜歡先將程序用調試器正常“跑”起來;然后中斷,統計當前的線程數,通過main函數從主線程追蹤其他工作線程是如何創建的;接著分析和研究各線程的用途和線程之間的交互,這樣可以做到整體性把握;最后找感興趣的細節去學習。
總之,C++是一門講究深度的編程語言,其“深度”不體現在掌握多少C++語法,而在于是否熟悉所寫的C++代碼背后的系統原理,這是需要長期積累的,當然,一旦學成,就可以快速學習其他編程語言和框架。
本書概要
本書總計9章,主要基于C++,詳細講解服務器開發中基礎且重要的技術棧,以期讀者掌握“造輪子”的方法。
第1章講解C++新標準中新增的常用語言特性和類庫。
第2章講解C++開發者應該掌握的各類開發工具和工作環境,詳細、深入地講解Linux gdb調試方面的內容。毫不夸張地說,掌握了gdb調試,就等于拿到了學習各種C++開源項目的“鑰匙”。
第3章詳細講解多線程的原理,涵蓋Windows和Linux的各類線程同步原語,以及基于線程同步技術、生產者/消費者模型衍生的隊列系統。
第4章進行操作系統層面的網絡編程重難點解析,講解Linux上的常用網絡通信模型,通過大量詳盡的代碼實例和測試,深入淺出地探究和驗證網絡通信編程的重難點技術。
第5章講解排查和定位網絡通信問題的常用開發工具。
第6章詳細講解網絡通信協議的設計思想,并從“造輪子”的角度講解常用網絡通信協議的格式、使用方法和注意事項,講解設計網絡通信協議時需要考慮的各類問題,最后對幾種常用的通信協議逐一剖析并給出具體的實現邏輯。
第7章詳細講解如何設計一個高性能的帶網絡通信組件的服務,并結合一些經典案例進行分析,還詳細講解經典服務框架的設計思路和各個模塊的具體實現方法。
第8章以redis-server源碼為例,論證第7章講解的服務設計原理。
第9章是對第7章內容的補充,詳細講解一個服務的常用模塊設計思路。
相關資源
本書提供源碼下載、讀者交流群等服務,詳情請參見本書封底的讀者服務信息。
若想獲取關于高性能服務器開發的更多知識,可以關注筆者的兩個微信公眾號:“高性能服務器開發”和“程序員小方”。
致謝
感謝筆者的妻子承擔家務及照顧筆者的生活,讓筆者可以集中精力寫作本書。
感謝各位同事幫助筆者成長與提高。
感謝王旭東等同學為本書的校對和勘誤做出貢獻,感謝“高性能服務器開發”群內小伙伴們的支持。
感謝電子工業出版社工作嚴謹、高效的張國霞編輯,她在成書過程中對筆者的指導、協助和鞭策,是本書得以完成的重要助力。 |
內容簡介:本書從操作系統原理角度講解進行C++服務器開發所需掌握的技術棧。全書總計9章,第1∼2章講解C++ 11/14/17新標準中的常用特性、新增類庫,以及C++開發者必須熟練掌握的開發調試工具鏈;第3∼6章詳細講解C++服務器開發中的多線程編程技術、網絡編程重難點知識、網絡故障調試與排查常用工具,以及通信協議的設計思路、技巧;第7∼8章詳細講解一個帶網絡通信組件的高性能服務的基本設計思路和注意事項;第9章進一步補充服務相關的常用模塊設計思路和方法。本書秉承的思想是,通過掌握技術原理,可以輕松制造“輪子”,靈活設計出優雅、魯棒的服務,并快速學習新技術。無論是對于C/C++開發者、計算機專業的學生,還是對于想了解操作系統原理的讀者,本書都極具參考價值。 |
目錄:第1章 C++必知必會 1
1.1 C++ RAII慣用法 1
1.1.1 版本1:最初的寫法 1
1.1.2 版本2:使用goto語句 3
1.1.3 版本3:使用do...while(0)循環 5
1.1.4 版本4:使用RAII慣用法 7
1.1.5 小結 12
1.2 pimpl慣用法 12
1.3 C++ 11/14/17新增的實用特性 17
1.4 統一的類成員初始化語法與std::initializer_list<T> 19
1.5 C++ 17注解標簽(attributes) 24
1.5.1 C++ 98/03的enumeration和C++ 11的enumerator 25
1.5.2 C++ 17的注解標簽 25
1.6 final、override關鍵字和=default、=delete語法 28
1.6.1 final關鍵字 28
1.6.2 override關鍵字 29
1.6.3 =default語法 31
1.6.4 =delete語法 32
1.7 auto關鍵字的用法 34
1.8 Range-based循環語法 35
1.8.1 自定義對象如何支持Range-based循環語法 37
1.8.2 for-each循環的實現原理 38
1.9 C++ 17結構化綁定 39
1.10 stl容器新增的實用方法 43
1.10.1 原位構造與容器的emplace系列函數 43
1.10.2 std::map的try_emplace方法與insert_or_assign方法 44
1.11 stl 中的智能指針類詳解 52
1.11.1 C++ 98/03的嘗試——std::auto_ptr 52
1.11.2 std::unique_ptr 55
1.11.3 std::shared_ptr 59
1.11.4 std::enable_shared_from_this 61
1.11.5 std::weak_ptr 63
1.11.6 智能指針對象的大小 67
1.11.7 使用智能指針時的注意事項 68
第2章 C++后端開發必備的工具和調試知識 71
2.1 SSH工具與FTP工具 71
2.1.1 Xshell 71
2.1.2 FTP 75
2.2 makefile與CMake 76
2.3 使用Visual Studio管理和閱讀開源項目代碼 83
2.4 gdb調試 87
2.4.1 被調試的程序需要帶調試信息 87
2.4.2 啟動gdb調試的方法 89
2.5 gdb常用命令詳解——利用gdb調試Redis 94
2.5.1 gdb常用調試命令概覽和說明 94
2.5.2 用gdb調試Redis前的準備工作 96
2.5.3 run命令 97
2.5.4 continue命令 98
2.5.5 break命令 98
2.5.6 tbreak命令 101
2.5.7 backtrace與frame命令 101
2.5.8 info break、enable、disable、delete命令 102
2.5.9 list命令 104
2.5.10 print與ptype命令 107
2.5.11 info與thread命令 109
2.5.12 next、step、until、finish、return、jump命令 112
2.5.13 disassemble命令 122
2.5.14 set args與show args命令 122
2.5.15 watch命令 123
2.5.16 display命令 124
2.5.17 dir命令 125
2.6 使用gdb調試多線程程序 126
2.6.1 調試多線程程序的方法 126
2.6.2 在調試時控制線程切換 128
2.7 使用gdb調試多進程程序——以調試Nginx為例 137
2.8 gdb實用調試技巧 143
2.8.1 將print輸出的字符串或字符數組完整顯示 144
2.8.2 讓被gdb調試的程序接收信號 144
2.8.3 函數明明存在,添加斷點時卻無效 145
2.8.4 調試中的斷點 146
2.8.5 自定義gdb調試命令 147
2.9 gdb tui——gdb圖形化界面 148
2.9.1 開啟gdb TUI模式 149
2.9.2 gdb TUI模式下的4個窗口 149
2.9.3 解決tui窗口不自動更新內容的問題 150
2.9.4 窗口焦點切換 150
2.10 gdb的升級版——cgdb 151
2.11 使用VisualGDB調試 154
2.11.1 使用VisualGDB調試已經運行的程序 155
2.11.2 使用VisualGDB從頭調試程序 156
第3章 多線程編程與資源同步 159
3.1 線程的基本概念及常見問題 159
3.1.1 主線程退出,支線程也將退出嗎 159
3.1.2 某個線程崩潰,會導致進程退出嗎 160
3.2 線程的基本操作 160
3.2.1 創建線程 160
3.2.2 獲取線程ID 166
3.2.3 等待線程結束 173
3.3 慣用法:將C++類對象實例指針作為線程函數的參數 178
3.4 整型變量的原子操作 184
3.4.1 為什么給整型變量賦值不是原子操作 185
3.4.2 Windows平臺上對整型變量的原子操作 186
3.4.3 C++ 11對整型變量原子操作的支持 187
3.5 Linux線程同步對象 190
3.5.1 Linux互斥體 190
3.5.2 Linux信號量 198
3.5.3 Linux條件變量 202
3.5.4 Linux讀寫鎖 208
3.6 Windows線程同步對象 217
3.6.1 WaitForSingleObject與WaitForMultipleObjects函數 217
3.6.2 Windows臨界區對象 219
3.6.3 Windows Event對象 224
3.6.4 Windows Mutex對象 229
3.6.5 Windows Semaphore對象 231
3.6.6 Windows讀寫鎖 235
3.6.7 Windows條件變量 238
3.6.8 在多進程之間共享線程同步對象 243
3.7 C++ 11/14/17線程同步對象 244
3.7.1 std::mutex系列 244
3.7.2 std::shared_mutex 248
3.7.3 std::condition_variable 253
3.8 如何確保創建的線程一定能運行 256
3.9 多線程使用鎖經驗總結 258
3.9.1 減少鎖的使用次數 258
3.9.2 明確鎖的范圍 259
3.9.3 減少鎖的使用粒度 259
3.9.4 避免死鎖的一些建議 260
3.9.5 避免活鎖的一些建議 262
3.10 線程局部存儲 262
3.10.1 Windows的線程局部存儲 262
3.10.2 Linux的線程局部存儲 264
3.10.3 C++ 11 的 thread_local 關鍵字 267
3.11 C庫的非線程安全函數 268
3.12 線程池與隊列系統的設計 270
3.12.1 線程池的設計原理 270
3.12.2 環形隊列 275
3.12.3 消息中間件 275
3.13 纖程(Fiber)與協程(Routine) 277
3.13.1 纖程 277
3.13.2 協程 280
第4章 網絡編程重難點解析 282
4.1 學習網絡編程時應該掌握的socket函數 282
4.1.1 在Linux上查看socket函數的幫助信息 283
4.1.2 在Windows上查看socket函數的幫助信息 285
4.2 TCP網絡通信的基本流程 286
4.3 設計跨平臺網絡通信庫時的一些socket函數用法 290
4.3.1 socket數據類型 290
4.3.2 在Windows上調用socket函數 290
4.3.3 關閉socket函數 291
4.3.4 獲取socket函數的錯誤碼 291
4.3.5 套接字函數的返回值 293
4.3.6 select函數第1個參數的問題 293
4.3.7 錯誤碼WSAEWOULDBLOCK和EWOULDBLOCK 294
4.4 bind函數重難點分析 294
4.4.1 對bind函數如何選擇綁定地址 294
4.4.2 bind函數的端口號問題 295
4.5 select函數的用法和原理 302
4.5.1 Linux上的select函數 302
4.5.2 Windows上的select函數 317
4.6 socket的阻塞模式和非阻塞模式 318
4.6.1 如何將socket設置為非阻塞模式 318
4.6.2 send和recv函數在阻塞和非阻塞模式下的表現 320
4.6.3 非阻塞模式下send和recv函數的返回值總結 331
4.6.4 阻塞與非阻塞socket的各自適用場景 333
4.7 發送0字節數據的效果 333
4.8 connect函數在阻塞和非阻塞模式下的行為 339
4.9 連接時順便接收第1組數據 343
4.10 如何獲取當前socket對應的接收緩沖區中的可讀數據量 346
4.10.1 分析 346
4.10.2 注意事項 350
4.11 Linux EINTR錯誤碼 351
4.12 Linux SIGPIPE信號 352
4.13 Linux poll 函數的用法 353
4.14 Linux epoll模型 361
4.14.1 基本用法 361
4.14.2 epoll_wait與poll函數的區別 363
4.14.3 LT 模式和ET 模式 363
4.14.4 EPOLLONESHOT 選項 380
4.15 高效的readv和writev函數 386
4.16 主機字節序和網絡字節序 387
4.16.1 主機字節序 387
4.16.2 網絡字節序 388
4.16.3 操作系統提供的字節轉換函數匯總 389
4.17 域名解析API介紹 390
第5章 網絡通信故障排查常用命令 397
5.1 ifconfig命令 397
5.2 ping命令 401
5.3 telnet命令 402
5.4 netstat命令 407
5.5 lsof命令 409
5.6 nc命令 412
5.7 curl命令 415
5.8 tcpdump命令 416
第6章 網絡通信協議設計 422
6.1 理解TCP 422
6.2 如何解決粘包問題 423
6.3 解包與處理 425
6.4 從struct到TLV 430
6.4.1 協議的演化 430
6.4.2 協議的分類 434
6.4.3 協議設計工具 434
6.5 整型數值的壓縮 435
6.6 設計通信協議時的注意事項 437
6.6.1 字節對齊 437
6.6.2 顯式地指定整型字段的長度 438
6.6.3 涉及浮點數時要考慮精度問題 438
6.6.4 大小端問題 438
6.6.5 協議與自動升級功能 438
6.7 包分片 439
6.8 XML與JSON格式的協議 444
6.9 一個自定義協議示例 445
6.10 理解HTTP 460
6.10.1 HTTP格式介紹 460
6.10.2 GET與POST方法 461
6.10.3 HTTP chunk編碼 465
6.10.4 HTTP客戶端的編碼實現 466
6.10.5 HTTP服務端的實現 466
6.10.6 HTTP與長連接 471
6.10.7 libcurl 471
6.10.8 Restful接口與Java Spring MVC 477
6.11 SMTP、POP3與郵件客戶端 478
6.11.1 郵件協議簡介 478
6.11.2 SMTP 479
6.11.3 POP3 494
6.11.4 郵件客戶端 499
6.12 WebSocket協議 499
6.12.1 WebSocket協議的握手過程 500
6.12.2 WebSocket協議的格式 503
6.12.3 WebSocket協議的壓縮格式 506
6.12.4 WebSocket協議裝包與解包示例 508
6.12.5 解析握手協議 512
第7章 單個服務的基本結構 515
7.1 網絡通信組件的效率問題 515
7.1.1 高效網絡通信框架的設計原則 515
7.1.2 連接的被動關閉與主動關閉 519
7.1.3 長連接和短連接 519
7.2 原始的服務器結構 520
7.3 一個連接對應一個線程模型 522
7.4 Reactor模式 523
7.5 one thread one loop思想 524
7.5.1 one thread one loop程序的基本結構 524
7.5.2 線程的分工 525
7.5.3 喚醒機制的實現 527
7.5.4 handle_other_things方法的實現邏輯 532
7.5.5 帶定時器的程序結構 533
7.5.6 one thread one loop的效率保障 534
7.6 收發數據的正確做法 534
7.6.1 如何收取數據 534
7.6.2 如何發送數據 535
7.6.3 不要多個線程同時利用一個socket收(發)數據 538
7.7 發送、接收緩沖區的設計要點 538
7.7.1 為什么需要發送緩沖區和接收緩沖區 539
7.7.2 如何設計發送緩沖區和接收緩沖區 539
7.7.3 服務端發送數據時對端一直不接收的問題 543
7.8 網絡庫的分層設計 544
7.8.1 網絡庫設計中的各個層 544
7.8.2 將Session進一步分層 550
7.8.3 連接信息與EventLoop/Thread的對應關系 551
7.9 后端服務中的定時器設計 551
7.9.1 最簡單的定時器 551
7.9.2 定時器設計的基本思路 552
7.9.3 定時器邏輯的性能優化 561
7.9.4 對時間的緩存 564
7.10 處理業務數據時是否一定要單獨開線程 565
7.11 非侵入式結構與侵入式結構 570
7.11.1 非侵入式結構 570
7.11.2 侵入式結構 571
7.12 帶有網絡通信模塊的服務器的經典結構 578
7.12.1 為何要將listenfd設置成非阻塞模式 578
7.12.2 基于one thread one loop結構的經典服務器結構 584
7.12.3 服務器的性能瓶頸 586
第8章 Redis網絡通信模塊源碼分析 587
8.1 調試Redis環境與準備 587
8.1.1 Redis源碼編譯與啟動 587
8.1.2 通信示例與術語約定 589
8.2 探究redis-server端的網絡通信模塊 589
8.2.1 監聽fd的初始化工作 589
8.2.2 接受客戶端連接 592
8.2.3 epollfd的創建 600
8.2.4 監聽fd與客戶端fd是如何掛載到epollfd上的 601
8.2.5 readQueryFromClient函數 611
8.2.6 如何處理可寫事件 613
8.2.7 Redis 6.0多線程網絡I/O 620
8.2.8 Redis對客戶端的管理 635
8.2.9 客戶端斷開流程 646
8.2.10 Redis中收發緩沖區的設計 653
8.2.11 定時器邏輯 659
8.2.12 鉤子函數 662
8.2.13 redis-server端網絡通信模塊小結 662
8.3 探究redis-cli端的網絡通信模型 663
8.4 Redis的通信協議格式 673
8.4.1 請求命令格式 673
8.4.2 應答命令格式 674
8.4.3 多命令和流水線 677
8.4.4 特殊的redis-cli與內聯命令 677
8.4.5 Redis對協議數據的解析邏輯 678
第9章 服務器開發中的常用模塊設計 681
9.1 斷線自動重連的應用場景和邏輯設計 681
9.2 保活機制與心跳包 683
9.2.1 TCP keepalive選項 683
9.2.2 應用層的心跳包機制設計 684
9.2.3 有代理的心跳包機制設計 689
9.2.4 帶業務數據的心跳包 690
9.2.5 心跳包與流量 690
9.2.6 心跳包與調試 691
9.2.7 心跳包與日志 691
9.3 日志模塊的設計 692
9.3.1 為什么需要日志 692
9.3.2 日志系統的技術實現 692
9.3.3 在C/C++中輸出網絡數據包日志 716
9.3.4 調試時的日志 719
9.3.5 統計程序性能日志 719
9.3.6 根據類型將日志寫入不同的文件中 725
9.3.7 集中式日志服務與分布式日志服務 725
9.3.8 從業務層面看在一條日志中應該包含什么內容 727
9.3.9 在日志中不要出現敏感信息 729
9.3.10 開發過程中的日志遞進縮減策略 730
9.4 錯誤碼系統的設計 730
9.4.1 錯誤碼的作用 730
9.4.2 錯誤碼系統設計實踐 731
9.5 監控端口 733 |
序: |