编辑推荐

  

多年编写生产环境下多线程服务端程序经验之精华,示范在多核时代采用现代C++编写多线程TCP网络服务器的正规做法

内容简介

  《Linux多线程服务端编程使用muduoC++网络库》主要讲述采用现代C++在x86-64Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即oneloopperthread。这是在Linux下以native语言编写用户态高性能网络程序成熟的模式,掌握之后可顺利地开发各类常见的服务端网络应用程序。《Linux多线程服务端编程 使用muduo C++网络库》以muduo网络库为例,讲解这种编程模型的使用方法及注意事项。
  《Linux多线程服务端编程使用muduoC++网络库》的宗旨是贵精不贵多。掌握两种基本的同步原语就可以满足各种多线程同步的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。

作者简介

  陈硕,北京师范大学硕士,擅长C++多线程网络编程和实时分布式系统架构。曾在摩根士丹利IT部门工作5年,从事实时外汇交易系统开发。现在在美国加州硅谷某互联网大公司工作,从事大规模分布式系统的可靠性工程。编写了开源C++网络库muduo,参与翻译了《代码大全(第2版)》和《C++编程规范(繁体版)》,整理了《C++Primer(第4版)(评注版)》,并曾多次在各地技术大会演讲。

目录

第1部分C++多线程系统编程
第1章线程安全的对象生命期管理
1.1当析构函数遇到多线程
1.1.1线程安全的定义
1.1.2MutexLock与MutexLockGuard
1.1.3一个线程安全的Counter示例
1.2对象的创建很简单.
1.3销毁太难
1.3.1mutex不是办法
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.10shared_ptr技术与陷阱
1.11对象池.
1.11.1enable_shared_from_this
1.11.2弱回调.
1.12替代方案
1.13心得与小结.
1.14Observer之谬.
第2章线程同步精要
2.1互斥器(mutex).
2.1.1只使用非递归的mutex
2.1.2死锁
2.2条件变量(conditionvariable).
2.3不要用读写锁和信号量
2.4封装MutexLock、MutexLockGuard、Condition
2.5线程安全的Singleton实现
2.6sleep(3)不是同步原语
2.7归纳与总结.
2.8借shared_ptr实现copy-on-write
第3章多线程服务器的适用场合与常用编程模型
3.1进程与线程.
3.2单线程服务器的常用编程模型
3.3多线程服务器的常用编程模型
3.3.1oneloopperthread
3.3.2线程池.
3.3.3推荐模式
3.4进程间通信只用TCP.
3.5多线程服务器的适用场合.
3.5.1必须用单线程的场合.
3.5.2单线程程序的优缺点.
3.5.3适用多线程程序的场景
3.6“多线程服务器的适用场合”例释与答疑
第4章C++多线程系统编程精要
4.1基本线程原语的选用.
4.2C/C++系统库的线程安全性.
4.3Linux上的线程标识
4.4线程的创建与销毁的守则.
4.4.1pthread_cancel与C++.
4.4.2exit(3)在C++中不是线程安全的.
4.5善用__thread关键字.
4.6多线程与IO
4.7用RAII包装文件描述符.
4.8RAII与fork().
4.9多线程与fork().
4.10多线程与signal
4.11Linux新增系统调用的启示
第5章高效的多线程日志
5.1功能需求
5.2性能需求
5.3多线程异步日志
5.4其他方案

