《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效
《C++服务器开发精髓》[61M]百度网盘|pdf下载|亲测有效

C++服务器开发精髓 pdf下载

isbn:9787121412639
出版社 电子工业出版社
出版年 2021-07-01
页数 752页
ISBN 9787121412639
装帧 精装
评分 8.9(豆瓣)
限时特惠 00:00:00
活动结束后恢复原价
纸质书参考价 ¥23
电子版限时价 ¥5.99 省 18 元

选择版本

不满意全额退款
发货失败双倍赔偿
邮箱即时发送

内容简介

本篇主要提供C++服务器开发精髓电子书的pdf版本下载,本电子书下载方式为百度网盘方式,点击以上按钮下单完成后即会通过邮件和网页的方式发货,有问题请联系邮箱ebook666@outlook.com

产品特色

编辑推荐

适读人群 :无论是对于C/C++开发者、计算机专业的学生,还是对于想了解操作系统原理的读者,本书都极具参考价值。

## 这本书有什么内容?


本书详细介绍了除数据库外, 要成为一名合格的C++开发者需要掌握哪些知识。通过本书你将获得:

C++开发编译调试完整技术链;

C++ 11/14/17的常用特性和实用方法

多线程编程技术;

作者精心凝炼的二十多个网络编程重难点知识;

网络故障排查与定位知识;

如何设计可兼容可扩展的通信协议;

如何设计高性能网络框架;

如何设计高性能服务框架;

如何开发服务常用组件等。


每一章节都实实在在地剖析技术背后的原理,教你知其然更知其所以然。


## 这本书适合哪些读者?


如果你是个学生,将来想做C/C++后台开发,或者做了几年C/C++后台开发后,还没形成自己的技术体系或者技术认知,那么一定要阅读本书。除了数据库,其他部分都介绍得很详细。


本书的突出特点就是用了非常细粒度的示例,娓娓道来,将各个技术原理都解释清楚,详述哪些是重点和难点,且这些示例都来自实际开发。


如果你是非C/C++技术栈的读者,想了解自己所用的语言(如 Java/Go)的运行时在操作系统接口层面是如何实现的,那么本书也适合你。


内容简介

本书从操作系统原理角度讲解进行C++服务器开发所需掌握的技术栈。全书总计9章,第1~2章讲解C++ 11/14/17新标准中的常用特性、新增类库,以及C++开发者必须熟练掌握的开发调试工具链;第3~6章详细讲解C++服务器开发中的多线程编程技术、网络编程重难点知识、网络故障调试与排查常用工具,以及通信协议的设计思路、技巧;第7~8章详细讲解一个带网络通信组件的高性能服务的基本设计思路和注意事项;第9章进一步补充服务相关的常用模块设计思路和方法。本书秉承的思想是,通过掌握技术原理,可以轻松制造“轮子”,灵活设计出优雅、鲁棒的服务,并快速学习新技术。

无论是对于C/C++开发者、计算机专业的学生,还是对于想了解操作系统原理的读者,本书都极具参考价值。


作者简介

张远龙,微信公众号“高性能服务器开发”的作者,曾就职于银天下、携程、字节跳动等公司,做过金融交易系统、IM、呼叫中心等项目,研究方向为高性能高并发服务开发。


目录

第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. 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.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


第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.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.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.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


第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.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 协议与自动升级功能

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.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.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.4 错误码系统的设计... 730

9.4.1 错误码的作用... 730

9.4.2 错误码系统设计实践... 731

9.5 监控端口... 733


前言/序言

为什么写作本书

笔者自学生时代便开始接触 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/C++开发者、计算机专业的学生,还是对于想了解操作系统原理的读者,本书都极具参考价值。

## 这本书有什么内容?


本书详细介绍了除数据库外, 要成为一名合格的C++开发者需要掌握哪些知识。通过本书你将获得:

C++开发编译调试完整技术链;

C++ 11/14/17的常用特性和实用方法

多线程编程技术;

作者精心凝炼的二十多个网络编程重难点知识;

网络故障排查与定位知识;

如何设计可兼容可扩展的通信协议;

如何设计高性能网络框架;

如何设计高性能服务框架;

如何开发服务常用组件等。


每一章节都实实在在地剖析技术背后的原理,教你知其然更知其所以然。


## 这本书适合哪些读者?


如果你是个学生,将来想做C/C++后台开发,或者做了几年C/C++后台开发后,还没形成自己的技术体系或者技术认知,那么一定要阅读本书。除了数据库,其他部分都介绍得很详细。


本书的突出特点就是用了非常细粒度的示例,娓娓道来,将各个技术原理都解释清楚,详述哪些是重点和难点,且这些示例都来自实际开发。


如果你是非C/C++技术栈的读者,想了解自己所用的语言(如 Java/Go)的运行时在操作系统接口层面是如何实现的,那么本书也适合你。


内容简介

本书从操作系统原理角度讲解进行C++服务器开发所需掌握的技术栈。全书总计9章,第1~2章讲解C++ 11/14/17新标准中的常用特性、新增类库,以及C++开发者必须熟练掌握的开发调试工具链;第3~6章详细讲解C++服务器开发中的多线程编程技术、网络编程重难点知识、网络故障调试与排查常用工具,以及通信协议的设计思路、技巧;第7~8章详细讲解一个带网络通信组件的高性能服务的基本设计思路和注意事项;第9章进一步补充服务相关的常用模块设计思路和方法。本书秉承的思想是,通过掌握技术原理,可以轻松制造“轮子”,灵活设计出优雅、鲁棒的服务,并快速学习新技术。

无论是对于C/C++开发者、计算机专业的学生,还是对于想了解操作系统原理的读者,本书都极具参考价值。


作者简介

张远龙,微信公众号“高性能服务器开发”的作者,曾就职于银天下、携程、字节跳动等公司,做过金融交易系统、IM、呼叫中心等项目,研究方向为高性能高并发服务开发。


目录

第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. 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.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


第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.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.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.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


第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.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 协议与自动升级功能

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.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.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.4 错误码系统的设计... 730

9.4.1 错误码的作用... 730

9.4.2 错误码系统设计实践... 731

9.5 监控端口... 733


前言/序言

为什么写作本书

笔者自学生时代便开始接触 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章内容的补充,详细讲解一个服务的常用模块设计思路。


相关资源

本书提供源码下载、读者交流群等服务,详情请参见本书封底的读者服务信息。

若想获取关于高性能服务器开发的更多知识,可以关注笔者的两个微信公众号:“高性能服务器开发”和“程序员小方”。


致谢

感谢笔者的妻子承担家务及照顾笔者的生活,让笔者可以集中精力写作本书。

感谢各位同事帮助笔者成长与提高。

感谢王旭东等同学为本书的校对和勘误做出贡献,感谢“高性能服务器开发”群内小伙伴们的支持。

感谢电子工业出版社工作严谨、高效的张国霞编辑,她在成书过程中对笔者的指导、协助和鞭策,是本书得以完成的重要助力。