出版時(shí)間:2013-1-15 出版社:電子工業(yè)出版社 作者:陳碩 頁數(shù):616
Tag標(biāo)簽:無
前言
本書主要講述采用現(xiàn)代C++ 在x86-64 Linux上編寫多線程TCP網(wǎng)絡(luò)服務(wù)程序的主流常規(guī)技術(shù),這也是我對(duì)過去5年編寫生產(chǎn)環(huán)境下的多線程服務(wù)端程序的經(jīng)驗(yàn)總結(jié)。本書重點(diǎn)講解多線程網(wǎng)絡(luò)服務(wù)器的一種IO 模型,即one loop per thread。這是一種適應(yīng)性較強(qiáng)的模型,也是Linux 下以native語言編寫用戶態(tài)高性能網(wǎng)絡(luò)程序最成熟的模式,掌握之后可順利地開發(fā)各類常見的服務(wù)端網(wǎng)絡(luò)應(yīng)用程序。本書以muduo網(wǎng)絡(luò)庫為例,講解這種編程模型的使用方法及注意事項(xiàng)。muduo 是一個(gè)基于非阻塞IO 和事件驅(qū)動(dòng)的現(xiàn)代C++ 網(wǎng)絡(luò)庫,原生支持oneloop per thread 這種IO 模型。muduo 適合開發(fā)Linux 下的面向業(yè)務(wù)的多線程服務(wù)端網(wǎng)絡(luò)應(yīng)用程序,其中“面向業(yè)務(wù)的網(wǎng)絡(luò)編程”的定義見附錄A?!艾F(xiàn)代C++”指的不是C++11 新標(biāo)準(zhǔn),而是2005 年TR1 發(fā)布之后的C++ 語言和庫。與傳統(tǒng)C++ 相比,現(xiàn)代C++ 的變化主要有兩方面:資源管理(見第1 章)與事件回調(diào)(見第449 頁)。本書不是多線程編程教程,也不是網(wǎng)絡(luò)編程教程,更不是C++教程。讀者應(yīng)該已經(jīng)大致讀過《UNIX環(huán)境高級(jí)編程》、《UNIX網(wǎng)絡(luò)編程》、《C++Primer》或與之內(nèi)容相近的書籍。本書不談C++11,因?yàn)槟壳埃?012年)主流的Linux 服務(wù)端發(fā)行版的g++版本都還停留在4.4,C++11 進(jìn)入實(shí)用尚需一段時(shí)日。本書適用的硬件環(huán)境是主流x86-64 服務(wù)器,多路多核CPU、幾十GB 內(nèi)存、千兆以太網(wǎng)互聯(lián)。除了第5章講診斷日志之外,本書不涉及文件IO。本書分為四大部分,第1 部分“C++ 多線程系統(tǒng)編程”考察多線程下的對(duì)象生命期管理、線程同步方法、多線程與C++ 的結(jié)合、高效的多線程日志等。第2 部分“muduo 網(wǎng)絡(luò)庫”介紹使用現(xiàn)成的非阻塞網(wǎng)絡(luò)庫編寫網(wǎng)絡(luò)應(yīng)用程序的方法,以及muduo的設(shè)計(jì)與實(shí)現(xiàn)。第3部分“工程實(shí)踐經(jīng)驗(yàn)談”介紹分布式系統(tǒng)的工程化開發(fā)方法和C++在工程實(shí)踐中的功能特性取舍。第4部分“附錄”分享網(wǎng)絡(luò)編程和C++語言的學(xué)習(xí)經(jīng)驗(yàn)。本書的宗旨是貴精不貴多。掌握兩種基本的同步原語就可以滿足各種多線程同步的功能需求,還能寫出更易用的同步設(shè)施。掌握一種進(jìn)程間通信方式和一種多線程網(wǎng)絡(luò)編程模型就足以應(yīng)對(duì)日常開發(fā)任務(wù),編寫運(yùn)行于公司內(nèi)網(wǎng)環(huán)境的分布式服務(wù)系統(tǒng)。(本書不涉及分布式存儲(chǔ)系統(tǒng),也不涉及UDP。)術(shù)語與排版范例本書大量使用英文術(shù)語,甚至有少量英文引文。設(shè)計(jì)模式的名字一律用英文,例如Observer、Reactor、Singleton。在中文術(shù)語不夠突出時(shí),也會(huì)使用英文,例如class、heap、event loop、STLalgorithm等。注意幾個(gè)中文C++術(shù)語:對(duì)象實(shí)體(instance)、函數(shù)重載決議(resolution)、模板具現(xiàn)化(instantiation)、覆寫(override)虛函數(shù)、提領(lǐng)(dereference)指針。本書中的英語可數(shù)名詞一般不用復(fù)數(shù)形式,例如兩個(gè)class,6 個(gè)syscall;但有時(shí)會(huì)用(s) 強(qiáng)調(diào)中文名詞是復(fù)數(shù)。fd 是文件描述符(file descriptor)的縮寫?!癈PU數(shù)目”一般指的是核(core)的數(shù)目。容量單位kB、MB、GB表示的字節(jié)數(shù)分別為103、106、109,在特別強(qiáng)調(diào)準(zhǔn)確數(shù)值時(shí),會(huì)分別用KiB、MiB、GiB 表示210、220、230 字節(jié)。用諸如§11.5 表示本書第11.5 節(jié),L42 表示上下文中出現(xiàn)的第42 行代碼。[JCP]、[CC2e] 等是參考文獻(xiàn),見書末清單。
內(nèi)容概要
《Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫》主要講述采用現(xiàn)代C++在x86-64 Linux上編寫多線程TCP網(wǎng)絡(luò)服務(wù)程序的主流常規(guī)技術(shù),重點(diǎn)講解一種適應(yīng)性較強(qiáng)的多線程服務(wù)器的編程模型,即one loop per thread。這是在Linux下以native語言編寫用戶態(tài)高性能網(wǎng)絡(luò)程序最成熟的模式,掌握之后可順利地開發(fā)各類常見的服務(wù)端網(wǎng)絡(luò)應(yīng)用程序。本書以muduo網(wǎng)絡(luò)庫為例,講解這種編程模型的使用方法及注意事項(xiàng)?! 禠inux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫》的宗旨是貴精不貴多。掌握兩種基本的同步原語就可以滿足各種多線程同步的功能需求,還能寫出更易用的同步設(shè)施。掌握一種進(jìn)程間通信方式和一種多線程網(wǎng)絡(luò)編程模型就足以應(yīng)對(duì)日常開發(fā)任務(wù),編寫運(yùn)行于公司內(nèi)網(wǎng)環(huán)境的分布式服務(wù)系統(tǒng)。
作者簡介
陳碩,北京師范大學(xué)碩士,擅長C++多線程網(wǎng)絡(luò)編程和實(shí)時(shí)分布式系統(tǒng)架構(gòu)。曾在摩根士丹利IT部門工作5年,從事實(shí)時(shí)外匯交易系統(tǒng)開發(fā)?,F(xiàn)在在美國加州硅谷某互聯(lián)網(wǎng)大公司工作,從事大規(guī)模分布式系統(tǒng)的可靠性工程。編寫了開源C++網(wǎng)絡(luò)庫muduo,參與翻譯了《代碼大全(第2版)》和《C++編程規(guī)范(繁體版)》,整理了《C++ Primer(第4版)(評(píng)注版)》,并曾多次在各地技術(shù)大會(huì)演講。
書籍目錄
第1部分 C++多線程系統(tǒng)編程 第1章 線程安全的對(duì)象生命期管理 1.1 當(dāng)析構(gòu)函數(shù)遇到多線程 1.1.1 線程安全的定義 1.1.2 MutexLock與HutexLockGuard 1.1.3 一個(gè)線程安全的Counter示例 1.2 對(duì)象的創(chuàng)建很簡單 1.3 銷毀太難 1.3.1 mutex不是辦法 1.3.2 作為數(shù)據(jù)成員的mutex不能保護(hù)析構(gòu) 1.4 線程安全的Observer有多難 1.5 原始指針有何不妥 1.6 神器shared_ptr/weak_ptr 1.7 插曲:系統(tǒng)地避免各種指針錯(cuò)誤 1.8 應(yīng)用到Observer上 1.9 再論shared_ptr的線程安全 1.10 shared_ptr技術(shù)與陷阱 1.11 對(duì)象池 1.11.1 enable_shared_from_this 1.11.2 弱回調(diào) 1.12 替代方案 1.13 心得與小結(jié) 1.14 Observer之謬 第2章 線程同步精要 2.1 互斥器(mutex) 2.1.1 只使用非遞歸的mutex 2.1.2 死鎖 2.2 條件變量(condition variable) 2.3 不要用讀寫鎖和信號(hào)量 2.4 封裝MutexLock、MutexLockGuard、Condition 2.5 線程安全的Singleton實(shí)現(xiàn) 2.6 sleep(3)不是同步原語 2.7 歸納與總結(jié) 2.8 借shared_ptr實(shí)現(xiàn)copy-on-write 第3章 多線程服務(wù)器的適用場合與常用編程模型 3.1 進(jìn)程與線程 3.2 單線程服務(wù)器的常用編程模型 3.3 多線程服務(wù)器的常用編程模型 3.3.1 one loop per thread 3.3.2 線程池 3.3.3 推薦模式 3.4 進(jìn)程間通信只用TCP 3.5 多線程服務(wù)器的適用場合 3.5.1 必須用單線程的場合 3.5.2 單線程程序的優(yōu)缺點(diǎn) 3.5.3 適用多線程程序的場景 3.6 “多線程服務(wù)器的適用場合”例釋與答疑 第4章 C++多線程系統(tǒng)編程精要 4.1 基本線程原語的選用 4.2 C/C++系統(tǒng)庫的線程安全性 4.3 Linux上的線程標(biāo)識(shí) 4.4 線程的創(chuàng)建與銷毀的守則 4.4.1 pthread_cancel與C++ 4.4.2 exit(3)在C++中不是線程安全的 4.5 善用__thread關(guān)鍵字 4.6 多線程與IO Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫 4.7 用RAII包裝文件描述符 4.8 RAII與fork() 4.9 多線程與fork() 4.10 多線程與signal 4.11 Linux新增系統(tǒng)調(diào)用的啟示 第5章 高效的多線程日志 5.1 功能需求 5.2 性能需求 5.3 多線程異步日志 5.4 其他方案 第2部分 muduo網(wǎng)絡(luò)庫 第6章 muduo網(wǎng)絡(luò)庫簡介 6.1 由來 6.2 安裝 6.3 目錄結(jié)構(gòu) 6.3.1 代碼結(jié)構(gòu) 6.3.2 例子 6.3.3 線程模型 6.4 使用教程 6.4.1 TCP網(wǎng)絡(luò)編程本質(zhì)論 6.4.2 echo服務(wù)的實(shí)現(xiàn) 6.4.3 七步實(shí)現(xiàn)finger服務(wù) 6.5 性能評(píng)測 6.5.1 muduo與Boost.Asio、libevent2的吞吐量對(duì)比 6.5.2 擊鼓傳花:對(duì)比muduo與libevent2的事件處理效率 6.5.3 muduo與Nginx的吞吐量對(duì)比 6.5.4 muduo與ZeroMQ的延遲對(duì)比 6.6 詳解muduo多線程模型 6.6.1 數(shù)獨(dú)求解服務(wù)器 6.6.2 常見的并發(fā)網(wǎng)絡(luò)服務(wù)程序設(shè)計(jì)方案 Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫 第7章 muduo編程示例 7.1 五個(gè)簡單TCP示例 7.2 文件傳輸 7.3 Boost.Asio的聊天服務(wù)器 7.3.1 TCP分包 7.3.2 消息格式 7.3.3 編解碼器LengthHeaderCodec 7.3.4 服務(wù)端的實(shí)現(xiàn) 7.3.5 客戶端的實(shí)現(xiàn) 7.4 muduo Buffer類的設(shè)計(jì)與使用 7.4.1 muduo的IO模型 7.4.2 為什么non-blocking網(wǎng)絡(luò)編程中應(yīng)用層buffer是必需的 7.4.3 Buffer的功能需求 7.4.4 Buffer的數(shù)據(jù)結(jié)構(gòu) 7.4.5 Buffer的操作 7.4.6 其他設(shè)計(jì)方案 7.4.7 性能是不是問題 7.5 一種自動(dòng)反射消息類型的Google Protobuf網(wǎng)絡(luò)傳輸方案 7.5.1 網(wǎng)絡(luò)編程中使用Protobuf的兩個(gè)先決條件 7.5.2 根據(jù)type name反射自動(dòng)創(chuàng)建Message對(duì)象 7.5.3 Protobuf傳輸格式 7.6 在muduo中實(shí)現(xiàn)Protobuf編解碼器與消息分發(fā)器 7.6.1 什么是編解碼器(codec) 7.6.2 實(shí)現(xiàn)ProtobufCodec 7.6.3 消息分發(fā)器(dispatcher)有什么用 7.6.4 ProtobufCodec與ProtobufDispatcher的綜合運(yùn)用 7.6.5 ProtobufDispatcher的兩種實(shí)現(xiàn) 7.6.6 ProtobufCodec和ProtobufDispatcher有何意義 7.7 限制服務(wù)器的最大并發(fā)連接數(shù) 7.7.1 為什么要限制并發(fā)連接數(shù) 7.7.2 在muduo中限制并發(fā)連接數(shù) Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫 7.8 定時(shí)器 7.8.1 程序中的時(shí)間 7.8.2 Linux時(shí)間函數(shù) 7.8.3 muduo的定時(shí)器接口 7.8.4 Boost.Asio Timer示例 7.8.5 Java Netty示例 7.9 測量兩臺(tái)機(jī)器的網(wǎng)絡(luò)延遲和時(shí)間差 7.10 用timing wheel踢掉空閑連接 7.10.1 timing wheel原理 7.10.2 代碼實(shí)現(xiàn)與改進(jìn) 7.11 簡單的消息廣播服務(wù) 7.12 “串并轉(zhuǎn)換”連接服務(wù)器及其自動(dòng)化測試 7.13 socks4a代理服務(wù)器 7.13.1 TCP中繼器 7.13.2 socks4a代理服務(wù)器 7.13.3 N:1與1:N連接轉(zhuǎn)發(fā) 7.14 短址服務(wù) 7.15 與其他庫集成 7.15.1 UDNS 7.15.2 c—ares DNS 7.15.3 curl 7.15.4 更多 …… 第8章 muduo網(wǎng)絡(luò)庫設(shè)計(jì)與實(shí)現(xiàn) 第3部分 工程實(shí)踐經(jīng)驗(yàn)談 第9章 分布式系統(tǒng)工程實(shí)踐 第10章 C++編譯鏈接模型精要 第11章 反思C++面向?qū)ο笈c虛函數(shù) 第12章 C++經(jīng)驗(yàn)談 第4部分 附錄 附錄A 談一談網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn) 附錄B 從《C++Primer(第4版)》入手學(xué)習(xí)C++ 附錄C 關(guān)于Boost的看法 附錄D 關(guān)于TCP并發(fā)連接的幾個(gè)思考題與試驗(yàn) 參考文獻(xiàn)
章節(jié)摘錄
版權(quán)頁: 插圖: 談一談網(wǎng)絡(luò)編程學(xué)習(xí)經(jīng)驗(yàn) 本文談一談我在學(xué)習(xí)網(wǎng)絡(luò)編程方面的一些個(gè)人經(jīng)驗(yàn)?!熬W(wǎng)絡(luò)編程”這個(gè)術(shù)語的范圍很廣,本文指用Sockets API 開發(fā)基于TCP/IP 的網(wǎng)絡(luò)應(yīng)用程序,具體定義見§A.1.5 “網(wǎng)絡(luò)編程的各種任務(wù)角色”。 受限于本人的經(jīng)歷和經(jīng)驗(yàn),本附錄的適應(yīng)范圍是: x86-64 Linux 服務(wù)端網(wǎng)絡(luò)編程,直接或間接使用Sockets API。 公司內(nèi)網(wǎng)。不一定是局域網(wǎng),但總體位于公司防火墻之內(nèi),環(huán)境可控。 本文可能不適合: PC 客戶端網(wǎng)絡(luò)編程,程序運(yùn)行在客戶的PC 上,環(huán)境多變且不可控。 Windows 網(wǎng)絡(luò)編程。 面向公網(wǎng)的服務(wù)程序。 高性能網(wǎng)絡(luò)服務(wù)器。 本文分兩個(gè)部分: 1.網(wǎng)絡(luò)編程的一些“胡思亂想”,以自問自答的形式談?wù)勎覍?duì)這一領(lǐng)域的認(rèn)識(shí)。 2.幾本必看的書,基本上還是W.Richard Stevents 的那幾本。 另外,本文沒有特別說明時(shí)均暗指TCP 協(xié)議,“連接”是“TCP 連接”,“服務(wù)端”是“TCP 服務(wù)端”。 A.1 網(wǎng)絡(luò)編程的一些“胡思亂想” 以下大致列出我對(duì)網(wǎng)絡(luò)編程的一些想法,前后無關(guān)聯(lián)。 A.1.1 網(wǎng)絡(luò)編程是什么 網(wǎng)絡(luò)編程是什么?是熟練使用Sockets API嗎?說實(shí)話,在實(shí)際項(xiàng)目里我只用過兩次Sockets API,其他時(shí)候都是使用封裝好的網(wǎng)絡(luò)庫。 第一次是2005年在學(xué)校做一個(gè)羽毛球賽場計(jì)分系統(tǒng):我用C#編寫運(yùn)行在PC上的軟件,負(fù)責(zé)比分的顯示;再用C#寫了運(yùn)行在PDA上的計(jì)分界面,記分員拿著PDA記錄比分;這兩部分程序通過TCP協(xié)議相互通信。這其實(shí)是個(gè)簡單的分布式系統(tǒng),體育館有幾片場地,每個(gè)場地都有一名拿PDA的記分員,每個(gè)場地都有兩臺(tái)顯示比分的PC(顯示器是42寸平板電視,放在場地的對(duì)角,這樣兩邊看臺(tái)的觀眾都能看到比分)。這兩臺(tái)PC的功能不完全一樣,一臺(tái)只負(fù)責(zé)顯示當(dāng)前比分,另一臺(tái)還要負(fù)責(zé)與PDA通信,并更新數(shù)據(jù)庫里的比分信息。此外,還有一臺(tái)PC負(fù)責(zé)周期性地從數(shù)據(jù)庫讀出全部7片場地的比分,顯示在體育館墻上的大屏幕上。這臺(tái)PC上還運(yùn)行著一個(gè)程序,負(fù)責(zé)生成比分?jǐn)?shù)據(jù)的靜態(tài)頁面,通過FTP上傳發(fā)布到某門戶網(wǎng)站的體育頻道。系統(tǒng)中還有一個(gè)錄入賽程(參賽隊(duì)、運(yùn)動(dòng)員、出場順序等)數(shù)據(jù)庫的程序,運(yùn)行在數(shù)據(jù)庫服務(wù)器上。算下來整個(gè)系統(tǒng)有十來個(gè)程序,運(yùn)行在二十多臺(tái)設(shè)備(PC 和PDA)上,還要考慮可靠性,避免single point of failure。 這是我第一次寫實(shí)際項(xiàng)目中的網(wǎng)絡(luò)程序,當(dāng)時(shí)寫下來的感覺是像寫命令行與用戶交互的程序:程序在命令行輸出一句提示語,等待客戶輸入一句話,然后處理客戶輸入,再輸出下一句提示語,如此循環(huán)。只不過這里的“客戶”不是人,而是另一個(gè)程序。在建立好TCP 連接之后,雙方的程序都是read/write 循環(huán)(為求簡單,我用的是blocking 讀寫),直到有一方斷開連接。 第二次是2010 年編寫muduo 網(wǎng)絡(luò)庫,我再次拿起了Sockets API,寫了一個(gè)基于Reactor 模式的C++ 網(wǎng)絡(luò)庫。寫這個(gè)庫的目的之一就是想讓日常的網(wǎng)絡(luò)編程從Sockets API 的瑣碎細(xì)節(jié)中解脫出來,讓程序員專注于業(yè)務(wù)邏輯,把時(shí)間用在刀刃上。muduo 網(wǎng)絡(luò)庫的示例代碼包含了幾十個(gè)網(wǎng)絡(luò)程序,這些示例程序都沒有直接使用Sockets API。 在此之外,無論是實(shí)習(xí)還是工作,雖然我寫的程序都會(huì)通過TCP 協(xié)議與其他程序打交道,但我沒有直接使用過Sockets API。對(duì)于TCP 網(wǎng)絡(luò)編程,我認(rèn)為核心是處理“三個(gè)半事件”,見§6.4.1“TCP 網(wǎng)絡(luò)編程本質(zhì)論”。程序員的主要工作是在事件處理函數(shù)中實(shí)現(xiàn)業(yè)務(wù)邏輯,而不是和Sockets API“較勁”。 這里還是沒有說清楚“網(wǎng)絡(luò)編程”是什么,請(qǐng)繼續(xù)閱讀后文§A.1.5“網(wǎng)絡(luò)編程的各種任務(wù)角色”。 A.1.2 學(xué)習(xí)網(wǎng)絡(luò)編程有用嗎 以上說的是比較底層的網(wǎng)絡(luò)編程,程序代碼直接面對(duì)從TCP 或UDP 收到的數(shù)據(jù)以及構(gòu)造數(shù)據(jù)包發(fā)出去。在實(shí)際工作中,另一種常見的情況是通過各種client library來與服務(wù)端打交道,或者在現(xiàn)成的框架中填空來實(shí)現(xiàn)server,或者采用更上層的通信方式。比如用libmemcached 與memcached 打交道,使用libpq 來與PostgreSQL 打交道,編寫Servlet 來響應(yīng)HTTP 請(qǐng)求,使用某種RPC 與其他進(jìn)程通信,等等。這些情況都會(huì)發(fā)生網(wǎng)絡(luò)通信,但不一定算作“網(wǎng)絡(luò)編程”。如果你的工作是前面列舉的這些,學(xué)習(xí)TCP/IP 網(wǎng)絡(luò)編程還有用嗎? 我認(rèn)為還是有必要學(xué)一學(xué),至少在troubleshooting 的時(shí)候有用。無論如何,這些library 或framework 都會(huì)調(diào)用底層的Sockets API 來實(shí)現(xiàn)網(wǎng)絡(luò)功能。當(dāng)你的程序遇到一個(gè)線上問題時(shí),如果你熟悉Sockets API,那么從strace 不難發(fā)現(xiàn)程序卡在哪里,盡管可能你沒有直接調(diào)用這些Sockets API。另外,熟悉TCP/IP 協(xié)議、會(huì)用tcpdump 也非常有助于分析解決線上網(wǎng)絡(luò)服務(wù)問題。 A.1.3 在什么平臺(tái)上學(xué)習(xí)網(wǎng)絡(luò)編程 對(duì)于服務(wù)端網(wǎng)絡(luò)編程,我建議在Linux 上學(xué)習(xí)。 如果在10年前,這個(gè)問題的答案或許是FreeBSD,因?yàn)镕reeBSD“根正苗紅”,在2000年那一次互聯(lián)網(wǎng)浪潮中扮演了重要角色,是很多公司首選的免費(fèi)服務(wù)器操作系統(tǒng)。2000 年那會(huì)兒Linux 還遠(yuǎn)未成熟,連epoll 都還沒有實(shí)現(xiàn)。(FreeBSD 在2001年發(fā)布4.1 版,加入了kqueue,從此C10k 不是問題。) 10年后的今天,事情起了一些變化,Linux成為市場份額最大的服務(wù)器操作系統(tǒng)。在Linux這種大眾系統(tǒng)上學(xué)網(wǎng)絡(luò)編程,遇到什么問題會(huì)比較容易解決。因?yàn)橛玫娜硕?,你遇到的問題別人多半也遇到過;同樣因?yàn)橛玫娜硕?,如果真的有什么?nèi)核bug,很快就會(huì)得到修復(fù),至少有work around 的辦法。如果用別的系統(tǒng),可能一個(gè)問題發(fā)到論壇上半個(gè)月都不會(huì)有人理。從內(nèi)核源碼的風(fēng)格看,F(xiàn)reeBSD 更干凈整潔,注釋到位,但是無奈它的市場份額遠(yuǎn)不如Linux,學(xué)習(xí)Linux 是更好的技術(shù)投資。 A.1.4 可移植性重要嗎 寫網(wǎng)絡(luò)程序要不要考慮移植性?要不要跨平臺(tái)?這取決于項(xiàng)目需要,如果貴公司做的程序要賣給其他公司,而對(duì)方可能使用Windows、Linux、FreeBSD、Solaris、AIX、HP-UX 等等操作系統(tǒng),這時(shí)候當(dāng)然要考慮移植性。如果編寫公司內(nèi)部的服務(wù)器上用的網(wǎng)絡(luò)程序,那么大可只關(guān)注一個(gè)平臺(tái),比如Linux。因?yàn)榫帉懞途S護(hù)可移植的網(wǎng)絡(luò)程序的代價(jià)相當(dāng)高,平臺(tái)間的差異可能遠(yuǎn)比想象中大,即便是POSIX 系統(tǒng)之間也有不小的差異(比如Linux 沒有SO_NOSIGPIPE 選項(xiàng),Linux 的pipe(2) 是單向的,而FreeBSD 是雙向的),錯(cuò)誤的返回碼也大不一樣。 我就不打算把muduo 往Windows 或其他操作系統(tǒng)移植。如果需要編寫可移植的網(wǎng)絡(luò)程序,我寧愿用libevent、libuv、Java Netty 這樣現(xiàn)成的庫,把“臟活、累活”留給別人。 A.1.5 網(wǎng)絡(luò)編程的各種任務(wù)角色 計(jì)算機(jī)網(wǎng)絡(luò)是個(gè)big topic,涉及很多人物和角色,既有開發(fā)人員,也有運(yùn)維人員。比方說:公司內(nèi)部兩臺(tái)機(jī)器之間ping 不通,通常由網(wǎng)絡(luò)運(yùn)維人員解決,看看是布線有問題還是路由器設(shè)置不對(duì);兩臺(tái)機(jī)器能ping 通,但是程序連不上,經(jīng)檢查是本機(jī)防火墻設(shè)置有問題,通常由系統(tǒng)管理員解決;兩臺(tái)機(jī)器能連上,但是丟包很嚴(yán)重,發(fā)現(xiàn)是網(wǎng)卡或者交換機(jī)的網(wǎng)口故障,由硬件維修人員解決;兩臺(tái)機(jī)器的程序能連上,但是偶爾發(fā)過去的請(qǐng)求得不到響應(yīng),通常是程序bug,應(yīng)該由開發(fā)人員解決。 本文主要關(guān)心開發(fā)人員這一角色。下面簡單列出一些我能想到的跟網(wǎng)絡(luò)打交道的編程任務(wù),其中前三項(xiàng)是面向網(wǎng)絡(luò)本身,后面幾項(xiàng)是在計(jì)算機(jī)網(wǎng)絡(luò)之上構(gòu)建信息系統(tǒng)。 1.開發(fā)網(wǎng)絡(luò)設(shè)備,編寫防火墻、交換機(jī)、路由器的固件(firmware)。 2.開發(fā)或移植網(wǎng)卡的驅(qū)動(dòng)。 3.移植或維護(hù)TCP/IP 協(xié)議棧(特別是在嵌入式系統(tǒng)上)。 4.開發(fā)或維護(hù)標(biāo)準(zhǔn)的網(wǎng)絡(luò)協(xié)議程序,HTTP、FTP、DNS、SMTP、POP3、NFS。 5.開發(fā)標(biāo)準(zhǔn)網(wǎng)絡(luò)協(xié)議的“附加品”,比如HAProxy、squid、varnish 等Web loadbalancer。 6.開發(fā)標(biāo)準(zhǔn)或非標(biāo)準(zhǔn)網(wǎng)絡(luò)服務(wù)的客戶端庫,比如ZooKeeper 客戶端庫、memcached客戶端庫。 7.開發(fā)與公司業(yè)務(wù)直接相關(guān)的網(wǎng)絡(luò)服務(wù)程序,比如即時(shí)聊天軟件的后臺(tái)服務(wù)器、網(wǎng)游服務(wù)器、金融交易系統(tǒng)、互聯(lián)網(wǎng)企業(yè)用的分布式海量存儲(chǔ)、微博發(fā)帖的內(nèi)部廣播通知等等。 8.客戶端程序中涉及網(wǎng)絡(luò)的部分,比如郵件客戶端中與POP3、SMTP 通信的部分,以及網(wǎng)游的客戶端程序中與服務(wù)器通信的部分。 本文所指的“網(wǎng)絡(luò)編程”專指第7 項(xiàng),即在TCP/IP 協(xié)議之上開發(fā)業(yè)務(wù)軟件。換句話說,不是用Sockets API 開發(fā)muduo 這樣的網(wǎng)絡(luò)庫,而是用libevent、muduo、Netty、gevent 這樣現(xiàn)成的庫開發(fā)業(yè)務(wù)軟件,muduo 自帶的十幾個(gè)示例程序是業(yè)務(wù)軟件的代表。 A.1.6 面向業(yè)務(wù)的網(wǎng)絡(luò)編程的特點(diǎn) 與通用的網(wǎng)絡(luò)服務(wù)器不同,面向公司業(yè)務(wù)的專用網(wǎng)絡(luò)程序有其自身的特點(diǎn)。 業(yè)務(wù)邏輯比較復(fù)雜,而且時(shí)常變化 如果寫一個(gè)HTTP 服務(wù)器,在大致實(shí)現(xiàn)HTTP 1.1 標(biāo)準(zhǔn)之后,程序的主體功能一般不會(huì)有太大的變化,程序員會(huì)把時(shí)間放在性能調(diào)優(yōu)和bug 修復(fù)上。而開發(fā)針對(duì)公司業(yè)務(wù)的專用程序時(shí),功能說明書(spec)很可能不如HTTP 1.1 標(biāo)準(zhǔn)那么細(xì)致明確。更重要的是,程序是快速演化的。以即時(shí)聊天工具的后臺(tái)服務(wù)器為例,可能第一版只支持在線聊天;幾個(gè)月之后發(fā)布第二版,支持離線消息;又過了幾個(gè)月,第三版支持隱身聊天;隨后,第四版支持上傳頭像;如此等等。這要求程序員能快速響應(yīng)新的業(yè)務(wù)需求,公司才能保持競爭力。由于業(yè)務(wù)時(shí)常變化(假設(shè)每月一次版本升級(jí)),也會(huì)降低服務(wù)程序連續(xù)運(yùn)行時(shí)間的要求。相反,我們要設(shè)計(jì)一套流程,通過輪流重啟服務(wù)器來完成平滑升級(jí)(§9.2.2)。 不一定需要遵循公認(rèn)的通信協(xié)議標(biāo)準(zhǔn) 比方說網(wǎng)游服務(wù)器就沒什么協(xié)議標(biāo)準(zhǔn),反正客戶端和服務(wù)端都是本公司開發(fā)的,如果發(fā)現(xiàn)目前的協(xié)議設(shè)計(jì)有問題,兩邊一起改就行了。由于可以自己設(shè)計(jì)協(xié)議,因此我們可以繞開一些性能難點(diǎn),簡化程序結(jié)構(gòu)。比方說,對(duì)于多線程的服務(wù)程序,如果用短連接TCP 協(xié)議,為了優(yōu)化性能通常要精心設(shè)計(jì)accept 新連接的機(jī)制2,避免驚群并減少上下文切換。但是如果改用長連接,用最簡單的單線程accept 就行了。 程序結(jié)構(gòu)沒有定論 對(duì)于高并發(fā)大吞吐的標(biāo)準(zhǔn)網(wǎng)絡(luò)服務(wù),一般采用單線程事件驅(qū)動(dòng)的方式開發(fā),比如HAProxy、lighttpd 等都是這個(gè)模式。但是對(duì)于專用的業(yè)務(wù)系統(tǒng),其業(yè)務(wù)邏輯比較復(fù)雜,占用較多的CPU 資源,這種單線程事件驅(qū)動(dòng)方式不見得能發(fā)揮現(xiàn)在多核處理器的優(yōu)勢。這留給程序員比較大的自由發(fā)揮空間,做好了“橫掃千軍”,做爛了一敗涂地。我認(rèn)為目前one loop per thread 是通用性較高的一種程序結(jié)構(gòu),能發(fā)揮多核的優(yōu)勢,見§3.3 和§6.6。 性能評(píng)判的標(biāo)準(zhǔn)不同 如果開發(fā)httpd 這樣的通用服務(wù),必然會(huì)和開源的Nginx、lighttpd 等高性能服務(wù)器比較,程序員要投入相當(dāng)?shù)木θ?yōu)化程序,才能在市場上占有一席之地。而面向業(yè)務(wù)的專用網(wǎng)絡(luò)程序不一定是IO bound,也不一定有開源的實(shí)現(xiàn)以供對(duì)比性能,優(yōu)化方向也可能不同。程序員通常更加注重功能的穩(wěn)定性與開發(fā)的便捷性。性能只要一代比一代強(qiáng)即可。 網(wǎng)絡(luò)編程起到支撐作用,但不處于主導(dǎo)地位 程序員的主要工作是實(shí)現(xiàn)業(yè)務(wù)邏輯,而不只是實(shí)現(xiàn)網(wǎng)絡(luò)通信協(xié)議。這要求程序員深入理解業(yè)務(wù)。程序的性能瓶頸不一定在網(wǎng)絡(luò)上,瓶頸有可能是CPU、Disk IO、數(shù)據(jù)庫等,這時(shí)優(yōu)化網(wǎng)絡(luò)方面的代碼并不能提高整體性能。只有對(duì)所在的領(lǐng)域有深入的了解,明白各種因素的權(quán)衡(trade-off),才能做出一些有針對(duì)性的優(yōu)化?,F(xiàn)在的機(jī)器上,簡單的并發(fā)長連接echo服務(wù)程序不用特別優(yōu)化就做到十多萬qps,但是如果每個(gè)業(yè)務(wù)請(qǐng)求需要1ms 密集計(jì)算,在8 核機(jī)器上充其量能達(dá)到8000 qps,優(yōu)化IO 不如去優(yōu)化業(yè)務(wù)計(jì)算(如果投入產(chǎn)出合算的話)。 A.1.7 幾個(gè)術(shù)語 互聯(lián)網(wǎng)上的很多“口水戰(zhàn)”是由對(duì)同一術(shù)語的不同理解引起的,比如我寫的《多線程服務(wù)器的適用場合》3,就曾經(jīng)被人說是“掛羊頭賣狗肉”,因?yàn)檫@篇文章中舉的master 例子“根本就算不上是個(gè)網(wǎng)絡(luò)服務(wù)器。因?yàn)樗钠款i根本就跟網(wǎng)絡(luò)無關(guān)?!?網(wǎng)絡(luò)服務(wù)器 “網(wǎng)絡(luò)服務(wù)器”這個(gè)術(shù)語確實(shí)含義模糊,到底指硬件還是軟件?到底是服務(wù)于網(wǎng)絡(luò)本身的機(jī)器(交換機(jī)、路由器、防火墻、NAT),還是利用網(wǎng)絡(luò)為其他人或程序提供服務(wù)的機(jī)器(打印服務(wù)器、文件服務(wù)器、郵件服務(wù)器)?每個(gè)人根據(jù)自己熟悉的領(lǐng)域,可能會(huì)有不同的解讀。比方說,或許有人認(rèn)為只有支持高并發(fā)、高吞吐量的才算是網(wǎng)絡(luò)服務(wù)器。 為了避免無謂的爭執(zhí),我只用“網(wǎng)絡(luò)服務(wù)程序”或者“網(wǎng)絡(luò)應(yīng)用程序”這種含義明確的術(shù)語?!伴_發(fā)網(wǎng)絡(luò)服務(wù)程序”通常不會(huì)造成誤解。 客戶端?服務(wù)端?在TCP 網(wǎng)絡(luò)編程中,客戶端和服務(wù)端很容易區(qū)分,主動(dòng)發(fā)起連接的是客戶端,被動(dòng)接受連接的是服務(wù)端。當(dāng)然,這個(gè)“客戶端”本身也可能是個(gè)后臺(tái)服務(wù)程序,HTTP proxy 對(duì)HTTP server 來說就是個(gè)客戶端。 客戶端編程?服務(wù)端編程?但是“服務(wù)端編程”和“客戶端編程”就不那么好區(qū)分了。比如Web crawler,它會(huì)主動(dòng)發(fā)起大量連接,扮演的是HTTP客戶端的角色,但似乎應(yīng)該歸入“服務(wù)端編程”。又比如寫一個(gè)HTTP proxy,它既會(huì)扮演服務(wù)端——被動(dòng)接受Web browser 發(fā)起的連接,也會(huì)扮演客戶端——主動(dòng)向HTTP server發(fā)起連接,它究竟算服務(wù)端還是客戶端?我猜大多數(shù)人會(huì)把它歸入服務(wù)端編程。 那么究竟如何定義“服務(wù)端編程”? 服務(wù)端編程需要處理大量并發(fā)連接?也許是,也許不是。比如云風(fēng)在一篇介紹網(wǎng)游服務(wù)器的博客4中就談到,網(wǎng)游中用到的“連接服務(wù)器”需要處理大量連接,而“邏輯服務(wù)器”只有一個(gè)外部連接。那么開發(fā)這種網(wǎng)游“邏輯服務(wù)器”算服務(wù)端編程還是客戶端編程呢?又比如機(jī)房的服務(wù)進(jìn)程監(jiān)控軟件,并發(fā)數(shù)跟機(jī)器數(shù)成正比,至多也就是兩三千的并發(fā)連接。(再大規(guī)模就超出本書的范圍了。) 我認(rèn)為,“服務(wù)端網(wǎng)絡(luò)編程”指的是編寫沒有用戶界面的長期運(yùn)行的網(wǎng)絡(luò)程序,程序默默地運(yùn)行在一臺(tái)服務(wù)器上,通過網(wǎng)絡(luò)與其他程序打交道,而不必和人打交道。與之對(duì)應(yīng)的是客戶端網(wǎng)絡(luò)程序,要么是短時(shí)間運(yùn)行,比如wget;要么是有用戶界面(無論是字符界面還是圖形界面)。本文主要談服務(wù)端網(wǎng)絡(luò)編程。
編輯推薦
《Linux多線程服務(wù)端編程:使用muduo C++網(wǎng)絡(luò)庫》編輯推薦:示范在多核時(shí)代采用現(xiàn)代C++編寫,多線程TCP網(wǎng)絡(luò)服務(wù)器的正規(guī)做法。
圖書封面
圖書標(biāo)簽Tags
無
評(píng)論、評(píng)分、閱讀與下載