第2部分muduo网络库
第6章muduo网络库简介
6.1由来.
6.2安装.
6.3目录结构
6.3.1代码结构
6.3.2例子
6.3.3线程模型
6.4使用教程
6.4.1TCP网络编程本质论.
6.4.2echo服务的实现.
6.4.3七步实现finger服务.
6.5性能评测
6.5.1muduo与Boost.Asio、libevent2的吞吐量对比
6.5.2击鼓传花:对比muduo与libevent2的事件处理效率
6.5.3muduo与Nginx的吞吐量对比.
6.5.4muduo与ZeroMQ的延迟对比.
6.6详解muduo多线程模型.
6.6.1数独求解服务器
6.6.2常见的并发网络服务程序设计方案.
第7章muduo编程示例
7.1五个简单TCP示例
7.2文件传输
7.3Boost.Asio的聊天服务器.
7.3.1TCP分包
7.3.2消息格式
7.3.3编解码器LengthHeaderCodec
7.3.4服务端的实现.
7.3.5客户端的实现.
7.4muduoBuffer类的设计与使用.
7.4.1muduo的IO模型
7.4.2为什么non-blocking网络编程中应用层buffer是必需的
7.4.3Buffer的功能需求
7.4.4Buffer的数据结构
7.4.5Buffer的操作.
7.4.6其他设计方案.
7.4.7性能是不是问题
7.5一种自动反射消息类型的GoogleProtobuf网络传输方案
7.5.1网络编程中使用Protobuf的两个先决条件.
7.5.2根据typename反射自动创建Message对象
7.5.3Protobuf传输格式
7.6在muduo中实现Protobuf编解码器与消息分发器
7.6.1什么是编解码器(codec)
7.6.2实现ProtobufCodec.
7.6.3消息分发器(dispatcher)有什么用
7.6.4ProtobufCodec与ProtobufDispatcher的综合运用.
7.6.5ProtobufDispatcher的两种实现
7.6.6ProtobufCodec和ProtobufDispatcher有何意义.
7.7限制服务器的最大并发连接数
7.7.1为什么要限制并发连接数
7.7.2在muduo中限制并发连接数
7.8定时器.
7.8.1程序中的时间.
7.8.2Linux时间函数
7.8.3muduo的定时器接口.
7.8.4Boost.AsioTimer示例
7.8.5JavaNetty示例
7.9测量两台机器的网络延迟和时间差.
7.10用timingwheel踢掉空闲连接
7.10.1timingwheel原理
7.10.2代码实现与改进
7.11简单的消息广播服务.
7.12“串并转换”连接服务器及其自动化测试
7.13socks4a代理服务器
7.13.1TCP中继器
7.13.2socks4a代理服务器
7.13.3N:1与1:N连接转发
7.14短址服务
7.15与其他库集成.
7.15.1UDNS.
7.15.2c-aresDNS.
7.15.3curl.
7.15.4更多
第8章muduo网络库设计与实现
8.0什么都不做的EventLoop.
8.1Reactor的关键结构
8.1.1Channelclass.
8.1.2Pollerclass
8.1.3EventLoop的改动.
8.2TimerQueue定时器
8.2.1TimerQueueclass.
8.2.2EventLoop的改动.
8.3EventLoop::runInLoop()函数
8.3.1提高TimerQueue的线程安全性.
8.3.2EventLoopThreadclass
8.4实现TCP网络库
8.5TcpServer接受新连接
8.5.1TcpServerclass
8.5.2TcpConnectionclass.
8.6TcpConnection断开连接.
8.7Buffer读取数据
8.7.1TcpConnection使用Buffer作为输入缓冲.
8.7.2Buffer::readFd()
8.8TcpConnection发送数据.
8.9完善TcpConnection
8.9.1SIGPIPE
8.9.2TCPNoDelay和TCPkeepalive
8.9.3WriteCompleteCallback和HighWaterMarkCallback.
8.10多线程TcpServer.
8.11Connector.
8.12TcpClient.
8.13epoll
8.14测试程序一览.

第3部分工程实践经验谈
第9章分布式系统工程实践
9.1我们在技术浪潮中的位置.
9.1.1分布式系统的本质困难
9.1.2分布式系统是个险恶的问题.
9.2分布式系统的可靠性浅说.
9.2.1分布式系统的软件不要求724可靠
9.2.2“能随时重启进程”作为程序设计目标.
9.3分布式系统中心跳协议的设计
9.4分布式系统中的进程标识.
9.4.1错误做法
9.4.2正确做法
9.4.3TCP协议的启示
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:机群管理与namingservice结合.
第10章C++编译链接模型精要
10.1C语言的编译模型及其成因.
10.1.1为什么C语言需要预处理
10.1.2C语言的编译模型.
10.2C++的编译模型
10.2.1单遍编译
10.2.2前向声明
10.3C++链接(linking).
10.3.1函数重载
10.3.2inline函数.
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源码编译是王道
第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.1C++程序库的作者的生存环境
11.3.2虚函数作为库的接口的两大用途
11.3.3虚函数作为接口的弊端
11.3.4假如Linux系统调用以COM接口方式实现
11.3.5Java是如何应对的
11.4动态库接口的推荐做法
11.5以boost::function和boost::bind取代虚函数.
11.5.1基本用途
11.5.2对程序库的影响
11.5.3对面向对象程序设计的影响.
11.6iostream的用途与局限
11.6.1stdio格式化输入输出的缺点.
11.6.2iostream的设计初衷.
11.6.3iostream与标准库其他组件的交互.
11.6.4iostream在使用方面的缺点.
11.6.5iostream在设计方面的缺点.
11.6.6一个300行的memorybufferoutputstream.
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数据抽象的例子
第12章C++经验谈
12.1用异或来交换变量是错误的.
12.1.1编译器会分别生成什么代码.
12.1.2为什么短的代码不一定快
12.2不要重载全局::operatornew()
12.2.1内存管理的基本要求.
12.2.2重载::operatornew()的理由.
12.2.3::operatornew()的两种重载方式.
12.2.4现实的开发环境
12.2.5重载::operatornew()的困境.
12.2.6解决办法:替换malloc()
12.2.7为单独的class重载::operatornew()有问题吗.
12.2.8有必要自行定制内存分配器吗
12.3带符号整数的除法与余数.
12.3.1语言标准怎么说
12.3.2C/C++编译器的表现.
12.3.3其他语言的规定
12.3.4脚本语言解释器代码.
12.3.5硬件实现
12.4在单元测试中mock系统调用
12.4.1系统函数的依赖注入.
12.4.2链接期垫片(linkseam)
12.5慎用匿名namespace.
12.5.1C语言的static关键字的两种用法.
12.5.2C++语言的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直接拷贝(eagercopy).
12.7.2写时复制(copy-on-write).
12.7.3短字符串优化(SSO)
12.8用STLalgorithm轻松解决几道算法面试题
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部分附录
附录A谈一谈网络编程学习经验
附录B从《C++Primer(第4版)》入手学习C++
附录C关于Boost的看法
附录D关于TCP并发连接的几个思考题与试验

精彩书摘

  附录A
  谈一谈网络编程学习经验
  本文谈一谈我在学习网络编程方面的一些个人经验。“网络编程”这个术语的范围很广,本文指用SocketsAPI开发基于TCP/IP的网络应用程序,具体定义见§A.1.5“网络编程的各种任务角色”。
  受限于本人的经历和经验,本附录的适应范围是:
  x86-64Linux服务端网络编程,直接或间接使用SocketsAPI。
  公司内网。不一定是局域网,但总体位于公司防火墙之内,环境可控。
  本文可能不适合:
  PC客户端网络编程,程序运行在客户的PC上,环境多变且不可控。
  Windows网络编程。
  面向公网的服务程序。
  高性能网络服务器。
  本文分两个部分:
  1.网络编程的一些“胡思乱想”,以自问自答的形式谈谈我对这一领域的认识。
  2.几本必看的书,基本上还是W.RichardStevents的那几本。
  另外,本文没有特别说明时均暗指TCP协议,“连接”是“TCP连接”,“服务
  端”是“TCP服务端”。
  A.1网络编程的一些“胡思乱想”
  以下大致列出我对网络编程的一些想法,前后无关联。
  A.1.1网络编程是什么
  网络编程是什么?是熟练使用SocketsAPI吗?说实话,在实际项目里我只用过
  两次SocketsAPI,其他时候都是使用封装好的网络库。
  第一次是2005年在学校做一个羽毛球赛场计分系统:我用C#编写运行在PC上的软件,负责比分的显示;再用C#写了运行在PDA上的计分界面,记分员拿着PDA记录比分;这两部分程序通过TCP协议相互通信。这其实是个简单的分布式系统,体育馆有几片场地,每个场地都有一名拿PDA的记分员,每个场地都有两台显示比分的PC(显示器是42寸平板电视,放在场地的对角,这样两边看台的观众都能看到比分)。这两台PC的功能不完全一样,一台只负责显示当前比分,另一台还要负责与PDA通信,并更新数据库里的比分信息。此外,还有一台PC负责周期性地从数据库读出全部7片场地的比分,显示在体育馆墙上的大屏幕上。这台PC上还运行着一个程序,负责生成比分数据的静态页面,通过FTP上传发布到某门户网站的体育频道。系统中还有一个录入赛程(参赛队、运动员、出场顺序等)数据库的程序,运行在数据库服务器上。算下来整个系统有十来个程序,运行在二十多台设备(PC和PDA)上,还要考虑可靠性,避免singlepointoffailure。
  这是我第一次写实际项目中的网络程序,当时写下来的感觉是像写命令行与用户交互的程序:程序在命令行输出一句提示语,等待客户输入一句话,然后处理客户输入,再输出下一句提示语,如此循环。只不过这里的“客户”不是人,而是另一个程序。在建立好TCP连接之后,双方的程序都是read/write循环(为求简单,我用的是blocking读写),直到有一方断开连接。
  第二次是2010年编写mudu网络库,我再次拿起了SocketsAPI,写了一个基于Reactor模式的C++网络库。写这个库的目的之一就是想让日常的网络编程从SocketsAPI的琐碎细节中解脱出来,让程序员专注于业务逻辑,把时间用在刀刃上。mudu网络库的示例代码包含了几十个网络程序,这些示例程序都没有直接使用SocketsAPI。
  在此之外,无论是实习还是工作,虽然我写的程序都会通过TCP协议与其他程序打交道,但我没有直接使用过SocketsAPI。对于TCP网络编程,我认为核心是处理“三个半事件”,见§6.4.1“TCP网络编程本质论”。程序员的主要工作是在事件处理函数中实现业务逻辑,而不是和SocketsAPI“较劲”。
  这里还是没有说清楚“网络编程”是什么,请继续阅读后文§A.1.5“网络编程的各种任务角色”。
  A.1.2学习网络编程有用吗
  以上说的是比较底层的网络编程,程序代码直接面对从TCP或UDP收到的数据以及构造数据包发出去。在实际工作中,另一种常见的情况是通过各种clientlibrary来与服务端打交道,或者在现成的框架中填空来实现server,或者采用更上层的通信方式。比如用libmemcached与memcached打交道,使用libpq来与PostgreSQL打交道,编写Servlet来响应HTTP请求,使用某种RPC与其他进程通信,等等。这些情况都会发生网络通信,但不一定算作“网络编程”。如果你的工作是前面列举的这些,学习TCP/IP网络编程还有用吗?
  我认为还是有必要学一学,至少在troubleshooting的时候有用。无论如何,这些library或framework都会调用底层的SocketsAPI来实现网络功能。当你的程序遇到一个线上问题时,如果你熟悉SocketsAPI,那么从strace不难发现程序卡在哪里,尽管可能你没有直接调用这些SocketsAPI。另外,熟悉TCP/IP协议、会用tcpdump也非常有助于分析解决线上网络服务问题。
  A.1.3在什么平台上学习网络编程
  对于服务端网络编程,我建议在Linux上学习。
  如果在10年前,这个问题的答案或许是FreeBSD,因为FreeBSD“根正苗红”,在2000年那一次互联网浪潮中扮演了重要角色,是很多公司首选的免费服务器操作系统。2000年那会儿Linux还远未成熟,连epoll都还没有实现。(FreeBSD在2001年发布4.1版,加入了kqueue,从此C10k不是问题。)
  10年后的今天,事情起了一些变化,Linux成为市场份额最大的服务器操作系统。在Linux这种大众系统上学网络编程,遇到什么问题会比较容易解决。因为用的人多,你遇到的问题别人多半也遇到过;同样因为用的人多,如果真的有什么内核bug,很快就会得到修复,至少有workaround的办法。如果用别的系统,可能一个问题发到论坛上半个月都不会有人理。从内核源码的风格看,FreeBSD更干净整洁,注释到位,但是无奈它的市场份额远不如Linux,学习Linux是更好的技术投资。
  A.1.4可移植性重要吗
  写网络程序要不要考虑移植性?要不要跨平台?这取决于项目需要,如果贵公司做的程序要卖给其他公司,而对方可能使用Windows、Linux、FreeBSD、Solaris、AIX、HP-UX等等操作系统,这时候当然要考虑移植性。如果编写公司内部的服务器上用的网络程序,那么大可只关注一个平台,比如Linux。因为编写和维护可移植的网络程序的代价相当高,平台间的差异可能远比想象中大,即便是POSIX系统之间也有不小的差异(比如Linux没有SO_NOSIGPIPE选项,Linux的pipe(2)是单向的,而FreeBSD是双向的),错误的返回码也大不一样。
  我就不打算把mudu往Windows或其他操作系统移植。如果需要编写可移植的网络程序,我宁愿用libevent、libuv、JavaNetty这样现成的库,把“脏活、累活”留给别人。
  A.1.5网络编程的各种任务角色
  计算机网络是个bigtopic,涉及很多人物和角色,既有开发人员,也有运维人员。比方说:公司内部两台机器之间ping不通,通常由网络运维人员解决,看看是布线有问题还是路由器设置不对;两台机器能ping通,但是程序连不上,经检查是本机防火墙设置有问题,通常由系统管理员解决;两台机器能连上,但是丢包很严重,发现是网卡或者交换机的网口故障,由硬件维修人员解决;两台机器的程序能连上,但是偶尔发过去的请求得不到响应,通常是程序bug,应该由开发人员解决。
  本文主要关心开发人员这一角色。下面简单列出一些我能想到的跟网络打交道的编程任务,其中前三项是面向网络本身,后面几项是在计算机网络之上构建信息系统。
  1.开发网络设备,编写防火墙、交换机、路由器的固件(firmware)。
  2.开发或移植网卡的驱动。
  3.移植或维护TCP/IP协议栈(特别是在嵌入式系统上)。
  4.开发或维护标准的网络协议程序,HTTP、FTP、DNS、SMTP、POP3、NFS。
  5.开发标准网络协议的“附加品”,比如HAProxy、squid、varnish等Webloadbalancer。
  6.开发标准或非标准网络服务的客户端库,比如ZooKeeper客户端库、memcached客户端库。
  7.开发与公司业务直接相关的网络服务程序,比如即时聊天软件的后台服务器、网游服务器、金融交易系统、互联网企业用的分布式海量存储、微博发帖的内部广播通知等等。
  8.客户端程序中涉及网络的部分,比如邮件客户端中与POP3、SMTP通信的部分,以及网游的客户端程序中与服务器通信的部分。
  本文所指的“网络编程”专指第7项,即在TCP/IP协议之上开发业务软件。换句话说,不是用SocketsAPI开发mudu这样的网络库,而是用libevent、muduo、Netty、gevent这样现成的库开发业务软件,mudu自带的十几个示例程序是业务软件的代表。
  A.1.6面向业务的网络编程的特点
  与通用的网络服务器不同,面向公司业务的专用网络程序有其自身的特点。
  业务逻辑比较复杂,而且时常变化如果写一个HTTP服务器,在大致实现HTTP1.1标准之后,程序的主体功能一般不会有太大的变化,程序员会把时间放在性能调优和bug修复上。而开发针对公司业务的专用程序时,功能说明书(spec)很可能不如HTTP1.1标准那么细致明确。更重要的是,程序是快速演化的。以即时聊天工具的后台服务器为例,可能第一版只支持在线聊天;几个月之后发布第二版,支持离线消息;又过了几个月,第三版支持隐身聊天;随后,第四版支持上传头像;如此等等。这要求程序员能快速响应新的业务需求,公司才能保持竞争力。由于业务时常变化(假设每月一次版本升级),也会降低服务程序连续运行时间的要求。相反,我们要设计一套流程,通过轮流重启服务器来完成平滑升级(§9.2.2)。
  不一定需要遵循公认的通信协议标准比方说网游服务器就没什么协议标准,反正客户端和服务端都是本公司开发的,如果发现目前的协议设计有问题,两边一起改就行了。由于可以自己设计协议,因此我们可以绕开一些性能难点,简化程序结构。比方说,对于多线程的服务程序,如果用短连接TCP协议,为了优化性能通常要精心设计accept新连接的机制2,避免惊群并减少上下文切换。但是如果改用长连接,用最简单的单线程accept就行了。
  程序结构没有定论对于高并发大吞吐的标准网络服务,一般采用单线程事件驱动的方式开发,比如HAProxy、lighttpd等都是这个模式。但是对于专用的业务系统,其业务逻辑比较复杂,占用较多的CPU资源,这种单线程事件驱动方式不见得能发挥现在多核处理器的优势。这留给程序员比较大的自由发挥空间,做好了“横扫千军”,做烂了一败涂地。我认为目前oneloopperthread是通用性较高的一种程序结构,能发挥多核的优势,见§3.3和§6.6。
  性能评判的标准不同如果开发httpd这样的通用服务,必然会和开源的Nginx、lighttpd等高性能服务器比较,程序员要投入相当的精力去优化程序,才能在市场上占有一席之地。而面向业务的专用网络程序不一定是Ibound,也不一定有开源的实现以供对比性能,优化方向也可能不同。程序员通常更加注重功能的稳定性与开发的便捷性。性能只要一代比一代强即可。
  网络编程起到支撑作用,但不处于主导地位程序员的主要工作是实现业务逻辑,而不只是实现网络通信协议。这要求程序员深入理解业务。程序的性能瓶颈不一定在网络上,瓶颈有可能是CPU、DiskIO、数据库等,这时优化网络方面的代码并不能提高整体性能。只有对所在的领域有深入的了解,明白各种因素的权衡(trade-off),才能做出一些有针对性的优化。现在的机器上,简单的并发长连接echo服务程序不用特别优化就做到十多万qps,但是如果每个业务请求需要1ms密集计算,在8核机器上充其量能达到8000qps,优化I不如去优化业务计算(如果投入产出合算的话)。
  A.1.7几个术语
  互联网上的很多“口水战”是由对同一术语的不同理解引起的,比如我写的《多线程服务器的适用场合》3,就曾经被人说是“挂羊头卖狗肉”,因为这篇文章中举的master例子“根本就算不上是个网络服务器。因为它的瓶颈根本就跟网络无关。”
  网络服务器“网络服务器”这个术语确实含义模糊,到底指硬件还是软件?到底是服务于网络本身的机器(交换机、路由器、防火墙、NAT),还是利用网络为其他人或程序提供服务的机器(打印服务器、文件服务器、邮件服务器)?每个人根据自己熟悉的领域,可能会有不同的解读。比方说,或许有人认为只有支持高并发、高吞吐量的才算是网络服务器。
  为了避免无谓的争执,我只用“网络服务程序”或者“网络应用程序”这种含义明确的术语。“开发网络服务程序”通常不会造成误解。
  客户端?服务端?在TCP网络编程中,客户端和服务端很容易区分,主动发起连接的是客户端,被动接受连接的是服务端。当然,这个“客户端”本身也可能是个后台服务程序,HTTPproxy对HTTPserver来说就是个客户端。
  客户端编程?服务端编程?但是“服务端编程”和“客户端编程”就不那么好区分了。比如Webcrawler,它会主动发起大量连接,扮演的是HTTP客户端的角色,但似乎应该归入“服务端编程”。又比如写一个HTTPproxy,它既会扮演服务端--被动接受Webbrowser发起的连接,也会扮演客户端--主动向HTTPserver发起连接,它究竟算服务端还是客户端?我猜大多数人会把它归入服务端编程。
  那么究竟如何定义“服务端编程”?
  服务端编程需要处理大量并发连接?也许是,也许不是。比如云风在一篇介绍网游服务器的博客4中就谈到,网游中用到的“连接服务器”需要处理大量连接,而“逻辑服务器”只有一个外部连接。那么开发这种网游“逻辑服务器”算服务端编程还是客户端编程呢?又比如机房的服务进程监控软件,并发数跟机器数成正比,至多也就是两三千的并发连接。(再大规模就超出《Linux多线程服务端编程 使用muduo C++网络库》的范围了。)
  我认为,“服务端网络编程”指的是编写没有用户界面的长期运行的网络程序,程序默默地运行在一台服务器上,通过网络与其他程序打交道,而不必和人打交道。与之对应的是客户端网络程序,要么是短时间运行,比如wget;要么是有用户界面(无论是字符界面还是图形界面)。本文主要谈服务端网络编程。
  ……

前言/序言

  《Linux多线程服务端编程 使用muduo C++网络库》主要讲述采用现代C++在x86-64Linux上编写多线程TCP网络服务程序的主流常规技术,这也是我对过去5年编写生产环境下的多线程服务端程序的经验总结。《Linux多线程服务端编程 使用muduo C++网络库》重点讲解多线程网络服务器的一种IO模型,即oneloopperthread。这是一种适应性较强的模型,也是Linux下以native语言编写用户态高性能网络程序最成熟的模式,掌握之后可顺利地开发各类常见的服务端网络应用程序。《Linux多线程服务端编程 使用muduo C++网络库》以muduo网络库为例,讲解这种编程模型的使用方法及注意事项。
  muduo是一个基于非阻塞IO和事件驱动的现代C++网络库,原生支持oneloopperthread这种IO模型。muduo适合开发Linux下的面向业务的多线程服务端网络应用程序,其中“面向业务的网络编程”的定义见附录A。“现代C++”指的不是C++11新标准,而是2005年TR1发布之后的C++语言和库。与传统C++相比,现代C++的变化主要有两方面:资源管理(见第1章)与事件回调(见第449页)。
  《Linux多线程服务端编程 使用muduo C++网络库》不是多线程编程教程,也不是网络编程教程,更不是C++教程。读者应该已经大致读过《UNIX环境高级编程》、《UNIX网络编程》、《C++Primer》或与之内容相近的书籍。《Linux多线程服务端编程 使用muduo C++网络库》不谈C++11,因为目前(2012年)主流的Linux服务端发行版的g++版本都还停留在4.4,C++11进入实用尚需一段时日。
  《Linux多线程服务端编程 使用muduo C++网络库》适用的硬件环境是主流x86-64服务器,多路多核CPU、几十GB内存、千兆以太网互联。除了第5章讲诊断日志之外,《Linux多线程服务端编程 使用muduo C++网络库》不涉及文件IO。
  《Linux多线程服务端编程 使用muduo C++网络库》分为四大部分,第1部分“C++多线程系统编程”考察多线程下的对象生命期管理、线程同步方法、多线程与C++的结合、高效的多线程日志等。第2部分“muduo网络库”介绍使用现成的非阻塞网络库编写网络应用程序的方法,以及muduo的设计与实现。第3部分“工程实践经验谈”介绍分布式系统的工程化开发方法和C++在工程实践中的功能特性取舍。第4部分“附录”分享网络编程和C++语言的学习经验。
  《Linux多线程服务端编程 使用muduo C++网络库》的宗旨是贵精不贵多。掌握两种基本的同步原语就可以满足各种多线程同步的功能需求,还能写出更易用的同步设施。掌握一种进程间通信方式和一种多线程网络编程模型就足以应对日常开发任务,编写运行于公司内网环境的分布式服务系统。(《Linux多线程服务端编程 使用muduo C++网络库》不涉及分布式存储系统,也不涉及UDP。)
  术语与排版范例
  《Linux多线程服务端编程 使用muduo C++网络库》大量使用英文术语,甚至有少量英文引文。设计模式的名字一律用英文,例如Observer、Reactor、Singleton。在中文术语不够突出时,也会使用英文,例如class、heap、eventloop、STLalgorithm等。注意几个中文C++术语:对象实体(instance)、函数重载决议(resolution)、模板具现化(instantiation)、覆写(override)虚函数、提领(dereference)指针。《Linux多线程服务端编程 使用muduo C++网络库》中的英语可数名词一般不用复数形式,例如两个class,6个syscall;但有时会用(s)强调中文名词是复数。fd是文件描述符(filedescriptor)的缩写。“CPU数目”一般指的是核(core)的数目。容量单位kB、MB、GB表示的字节数分别为103、106、109,在特别强调准确数值时,会分别用KiB、MiB、GiB表示210、220、230字节。用诸如§11.5表示《Linux多线程服务端编程 使用muduo C++网络库》第11.5节,L42表示上下文中出现的第42行代码。[JCP]、[CC2e]等是参考文献,见书末清单。
  一般术语用普通罗马字体,如mutex、socket;C++关键字用无衬线字体,如class、this、mutable;函数名和class名用等宽字体,如fork(2)、muduo::EventLoop,其中fork(2)表示系统函数fork()的文档位于manpage第2节,可以通过man2fork命令查看。如果函数名或类名过长,可能会折行,行末有连字号“-”,如EventLoop-ThreadPool。文件路径和URL采用窄字体,例如muduo/base/Date.h、http://chenshuo.com。用中文楷体表示引述别人的话。
  代码
  《Linux多线程服务端编程 使用muduo C++网络库》的示例代码以开源项目的形式发布在GitHub上,地址是
  《Linux多线程服务端编程 使用muduo C++网络库》配套页面提供全部源代码打包下载,正文中出现的类似recipes/thread的路径是压缩包内的相对路径,读者不难找到其对应的GitHubURL。《Linux多线程服务端编程 使用muduo C++网络库》引用代码的形式如下,左侧数字是文件的行号,右侧的“muduo/base/Types.h”是文件路径1。例如下面这几行代码是muduo::string的typedef。
  muduo/base/Types.h
  15namespacemuduo
  16{
  17
  18#ifdefMUDUO_STD_STRING
  19usingstd::string;
  20#else//!MUDUO_STD_STRING
  21typedef__gnu_cxx::__sso_stringstring;
  22#endif
  muduo/base/Types.h
  在第6、7两章的muduo示例代码中,路径muduo/examples/XXX会简写为examples/XXX。此外,第8章会把recipes/reactor/XXX简写为reactor/XXX。
  《Linux多线程服务端编程 使用muduo C++网络库》假定读者熟悉diff-u命令的输出格式,用于表示代码的改动。
  《Linux多线程服务端编程 使用muduo C++网络库》正文中出现的代码有时为了照顾排版而略有改写,例如改变缩进规则,去掉单行条件语句前后的花括号等。就编程风格而论,应以电子版代码为准。
  陈硕
  中国·香港


其他推荐