├── README.md
├── SUMMARY.md
├── _book
├── chapter1
│ ├── 1.1_课程介绍.html
│ ├── 1.2_项目介绍.html
│ └── index.html
├── chapter10
│ ├── 10.1_ini配置.html
│ ├── 10.2_xml配置.html
│ ├── 10.3_lua配置.html
│ └── index.html
├── chapter2
│ ├── 2.1_安装和配置linux系统.html
│ ├── 2.2_linux命令.html
│ ├── 2.3_安装开发工具.html
│ └── index.html
├── chapter3
│ ├── 3.1_如何架构设计.html
│ ├── 3.1_如何管理需求.html
│ ├── 3.3_共享单车架构设计.html
│ └── index.html
├── chapter4
│ ├── 4.1_敏捷概述.html
│ ├── 4.2_SCRUM介绍.html
│ ├── 4.3_我们应该认识到.html
│ └── index.html
├── chapter5
│ ├── 5.1_lua语言介绍.html
│ ├── 5.2_gtest单元测试.html
│ ├── 5.3_搭建持续集成环境.html
│ └── index.html
├── chapter6
│ └── index.html
├── chapter7
│ ├── 7.1_什么是shell.html
│ ├── 7.2_Shell编程.html
│ ├── 7.3_启动脚本实例.html
│ └── index.html
├── chapter8
│ ├── 8.1_编译原理.html
│ ├── 8.2_makefile.html
│ ├── 8.3_cmake.html
│ └── index.html
├── chapter9
│ ├── 9.1_gdb调试.html
│ ├── 9.2_coredump.html
│ ├── 9.3_内存泄漏检查.html
│ └── index.html
├── gitbook
│ ├── app.js
│ ├── fonts
│ │ └── fontawesome
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ └── fontawesome-webfont.woff
│ ├── images
│ │ ├── apple-touch-icon-precomposed-152.png
│ │ └── favicon.ico
│ ├── plugins
│ │ ├── gitbook-plugin-fontsettings
│ │ │ ├── buttons.js
│ │ │ └── website.css
│ │ ├── gitbook-plugin-highlight
│ │ │ ├── ebook.css
│ │ │ └── website.css
│ │ ├── gitbook-plugin-livereload
│ │ │ └── plugin.js
│ │ ├── gitbook-plugin-search
│ │ │ ├── lunr.min.js
│ │ │ ├── search.css
│ │ │ └── search.js
│ │ └── gitbook-plugin-sharing
│ │ │ └── buttons.js
│ └── style.css
├── images
│ ├── 5.1.0.png
│ └── 5.4.1.1.png
├── index.html
└── search_index.json
├── book.json
├── chapter1
├── 1.1_课程介绍.md
├── 1.2_项目介绍.md
└── README.md
├── chapter10
├── 10.1_ini配置.md
├── 10.2_xml配置.md
├── 10.3_lua配置.md
├── 10_1.png
└── README.md
├── chapter11
├── 11.1_重定向.md
├── 11.2_log4cpp.md
├── 11.3_大型项目的日志架构.md
├── 11_1.png
├── 11_2.png
├── README.md
├── elk_stack.png
└── log_web.jpg
├── chapter12
├── 12.1_进程的概念.md
├── 12.2_如何产生一个进程.md
└── 12.3_僵尸孤儿守护进程.md
├── chapter14
├── 14.1_俗话网络通信.md
├── 14.2_网络7层协议与4层协议.md
├── 14_1.png
├── 14_10.png
├── 14_11.png
├── 14_2.png
├── 14_3.png
├── 14_4.png
├── 14_5.png
├── 14_6.png
├── 14_7.png
├── 14_8.png
├── 14_9.png
└── README.md
├── chapter15
├── 15.1_socket接口介绍.md
├── 15.2_socket选项介绍.md
├── 15.3_socket编程实现.md
├── 15.4_TCP原理介绍.md
├── 15_5.png
├── 15_6.png
├── 15_7.png
├── 15_8.png
└── 15_9.png
├── chapter16
├── 16.1_UDP协议介绍.md
├── 16.2_UDP的实现.md
├── 16.3_UDP的connect的意义.md
├── 16_1.jpg
└── README.md
├── chapter17
├── 17.1_什么是长连接和短连接.md
├── 17.2_keepalive机制.md
└── README.md
├── chapter18
├── 15_4.jpg
├── 18.1_滑动窗口.md
├── 18.2_MTU.md
├── 18.3_拥塞控制.md
├── 18.4_BBR算法.md
├── 18_1.png
├── 18_2.png
├── 18_3.png
├── 18_5.png
├── 18_6.png
├── 18_7.png
├── 18_8.png
└── README.md
├── chapter19
├── 19.1_http协议介绍.md
├── 19.2_http应用.md
├── 19.3_短信验证码.md
├── 19_1.png
├── 19_2.jpg
├── 19_3.png
└── README.md
├── chapter2
├── 2.1_安装和配置linux系统.md
├── 2.2_linux命令.md
├── 2.3_安装开发工具.md
├── 2.4_搭建开发环境.md
└── README.md
├── chapter20
├── 20.3_protobuf序列化.md
├── 20_pb1.png
├── brks接口文档.docx
├── 协议设计.vsdx
└── 课程笔记.docx
├── chapter21
├── 多路复用的课堂笔记.docx
└── 阻塞非阻塞异步同步笔记.docx
├── chapter22
├── libevent.docx
└── libevent.ppt
├── chapter23
└── 23.1_创建线程和相关属性.md
├── chapter24
└── 线程池的设计.docx
├── chapter3
├── 3.1_如何架构设计.md
├── 3.1_如何管理需求.md
├── 3.3_共享单车架构设计.md
└── README.md
├── chapter4
├── 4.1_敏捷概述.md
├── 4.2_SCRUM介绍.md
├── 4.3_我们应该认识到.md
└── README.md
├── chapter5
├── 5.1_lua语言介绍.md
├── 5.2_1.png
├── 5.2_gtest单元测试.md
├── 5.3_1.jpg
├── 5.3_搭建持续集成环境.md
├── README.md
└── _book
│ ├── 5.1_lua语言介绍.md
│ ├── 5.2_gtest单元测试.md
│ ├── 5.3_搭建持续集成环境.md
│ ├── gitbook
│ ├── fonts
│ │ └── fontawesome
│ │ │ ├── FontAwesome.otf
│ │ │ ├── fontawesome-webfont.eot
│ │ │ ├── fontawesome-webfont.svg
│ │ │ ├── fontawesome-webfont.ttf
│ │ │ ├── fontawesome-webfont.woff
│ │ │ └── fontawesome-webfont.woff2
│ ├── gitbook-plugin-fontsettings
│ │ ├── fontsettings.js
│ │ └── website.css
│ ├── gitbook-plugin-highlight
│ │ ├── ebook.css
│ │ └── website.css
│ ├── gitbook-plugin-livereload
│ │ └── plugin.js
│ ├── gitbook-plugin-lunr
│ │ ├── lunr.min.js
│ │ └── search-lunr.js
│ ├── gitbook-plugin-search
│ │ ├── lunr.min.js
│ │ ├── search-engine.js
│ │ ├── search.css
│ │ └── search.js
│ ├── gitbook-plugin-sharing
│ │ └── buttons.js
│ ├── gitbook.js
│ ├── images
│ │ ├── apple-touch-icon-precomposed-152.png
│ │ └── favicon.ico
│ ├── style.css
│ └── theme.js
│ ├── index.html
│ └── search_index.json
├── chapter6
└── README.md
├── chapter7
├── 7.1_什么是shell.md
├── 7.2_Shell编程.md
├── 7.3_启动脚本实例.md
└── README.md
├── chapter8
├── 8.1_编译原理.md
├── 8.2_makefile.md
├── 8.3_cmake.md
├── 8.4_GNU_make.md
├── 8_1.png
├── 8_2.png
├── 8_3.png
└── README.md
├── chapter9
├── 9.1_gdb调试.md
├── 9.2_coredump.md
├── 9.3_内存泄漏检查.md
├── 9_1.png
├── 9_2.png
├── 9_3.png
├── 9_4.png
└── README.md
├── images
├── 5.1.0.png
└── 5.4.1.1.png
└── 服务器开发教程.pdf
/README.md:
--------------------------------------------------------------------------------
1 | # 课程介绍
2 |
3 | 我们拥有强大的团队去打造互联网最好的后台开发课程体系和师资力量,自信来源于专注和责任!!!
4 | 如果你有任何问题请联系Lee 哥:
5 | email: lizhiyong4360@gmail.com
6 |
7 | * [Introduction](README.md)
8 | * [课程介绍和项目介绍](chapter1/README.md)
9 | * [课程介绍](chapter1/1.1_课程介绍.md)
10 | * [项目介绍](chapter1/1.2_项目介绍.md)
11 | * [搭建开发环境](chapter2/README.md)
12 | * [安装和配置linux系统](chapter2/2.1_安装和配置linux系统.md)
13 | * [linux命令](chapter2/2.2_linux命令.md)
14 | * [安装开发工具](chapter2/2.3_安装开发工具.md)
15 | * [项目文档](chapter3/README.md)
16 | * [如何管理需求](chapter3/3.1_如何管理需求.md)
17 | * [如何架构设计](chapter3/3.1_如何架构设计.md)
18 | * [共享单车的架构](chapter3/3.3_共享单车架构设计.md)
19 | * [敏捷开发](chapter4/README.md)
20 | * [敏捷概述](chapter4/4.1_敏捷概述.md)
21 | * [SCRUM介绍](chapter4/4.2_SCRUM介绍.md)
22 | * [我们应该认识到](chapter4/4.3_我们应该认识到.md)
23 | * [持续集成](chapter5/README.md)
24 | * [lua语言介绍](chapter5/5.1_lua语言介绍.md)
25 | * [gtest单元测试](chapter5/5.2_gtest单元测试.md)
26 | * [搭建持续集成环境](chapter5/5.3_搭建持续集成环境.md)
27 | * [产品版本管理之git](chapter6/README.md)
28 | * [shell脚本](chapter7/README.md)
29 | * [什么是shell](chapter7/7.1_什么是shell.md)
30 | * [Shell编程](chapter7/7.2_Shell编程.md)
31 | * [启动脚本实例](chapter7/7.3_启动脚本实例.md)
32 | * [项目编译](chapter8/README.md)
33 | * [编译原理](chapter8/8.1_编译原理.md)
34 | * [makefile](chapter8/8.2_makefile.md)
35 | * [cmake](chapter8/8.3_cmake.md)
36 | * [GNU make](chapter8/8.4_GNU_make.md)
37 | * [调试技巧](chapter9/README.md)
38 | * [gdb调试](chapter9/9.1_gdb调试.md)
39 | * [coredump](chapter9/9.2_coredump.md)
40 | * [内存泄漏检查](chapter9/9.3_内存泄漏检查.md)
41 | * [项目配置文件](chapter10/README.md)
42 | * [ini配置](chapter10/10.1_ini配置.md)
43 | * [xml配置](chapter10/10.2_xml配置.md)
44 | * [lua配置](chapter10/10.3_lua配置.md)
45 | * [项目日志](chapter11/README.md)
46 | * [重定向](chapter11/11.1_重定向.md)
47 | * [log4cpp](chapter11/11.2_log4cpp.md)
48 | * [分布式系统的集中式日志解决方案](chapter11/11.3_大型项目的日志架构.md)
49 | * [进程](chapter12/README.md)
50 | * [进程的概念](chapter12/12.1_进程的概念.md)
51 | * [如何产生一个进程](chapter12/12.2_如何产生一个进程.md)
52 | * [僵尸、孤儿和守护进程](chapter12/12.3_僵尸孤儿守护进程.md)
53 | * [进程间通信](chapter13/README.md)
54 | * [管道与命名管道](chapter13/13.1_管道与命名管道.md)
55 | * [共享内存](chapter13/13.2_共享内存.md)
56 | * [信号量](chapter13/13.3_信号量.md)
57 | * [信号](chapter13/13.4_信号.md)
58 | * [网络协议](chapter14/README.md)
59 | * [俗话网络通信](chapter14/14.1_俗话网络通信.md)
60 | * [网络7层协议与4层协议](chapter14/14.2_网络7层协议与4层协议.md)
61 | * [tcp通信]
62 | * [socket接口介绍](chapter15/15.1_socket接口介绍.md)
63 | * [socket选项介绍](chapter15/15.2_socket选项介绍.md)
64 | * [socket编程实现](chapter15/15.3_socket编程实现.md)
65 | * [TCP原理介绍](chapter15/15.4_TCP原理介绍.md)
66 | * [udp通信](chapter16/README.md)
67 | * [UDP协议介绍](chapter16/16.1_UDP协议介绍.md)
68 | * [UDP的实现](chapter16/16.2_UDP的实现.md)
69 | * [UDP的connect的意义](chapter16/16.3_UDP的connect的意义.md)
70 | * [UDP的分包和组包](chapter16/16.4_UDP的分包和组包)
71 | * [TCP长连接和短连接](chapter17/README.md)
72 | * [什么是长连接和短连接](chapter17/17.1_什么是长连接和短连接.md)
73 | * [keepalive机制](chapter17/17.2_keepalive机制.md)
74 | * [如何正确维持一个长连接](chapter17/17.3_如何正确维持一个长连接.md)
75 | * [TCP流量控制](chapter18/README.md)
76 | * [滑动窗口](chapter18/18.1_滑动窗口.md)
77 | * [MTU](chapter18/18.2_MTU.md)
78 | * [拥塞控制](chapter18/18.3_拥塞控制.md)
79 | * [BBR算法](chapter18/18.4_BBR算法.md)
80 | * [http协议介绍](chapter19/README.md)
81 | * [http协议介绍](chapter19/19.1_http协议介绍.md)
82 | * [http应用](chapter19/19.2_http应用.md)
83 | * [短信验证码](chapter19/19.3_短信验证码.md)
84 | * [应用层协议设计和序列号](chapter20/README.md)
85 | * [应用层协议设计](chapter20/20.1_应用层协议设计.md)
86 | * [json序列化](chapter20/20.2_json序列化.md)
87 | * [protobuf序列化](chapter20/20.3_protobuf序列化.md)
88 | * [flatbuffer序列化](chapter20/20.4_flatbuffer序列化.md)
89 | * [网络IO模型](chapter21/README.md)
90 | * [libevent](chapter22/README.md)
--------------------------------------------------------------------------------
/SUMMARY.md:
--------------------------------------------------------------------------------
1 | # Summary
2 |
3 | * [Introduction](README.md)
4 | * [课程介绍和项目介绍](chapter1/README.md)
5 | * [课程介绍](chapter1/1.1_课程介绍.md)
6 | * [项目介绍](chapter1/1.2_项目介绍.md)
7 | * [搭建开发环境](chapter2/README.md)
8 | * [安装和配置linux系统](chapter2/2.1_安装和配置linux系统.md)
9 | * [linux命令](chapter2/2.2_linux命令.md)
10 | * [安装开发工具](chapter2/2.3_安装开发工具.md)
11 | * [搭建开发环境](chapter2/2.4_搭建开发环境.md)
12 | * [项目文档](chapter3/README.md)
13 | * [如何管理需求](chapter3/3.1_如何管理需求.md)
14 | * [如何架构设计](chapter3/3.1_如何架构设计.md)
15 | * [共享单车的架构](chapter3/3.3_共享单车架构设计.md)
16 | * [敏捷开发](chapter4/README.md)
17 | * [敏捷概述](chapter4/4.1_敏捷概述.md)
18 | * [SCRUM介绍](chapter4/4.2_SCRUM介绍.md)
19 | * [我们应该认识到](chapter4/4.3_我们应该认识到.md)
20 | * [持续集成](chapter5/README.md)
21 | * [lua语言介绍](chapter5/5.1_lua语言介绍.md)
22 | * [gtest单元测试](chapter5/5.2_gtest单元测试.md)
23 | * [搭建持续集成环境](chapter5/5.3_搭建持续集成环境.md)
24 | * [产品版本管理之git](chapter6/README.md)
25 | * [shell脚本](chapter7/README.md)
26 | * [什么是shell](chapter7/7.1_什么是shell.md)
27 | * [Shell编程](chapter7/7.2_Shell编程.md)
28 | * [启动脚本实例](chapter7/7.3_启动脚本实例.md)
29 | * [项目编译](chapter8/README.md)
30 | * [编译原理](chapter8/8.1_编译原理.md)
31 | * [makefile](chapter8/8.2_makefile.md)
32 | * [cmake](chapter8/8.3_cmake.md)
33 | * [GNU make](chapter8/8.4_GNU_make.md)
34 | * [调试技巧](chapter9/README.md)
35 | * [gdb调试](chapter9/9.1_gdb调试.md)
36 | * [coredump](chapter9/9.2_coredump.md)
37 | * [内存泄漏检查](chapter9/9.3_内存泄漏检查.md)
38 | * [项目配置文件](chapter10/README.md)
39 | * [ini配置](chapter10/10.1_ini配置.md)
40 | * [xml配置](chapter10/10.2_xml配置.md)
41 | * [lua配置](chapter10/10.3_lua配置.md)
42 | * [项目日志](chapter11/README.md)
43 | * [重定向](chapter11/11.1_重定向.md)
44 | * [log4cpp](chapter11/11.2_log4cpp.md)
45 | * [分布式系统的集中式日志解决方案](chapter11/11.3_大型项目的日志架构.md)
46 | * [进程](chapter12/README.md)
47 | * [进程的概念](chapter12/12.1_进程的概念.md)
48 | * [如何产生一个进程](chapter12/12.2_如何产生一个进程.md)
49 | * [僵尸、孤儿和守护进程](chapter12/12.3_僵尸孤儿守护进程.md)
50 | * [进程间通信](chapter13/README.md)
51 | * [管道与命名管道](chapter13/13.1_管道与命名管道.md)
52 | * [共享内存](chapter13/13.2_共享内存.md)
53 | * [信号量](chapter13/13.3_信号量.md)
54 | * [信号](chapter13/13.4_信号.md)
55 | * [网络协议](chapter14/README.md)
56 | * [俗话网络通信](chapter14/14.1_俗话网络通信.md)
57 | * [网络7层协议与4层协议](chapter14/14.2_网络7层协议与4层协议.md)
58 | * [tcp通信]
59 | * [socket接口介绍](chapter15/15.1_socket接口介绍.md)
60 | * [socket选项介绍](chapter15/15.2_socket选项介绍.md)
61 | * [socket编程实现](chapter15/15.3_socket编程实现.md)
62 | * [TCP原理介绍](chapter15/15.4_TCP原理介绍.md)
63 | * [udp通信](chapter16/README.md)
64 | * [UDP协议介绍](chapter16/16.1_UDP协议介绍.md)
65 | * [UDP的实现](chapter16/16.2_UDP的实现.md)
66 | * [UDP的connect的意义](chapter16/16.3_UDP的connect的意义.md)
67 | * [UDP的分包和组包](chapter16/16.4_UDP的分包和组包)
68 | * [TCP长连接和短连接](chapter17/README.md)
69 | * [什么是长连接和短连接](chapter17/17.1_什么是长连接和短连接.md)
70 | * [keepalive机制](chapter17/17.2_keepalive机制.md)
71 | * [如何正确维持一个长连接](chapter17/17.3_如何正确维持一个长连接.md)
72 | * [TCP流量控制](chapter18/README.md)
73 | * [滑动窗口](chapter18/18.1_滑动窗口.md)
74 | * [MTU](chapter18/18.2_MTU.md)
75 | * [拥塞控制](chapter18/18.3_拥塞控制.md)
76 | * [BBR算法](chapter18/18.4_BBR算法.md)
77 | * [http协议介绍](chapter19/README.md)
78 | * [http协议介绍](chapter19/19.1_http协议介绍.md)
79 | * [http应用](chapter19/19.2_http应用.md)
80 | * [短信验证码](chapter19/19.3_短信验证码.md)
81 | * [应用层协议设计和序列号](chapter20/README.md)
82 | * [应用层协议设计](chapter20/20.1_应用层协议设计.md)
83 | * [json序列化](chapter20/20.2_json序列化.md)
84 | * [protobuf序列化](chapter20/20.3_protobuf序列化.md)
85 | * [flatbuffer序列化](chapter20/20.4_flatbuffer序列化.md)
86 | * [网络IO模型](chapter21/README.md)
87 | * [libevent](chapter22/README.md)
88 | * [多线程编程](chapter23/README.md)
89 | * [创建线程和相关属性](chapter23/23.1_创建线程和相关属性.md)
90 | * [线程的前世今生](chapter23/23.2_线程的前世今生.md)
91 | * [线程的同步]
--------------------------------------------------------------------------------
/_book/gitbook/fonts/fontawesome/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/fonts/fontawesome/FontAwesome.otf
--------------------------------------------------------------------------------
/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/_book/gitbook/images/apple-touch-icon-precomposed-152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/images/apple-touch-icon-precomposed-152.png
--------------------------------------------------------------------------------
/_book/gitbook/images/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/gitbook/images/favicon.ico
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-fontsettings/buttons.js:
--------------------------------------------------------------------------------
1 | require(["gitbook", "lodash", "jQuery"], function(gitbook, _, $) {
2 | var fontState;
3 |
4 | var THEMES = {
5 | "white": 0,
6 | "sepia": 1,
7 | "night": 2
8 | };
9 |
10 | var FAMILY = {
11 | "serif": 0,
12 | "sans": 1
13 | };
14 |
15 | // Save current font settings
16 | function saveFontSettings() {
17 | gitbook.storage.set("fontState", fontState);
18 | update();
19 | }
20 |
21 | // Increase font size
22 | function enlargeFontSize(e) {
23 | e.preventDefault();
24 | if (fontState.size >= 4) return;
25 |
26 | fontState.size++;
27 | saveFontSettings();
28 | };
29 |
30 | // Decrease font size
31 | function reduceFontSize(e) {
32 | e.preventDefault();
33 | if (fontState.size <= 0) return;
34 |
35 | fontState.size--;
36 | saveFontSettings();
37 | };
38 |
39 | // Change font family
40 | function changeFontFamily(index, e) {
41 | e.preventDefault();
42 |
43 | fontState.family = index;
44 | saveFontSettings();
45 | };
46 |
47 | // Change type of color
48 | function changeColorTheme(index, e) {
49 | e.preventDefault();
50 |
51 | var $book = $(".book");
52 |
53 | if (fontState.theme !== 0)
54 | $book.removeClass("color-theme-"+fontState.theme);
55 |
56 | fontState.theme = index;
57 | if (fontState.theme !== 0)
58 | $book.addClass("color-theme-"+fontState.theme);
59 |
60 | saveFontSettings();
61 | };
62 |
63 | function update() {
64 | var $book = gitbook.state.$book;
65 |
66 | $(".font-settings .font-family-list li").removeClass("active");
67 | $(".font-settings .font-family-list li:nth-child("+(fontState.family+1)+")").addClass("active");
68 |
69 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, '');
70 | $book.addClass("font-size-"+fontState.size);
71 | $book.addClass("font-family-"+fontState.family);
72 |
73 | if(fontState.theme !== 0) {
74 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, '');
75 | $book.addClass("color-theme-"+fontState.theme);
76 | }
77 | };
78 |
79 | function init(config) {
80 | var $bookBody, $book;
81 |
82 | //Find DOM elements.
83 | $book = gitbook.state.$book;
84 | $bookBody = $book.find(".book-body");
85 |
86 | // Instantiate font state object
87 | fontState = gitbook.storage.get("fontState", {
88 | size: config.size || 2,
89 | family: FAMILY[config.family || "sans"],
90 | theme: THEMES[config.theme || "white"]
91 | });
92 |
93 | update();
94 | };
95 |
96 |
97 | gitbook.events.bind("start", function(e, config) {
98 | var opts = config.fontsettings;
99 |
100 | // Create buttons in toolbar
101 | gitbook.toolbar.createButton({
102 | icon: 'fa fa-font',
103 | label: 'Font Settings',
104 | className: 'font-settings',
105 | dropdown: [
106 | [
107 | {
108 | text: 'A',
109 | className: 'font-reduce',
110 | onClick: reduceFontSize
111 | },
112 | {
113 | text: 'A',
114 | className: 'font-enlarge',
115 | onClick: enlargeFontSize
116 | }
117 | ],
118 | [
119 | {
120 | text: 'Serif',
121 | onClick: _.partial(changeFontFamily, 0)
122 | },
123 | {
124 | text: 'Sans',
125 | onClick: _.partial(changeFontFamily, 1)
126 | }
127 | ],
128 | [
129 | {
130 | text: 'White',
131 | onClick: _.partial(changeColorTheme, 0)
132 | },
133 | {
134 | text: 'Sepia',
135 | onClick: _.partial(changeColorTheme, 1)
136 | },
137 | {
138 | text: 'Night',
139 | onClick: _.partial(changeColorTheme, 2)
140 | }
141 | ]
142 | ]
143 | });
144 |
145 |
146 | // Init current settings
147 | init(opts);
148 | });
149 | });
150 |
151 |
152 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-fontsettings/website.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Theme 1
3 | */
4 | .color-theme-1 .dropdown-menu {
5 | background-color: #111111;
6 | border-color: #7e888b;
7 | }
8 | .color-theme-1 .dropdown-menu .dropdown-caret .caret-inner {
9 | border-bottom: 9px solid #111111;
10 | }
11 | .color-theme-1 .dropdown-menu .buttons {
12 | border-color: #7e888b;
13 | }
14 | .color-theme-1 .dropdown-menu .button {
15 | color: #afa790;
16 | }
17 | .color-theme-1 .dropdown-menu .button:hover {
18 | color: #73553c;
19 | }
20 | /*
21 | * Theme 2
22 | */
23 | .color-theme-2 .dropdown-menu {
24 | background-color: #2d3143;
25 | border-color: #272a3a;
26 | }
27 | .color-theme-2 .dropdown-menu .dropdown-caret .caret-inner {
28 | border-bottom: 9px solid #2d3143;
29 | }
30 | .color-theme-2 .dropdown-menu .buttons {
31 | border-color: #272a3a;
32 | }
33 | .color-theme-2 .dropdown-menu .button {
34 | color: #62677f;
35 | }
36 | .color-theme-2 .dropdown-menu .button:hover {
37 | color: #f4f4f5;
38 | }
39 | .book .book-header .font-settings .font-enlarge {
40 | line-height: 30px;
41 | font-size: 1.4em;
42 | }
43 | .book .book-header .font-settings .font-reduce {
44 | line-height: 30px;
45 | font-size: 1em;
46 | }
47 | .book.color-theme-1 .book-body {
48 | color: #704214;
49 | background: #f3eacb;
50 | }
51 | .book.color-theme-1 .book-body .page-wrapper .page-inner section {
52 | background: #f3eacb;
53 | }
54 | .book.color-theme-2 .book-body {
55 | color: #bdcadb;
56 | background: #1c1f2b;
57 | }
58 | .book.color-theme-2 .book-body .page-wrapper .page-inner section {
59 | background: #1c1f2b;
60 | }
61 | .book.font-size-0 .book-body .page-inner section {
62 | font-size: 1.2rem;
63 | }
64 | .book.font-size-1 .book-body .page-inner section {
65 | font-size: 1.4rem;
66 | }
67 | .book.font-size-2 .book-body .page-inner section {
68 | font-size: 1.6rem;
69 | }
70 | .book.font-size-3 .book-body .page-inner section {
71 | font-size: 2.2rem;
72 | }
73 | .book.font-size-4 .book-body .page-inner section {
74 | font-size: 4rem;
75 | }
76 | .book.font-family-0 {
77 | font-family: Georgia, serif;
78 | }
79 | .book.font-family-1 {
80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
81 | }
82 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal {
83 | color: #704214;
84 | }
85 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a {
86 | color: inherit;
87 | }
88 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
89 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2,
90 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3,
91 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4,
92 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5,
93 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
94 | color: inherit;
95 | }
96 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
97 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 {
98 | border-color: inherit;
99 | }
100 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
101 | color: inherit;
102 | }
103 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr {
104 | background-color: inherit;
105 | }
106 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote {
107 | border-color: inherit;
108 | }
109 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre,
110 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code {
111 | background: #fdf6e3;
112 | color: #657b83;
113 | border-color: #f8df9c;
114 | }
115 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight {
116 | background-color: inherit;
117 | }
118 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th,
119 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td {
120 | border-color: #f5d06c;
121 | }
122 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr {
123 | color: inherit;
124 | background-color: #fdf6e3;
125 | border-color: #444444;
126 | }
127 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
128 | background-color: #fbeecb;
129 | }
130 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal {
131 | color: #bdcadb;
132 | }
133 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a {
134 | color: #3eb1d0;
135 | }
136 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
137 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2,
138 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3,
139 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4,
140 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5,
141 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
142 | color: #fffffa;
143 | }
144 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
145 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 {
146 | border-color: #373b4e;
147 | }
148 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
149 | color: #373b4e;
150 | }
151 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr {
152 | background-color: #373b4e;
153 | }
154 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote {
155 | border-color: #373b4e;
156 | }
157 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre,
158 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code {
159 | color: #9dbed8;
160 | background: #2d3143;
161 | border-color: #2d3143;
162 | }
163 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight {
164 | background-color: #282a39;
165 | }
166 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th,
167 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td {
168 | border-color: #3b3f54;
169 | }
170 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr {
171 | color: #b6c2d2;
172 | background-color: #2d3143;
173 | border-color: #3b3f54;
174 | }
175 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
176 | background-color: #35394b;
177 | }
178 | .book.color-theme-1 .book-header {
179 | color: #afa790;
180 | background: transparent;
181 | }
182 | .book.color-theme-1 .book-header .btn {
183 | color: #afa790;
184 | }
185 | .book.color-theme-1 .book-header .btn:hover {
186 | color: #73553c;
187 | background: none;
188 | }
189 | .book.color-theme-1 .book-header h1 {
190 | color: #704214;
191 | }
192 | .book.color-theme-2 .book-header {
193 | color: #7e888b;
194 | background: transparent;
195 | }
196 | .book.color-theme-2 .book-header .btn {
197 | color: #3b3f54;
198 | }
199 | .book.color-theme-2 .book-header .btn:hover {
200 | color: #fffff5;
201 | background: none;
202 | }
203 | .book.color-theme-2 .book-header h1 {
204 | color: #bdcadb;
205 | }
206 | .book.color-theme-1 .book-body .navigation {
207 | color: #afa790;
208 | }
209 | .book.color-theme-1 .book-body .navigation:hover {
210 | color: #73553c;
211 | }
212 | .book.color-theme-2 .book-body .navigation {
213 | color: #383f52;
214 | }
215 | .book.color-theme-2 .book-body .navigation:hover {
216 | color: #fffff5;
217 | }
218 | /*
219 | * Theme 1
220 | */
221 | .book.color-theme-1 .book-summary {
222 | color: #afa790;
223 | background: #111111;
224 | border-right: 1px solid rgba(0, 0, 0, 0.07);
225 | }
226 | .book.color-theme-1 .book-summary .book-search {
227 | background: transparent;
228 | }
229 | .book.color-theme-1 .book-summary .book-search input,
230 | .book.color-theme-1 .book-summary .book-search input:focus {
231 | border: 1px solid transparent;
232 | }
233 | .book.color-theme-1 .book-summary ul.summary li.divider {
234 | background: #7e888b;
235 | box-shadow: none;
236 | }
237 | .book.color-theme-1 .book-summary ul.summary li i.fa-check {
238 | color: #33cc33;
239 | }
240 | .book.color-theme-1 .book-summary ul.summary li.done > a {
241 | color: #877f6a;
242 | }
243 | .book.color-theme-1 .book-summary ul.summary li a,
244 | .book.color-theme-1 .book-summary ul.summary li span {
245 | color: #877f6a;
246 | background: transparent;
247 | font-weight: normal;
248 | }
249 | .book.color-theme-1 .book-summary ul.summary li.active > a,
250 | .book.color-theme-1 .book-summary ul.summary li a:hover {
251 | color: #704214;
252 | background: transparent;
253 | font-weight: normal;
254 | }
255 | /*
256 | * Theme 2
257 | */
258 | .book.color-theme-2 .book-summary {
259 | color: #bcc1d2;
260 | background: #2d3143;
261 | border-right: none;
262 | }
263 | .book.color-theme-2 .book-summary .book-search {
264 | background: transparent;
265 | }
266 | .book.color-theme-2 .book-summary .book-search input,
267 | .book.color-theme-2 .book-summary .book-search input:focus {
268 | border: 1px solid transparent;
269 | }
270 | .book.color-theme-2 .book-summary ul.summary li.divider {
271 | background: #272a3a;
272 | box-shadow: none;
273 | }
274 | .book.color-theme-2 .book-summary ul.summary li i.fa-check {
275 | color: #33cc33;
276 | }
277 | .book.color-theme-2 .book-summary ul.summary li.done > a {
278 | color: #62687f;
279 | }
280 | .book.color-theme-2 .book-summary ul.summary li a,
281 | .book.color-theme-2 .book-summary ul.summary li span {
282 | color: #c1c6d7;
283 | background: transparent;
284 | font-weight: 600;
285 | }
286 | .book.color-theme-2 .book-summary ul.summary li.active > a,
287 | .book.color-theme-2 .book-summary ul.summary li a:hover {
288 | color: #f4f4f5;
289 | background: #252737;
290 | font-weight: 600;
291 | }
292 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-highlight/ebook.css:
--------------------------------------------------------------------------------
1 | pre,
2 | code {
3 | /* http://jmblog.github.io/color-themes-for-highlightjs */
4 | /* Tomorrow Comment */
5 | /* Tomorrow Red */
6 | /* Tomorrow Orange */
7 | /* Tomorrow Yellow */
8 | /* Tomorrow Green */
9 | /* Tomorrow Aqua */
10 | /* Tomorrow Blue */
11 | /* Tomorrow Purple */
12 | }
13 | pre .hljs-comment,
14 | code .hljs-comment,
15 | pre .hljs-title,
16 | code .hljs-title {
17 | color: #8e908c;
18 | }
19 | pre .hljs-variable,
20 | code .hljs-variable,
21 | pre .hljs-attribute,
22 | code .hljs-attribute,
23 | pre .hljs-tag,
24 | code .hljs-tag,
25 | pre .hljs-regexp,
26 | code .hljs-regexp,
27 | pre .ruby .hljs-constant,
28 | code .ruby .hljs-constant,
29 | pre .xml .hljs-tag .hljs-title,
30 | code .xml .hljs-tag .hljs-title,
31 | pre .xml .hljs-pi,
32 | code .xml .hljs-pi,
33 | pre .xml .hljs-doctype,
34 | code .xml .hljs-doctype,
35 | pre .html .hljs-doctype,
36 | code .html .hljs-doctype,
37 | pre .css .hljs-id,
38 | code .css .hljs-id,
39 | pre .css .hljs-class,
40 | code .css .hljs-class,
41 | pre .css .hljs-pseudo,
42 | code .css .hljs-pseudo {
43 | color: #c82829;
44 | }
45 | pre .hljs-number,
46 | code .hljs-number,
47 | pre .hljs-preprocessor,
48 | code .hljs-preprocessor,
49 | pre .hljs-pragma,
50 | code .hljs-pragma,
51 | pre .hljs-built_in,
52 | code .hljs-built_in,
53 | pre .hljs-literal,
54 | code .hljs-literal,
55 | pre .hljs-params,
56 | code .hljs-params,
57 | pre .hljs-constant,
58 | code .hljs-constant {
59 | color: #f5871f;
60 | }
61 | pre .ruby .hljs-class .hljs-title,
62 | code .ruby .hljs-class .hljs-title,
63 | pre .css .hljs-rules .hljs-attribute,
64 | code .css .hljs-rules .hljs-attribute {
65 | color: #eab700;
66 | }
67 | pre .hljs-string,
68 | code .hljs-string,
69 | pre .hljs-value,
70 | code .hljs-value,
71 | pre .hljs-inheritance,
72 | code .hljs-inheritance,
73 | pre .hljs-header,
74 | code .hljs-header,
75 | pre .ruby .hljs-symbol,
76 | code .ruby .hljs-symbol,
77 | pre .xml .hljs-cdata,
78 | code .xml .hljs-cdata {
79 | color: #718c00;
80 | }
81 | pre .css .hljs-hexcolor,
82 | code .css .hljs-hexcolor {
83 | color: #3e999f;
84 | }
85 | pre .hljs-function,
86 | code .hljs-function,
87 | pre .python .hljs-decorator,
88 | code .python .hljs-decorator,
89 | pre .python .hljs-title,
90 | code .python .hljs-title,
91 | pre .ruby .hljs-function .hljs-title,
92 | code .ruby .hljs-function .hljs-title,
93 | pre .ruby .hljs-title .hljs-keyword,
94 | code .ruby .hljs-title .hljs-keyword,
95 | pre .perl .hljs-sub,
96 | code .perl .hljs-sub,
97 | pre .javascript .hljs-title,
98 | code .javascript .hljs-title,
99 | pre .coffeescript .hljs-title,
100 | code .coffeescript .hljs-title {
101 | color: #4271ae;
102 | }
103 | pre .hljs-keyword,
104 | code .hljs-keyword,
105 | pre .javascript .hljs-function,
106 | code .javascript .hljs-function {
107 | color: #8959a8;
108 | }
109 | pre .hljs,
110 | code .hljs {
111 | display: block;
112 | background: white;
113 | color: #4d4d4c;
114 | padding: 0.5em;
115 | }
116 | pre .coffeescript .javascript,
117 | code .coffeescript .javascript,
118 | pre .javascript .xml,
119 | code .javascript .xml,
120 | pre .tex .hljs-formula,
121 | code .tex .hljs-formula,
122 | pre .xml .javascript,
123 | code .xml .javascript,
124 | pre .xml .vbscript,
125 | code .xml .vbscript,
126 | pre .xml .css,
127 | code .xml .css,
128 | pre .xml .hljs-cdata,
129 | code .xml .hljs-cdata {
130 | opacity: 0.5;
131 | }
132 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-livereload/plugin.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var newEl = document.createElement('script'),
3 | firstScriptTag = document.getElementsByTagName('script')[0];
4 |
5 | if (firstScriptTag) {
6 | newEl.async = 1;
7 | newEl.src = '//' + window.location.hostname + ':35729/livereload.js';
8 | firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag);
9 | }
10 |
11 | })();
12 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-search/search.css:
--------------------------------------------------------------------------------
1 | .book .book-summary .book-search {
2 | padding: 6px;
3 | background: transparent;
4 | position: absolute;
5 | top: -50px;
6 | left: 0px;
7 | right: 0px;
8 | transition: top 0.5s ease;
9 | }
10 | .book .book-summary .book-search input,
11 | .book .book-summary .book-search input:focus,
12 | .book .book-summary .book-search input:hover {
13 | width: 100%;
14 | background: transparent;
15 | border: 1px solid transparent;
16 | box-shadow: none;
17 | outline: none;
18 | line-height: 22px;
19 | padding: 7px 4px;
20 | color: inherit;
21 | }
22 | .book.with-search .book-summary .book-search {
23 | top: 0px;
24 | }
25 | .book.with-search .book-summary ul.summary {
26 | top: 50px;
27 | }
28 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-search/search.js:
--------------------------------------------------------------------------------
1 | require([
2 | "gitbook",
3 | "lodash"
4 | ], function(gitbook, _) {
5 | var index = null;
6 | var $searchInput, $searchForm;
7 |
8 | // Use a specific index
9 | function loadIndex(data) {
10 | index = lunr.Index.load(data);
11 | }
12 |
13 | // Fetch the search index
14 | function fetchIndex() {
15 | $.getJSON(gitbook.state.basePath+"/search_index.json")
16 | .then(loadIndex);
17 | }
18 |
19 | // Search for a term and return results
20 | function search(q) {
21 | if (!index) return;
22 |
23 | var results = _.chain(index.search(q))
24 | .map(function(result) {
25 | var parts = result.ref.split("#")
26 | return {
27 | path: parts[0],
28 | hash: parts[1]
29 | }
30 | })
31 | .value();
32 |
33 | return results;
34 | }
35 |
36 | // Create search form
37 | function createForm(value) {
38 | if ($searchForm) $searchForm.remove();
39 |
40 | $searchForm = $('
', {
41 | 'class': 'book-search',
42 | 'role': 'search'
43 | });
44 |
45 | $searchInput = $('
', {
46 | 'type': 'text',
47 | 'class': 'form-control',
48 | 'val': value,
49 | 'placeholder': 'Type to search'
50 | });
51 |
52 | $searchInput.appendTo($searchForm);
53 | $searchForm.prependTo(gitbook.state.$book.find('.book-summary'));
54 | }
55 |
56 | // Return true if search is open
57 | function isSearchOpen() {
58 | return gitbook.state.$book.hasClass("with-search");
59 | }
60 |
61 | // Toggle the search
62 | function toggleSearch(_state) {
63 | if (isSearchOpen() === _state) return;
64 |
65 | gitbook.state.$book.toggleClass("with-search", _state);
66 |
67 | // If search bar is open: focus input
68 | if (isSearchOpen()) {
69 | gitbook.sidebar.toggle(true);
70 | $searchInput.focus();
71 | } else {
72 | $searchInput.blur();
73 | $searchInput.val("");
74 | gitbook.sidebar.filter(null);
75 | }
76 | }
77 |
78 | // Recover current search when page changed
79 | function recoverSearch() {
80 | var keyword = gitbook.storage.get("keyword", "");
81 |
82 | createForm(keyword);
83 |
84 | if (keyword.length > 0) {
85 | if(!isSearchOpen()) {
86 | toggleSearch();
87 | }
88 | gitbook.sidebar.filter(_.pluck(search(keyword), "path"));
89 | }
90 | };
91 |
92 |
93 | gitbook.events.bind("start", function(config) {
94 | // Pre-fetch search index and create the form
95 | fetchIndex();
96 | createForm();
97 |
98 | // Type in search bar
99 | $(document).on("keyup", ".book-search input", function(e) {
100 | var key = (e.keyCode ? e.keyCode : e.which);
101 | var q = $(this).val();
102 |
103 | if (key == 27) {
104 | e.preventDefault();
105 | toggleSearch(false);
106 | return;
107 | }
108 | if (q.length == 0) {
109 | gitbook.sidebar.filter(null);
110 | gitbook.storage.remove("keyword");
111 | } else {
112 | var results = search(q);
113 | gitbook.sidebar.filter(
114 | _.pluck(results, "path")
115 | );
116 | gitbook.storage.set("keyword", q);
117 | }
118 | });
119 |
120 | // Create the toggle search button
121 | gitbook.toolbar.createButton({
122 | icon: 'fa fa-search',
123 | label: 'Search',
124 | position: 'left',
125 | onClick: toggleSearch
126 | });
127 |
128 | // Bind keyboard to toggle search
129 | gitbook.keyboard.bind(['f'], toggleSearch)
130 | });
131 |
132 | gitbook.events.bind("page.change", recoverSearch);
133 | });
134 |
135 |
136 |
--------------------------------------------------------------------------------
/_book/gitbook/plugins/gitbook-plugin-sharing/buttons.js:
--------------------------------------------------------------------------------
1 | require(["gitbook", "lodash"], function(gitbook, _) {
2 | var SITES = {
3 | 'facebook': {
4 | 'label': 'Facebook',
5 | 'icon': 'fa fa-facebook',
6 | 'onClick': function(e) {
7 | e.preventDefault();
8 | window.open("http://www.facebook.com/sharer/sharer.php?s=100&p[url]="+encodeURIComponent(location.href));
9 | }
10 | },
11 | 'twitter': {
12 | 'label': 'Twitter',
13 | 'icon': 'fa fa-twitter',
14 | 'onClick': function(e) {
15 | e.preventDefault();
16 | window.open("http://twitter.com/home?status="+encodeURIComponent(document.title+" "+location.href));
17 | }
18 | },
19 | 'google': {
20 | 'label': 'Google+',
21 | 'icon': 'fa fa-google-plus',
22 | 'onClick': function(e) {
23 | e.preventDefault();
24 | window.open("https://plus.google.com/share?url="+encodeURIComponent(location.href));
25 | }
26 | },
27 | 'weibo': {
28 | 'label': 'Weibo',
29 | 'icon': 'fa fa-weibo',
30 | 'onClick': function(e) {
31 | e.preventDefault();
32 | window.open("http://service.weibo.com/share/share.php?content=utf-8&url="+encodeURIComponent(location.href)+"&title="+encodeURIComponent(document.title));
33 | }
34 | },
35 | 'instapaper': {
36 | 'label': 'Instapaper',
37 | 'icon': 'fa fa-instapaper',
38 | 'onClick': function(e) {
39 | e.preventDefault();
40 | window.open("http://www.instapaper.com/text?u="+encodeURIComponent(location.href));
41 | }
42 | },
43 | 'vk': {
44 | 'label': 'VK',
45 | 'icon': 'fa fa-vk',
46 | 'onClick': function(e) {
47 | e.preventDefault();
48 | window.open("http://vkontakte.ru/share.php?url="+encodeURIComponent(location.href));
49 | }
50 | }
51 | };
52 |
53 |
54 |
55 | gitbook.events.bind("start", function(e, config) {
56 | var opts = config.sharing;
57 |
58 | // Create dropdown menu
59 | var menu = _.chain(opts.all)
60 | .map(function(id) {
61 | var site = SITES[id];
62 |
63 | return {
64 | text: site.label,
65 | onClick: site.onClick
66 | };
67 | })
68 | .compact()
69 | .value();
70 |
71 | // Create main button with dropdown
72 | if (menu.length > 0) {
73 | gitbook.toolbar.createButton({
74 | icon: 'fa fa-share-alt',
75 | label: 'Share',
76 | position: 'right',
77 | dropdown: [menu]
78 | });
79 | }
80 |
81 | // Direct actions to share
82 | _.each(SITES, function(site, sideId) {
83 | if (!opts[sideId]) return;
84 |
85 | gitbook.toolbar.createButton({
86 | icon: site.icon,
87 | label: site.text,
88 | position: 'right',
89 | onClick: site.onClick
90 | });
91 | });
92 | });
93 | });
94 |
--------------------------------------------------------------------------------
/_book/images/5.1.0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/images/5.1.0.png
--------------------------------------------------------------------------------
/_book/images/5.4.1.1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/_book/images/5.4.1.1.png
--------------------------------------------------------------------------------
/book.json:
--------------------------------------------------------------------------------
1 | {
2 | "gitbook": "2.x.x",
3 | "plugins": [],
4 | "styles": {
5 | "website": "styles/ebook.css",
6 | "ebook": "styles/ebook.css",
7 | "pdf": "styles/pdf.css",
8 | "mobi": "styles/mobi.css",
9 | "epub": "styles/epub.css"
10 | },
11 | "links": {
12 | "sidebar": {
13 | "ntyTcp": "http://www.0voice.com"
14 | },
15 | "sharing": {
16 | "google": "fuck you gitbook",
17 | "facebook": null,
18 | "twitter": null,
19 | "weibo": null,
20 | "all": null
21 | }
22 | },
23 | "pluginsConfig": {
24 | "theme-default": {
25 | "showLevel": false
26 | }
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/chapter1/1.1_课程介绍.md:
--------------------------------------------------------------------------------
1 | # 1.1 课程介绍
2 |
3 |
--------------------------------------------------------------------------------
/chapter1/1.2_项目介绍.md:
--------------------------------------------------------------------------------
1 | # 1.2 项目介绍
2 |
3 |
--------------------------------------------------------------------------------
/chapter1/README.md:
--------------------------------------------------------------------------------
1 | # 第一节 课程介绍和项目介绍
2 |
3 |
--------------------------------------------------------------------------------
/chapter10/10.1_ini配置.md:
--------------------------------------------------------------------------------
1 | # 10.1 ini配置
2 |
3 | 我们先来看下ini的配置文件是啥,例如我们的配置文件如下,database就是我们的俗称的区段,ip,port就是字段,127.0.0.1和3306就是字段的值。
4 | ```
5 | [database]
6 | ip = 127.0.0.1 ;
7 | port = 3306 ;
8 | user = root ;
9 | pwd = 123456 ;
10 | db = dongnaobike;
11 |
12 | [server]
13 | port = 9090
14 |
15 | ```
16 | OK,那我们怎么实现从ini文件里加载上这些内容呢?首先分析就是读取文件内容,然后依照文件内容,把这些字段和值组成key-value的形式保存在某个数据结构中,这样我们通过key(区段+字段)就能找到value(值),那如何设计这个数据结构呢?
17 | 最容易想到的就是字典了(dictionary),这个在我们的数据结构的课堂上应该学过吧,字典是一个非常常用的数据结构了,如果大家有兴趣还可以去深度探索下redis的存储结构,也用到了字典,只不过它的那个字典设计更加复杂而已。
18 |
19 | ```
20 | typedef struct _dictionary_ {
21 | int n ; /** 字典中已经存储的个数 */
22 | ssize_t size ; /** 字典中节点的个数 */
23 | char ** val ; /** value列表 */
24 | char ** key ; /** key列表 */
25 | unsigned * hash ; /** hash值列表 */
26 | } dictionary ;
27 | ```
28 | 那他是如何存储数据的呢?我们首先来看它还应当提供的接口:
29 | ```
30 | dictionary * dictionary_new(size_t size);
31 | void dictionary_del(dictionary * vd);
32 | unsigned dictionary_hash(const char * key);
33 | const char * dictionary_get(const dictionary * d, const char * key, const char * def);
34 | int dictionary_set(dictionary * vd, const char * key, const char * val);
35 | void dictionary_unset(dictionary * d, const char * key);
36 | void dictionary_dump(const dictionary * d, FILE * out);
37 | ```
38 | 自然不用说,我们首先得通过dictionary_new接口创建一个字典,创建这个接口的实现如下,其实val和key都是字符串的二维数组,hash是一个无符号整形的数组,创建一个字典就是首先分配内存:
39 | ```
40 | dictionary * dictionary_new(size_t size)
41 | {
42 | dictionary * d ;
43 |
44 | /* If no size was specified, allocate space for DICTMINSZ */
45 | if (size
size = size ;
51 | d->val = (char**) calloc(size, sizeof *d->val);
52 | d->key = (char**) calloc(size, sizeof *d->key);
53 | d->hash = (unsigned*) calloc(size, sizeof *d->hash);
54 | }
55 | return d ;
56 | }
57 |
58 | ```
59 | 创建了字典,那如何往字典里插入key-value对呢?上面的这个数据结构其实就是这样的一个东西,用图表示如下:
60 | 
61 | 从上图,我们不难理解,就是在插入key-value时,首先计算出一个key的hash值(hash),然后把hash-key-value三元组存放在上图的结构的位置d->n位置,最后d->n移动一个位置,只不过我们在存放这个hash-key-value这个三元组之前要做各种判断,如:
62 | * hash值是否在字典中存在,如果存在则更新key和value;
63 | * 是否已经超出了字典的大小,如果是的则需要扩充字典的节点个数(当前大小的两倍);
64 | * 找到d->n位置;
65 |
66 | ```
67 | int dictionary_set(dictionary * d, const char * key, const char * val)
68 | {
69 | ssize_t i ;
70 | unsigned hash ;
71 |
72 | if (d==NULL || key==NULL) return -1 ;
73 |
74 | /* Compute hash for this key */
75 | hash = dictionary_hash(key) ;
76 | /* Find if value is already in dictionary */
77 | if (d->n>0) {
78 | for (i=0 ; isize ; i++) {
79 | if (d->key[i]==NULL)
80 | continue ;
81 | if (hash==d->hash[i]) { /* Same hash value */
82 | if (!strcmp(key, d->key[i])) { /* Same key */
83 | /* Found a value: modify and return */
84 | if (d->val[i]!=NULL)
85 | free(d->val[i]);
86 | d->val[i] = (val ? xstrdup(val) : NULL);
87 | /* Value has been modified: return */
88 | return 0 ;
89 | }
90 | }
91 | }
92 | }
93 | /* Add a new value */
94 | /* See if dictionary needs to grow */
95 | if (d->n==d->size) {
96 | /* Reached maximum size: reallocate dictionary */
97 | if (dictionary_grow(d) != 0)
98 | return -1;
99 | }
100 |
101 | /* Insert key in the first empty slot. Start at d->n and wrap at
102 | d->size. Because d->n < d->size this will necessarily
103 | terminate. */
104 | for (i=d->n ; d->key[i] ; ) {
105 | if(++i == d->size) i = 0;
106 | }
107 | /* Copy key */
108 | d->key[i] = xstrdup(key);
109 | d->val[i] = (val ? xstrdup(val) : NULL) ;
110 | d->hash[i] = hash;
111 | d->n ++ ;
112 | return 0 ;
113 | }
114 | ```
115 | 那如何从字典中取数据呢?我们先来看接口定义:
116 | ```
117 | const char * dictionary_get(const dictionary * d, const char * key, const char * def);
118 | ```
119 | 也就是要通过key返回对应的value,从上图我们更加要知道,我们去找这个value就是要找到对应的key的hash值所在的位置,也就是首先计算key的hash值,得到这个hash值后从d->hash数组里遍历出hash值,然后对应数组的下标 i 就是key和value所在d->key 和 d->value的位置,这样取走就行。好的,来看实现:
120 | ```
121 | const char * dictionary_get(const dictionary * d, const char * key, const char * def)
122 | {
123 | unsigned hash ;
124 | ssize_t i ;
125 |
126 | hash = dictionary_hash(key);
127 | for (i=0 ; isize ; i++) {
128 | if (d->key[i]==NULL)
129 | continue ;
130 | /* Compare hash */
131 | if (hash==d->hash[i]) {
132 | /* Compare string, to avoid hash collisions */
133 | if (!strcmp(key, d->key[i])) {
134 | return d->val[i] ;
135 | }
136 | }
137 | }
138 | return def ;
139 | }
140 | ```
141 | OK,了解了最基本的数据结构,那就是解析ini文件内容了,这个不再细讲,详见开源库的实现[https://github.com/ndevilla/iniparser](https://github.com/ndevilla/iniparser)。
142 |
143 | 最终来看下我们如何使用这个开源库的:
144 | ```
145 | typedef struct st_env_config
146 | {
147 | //数据库的配置
148 | std::string db_ip;
149 | unsigned short db_port;
150 | std::string db_user;
151 | std::string db_pwd;
152 | std::string db_name;
153 |
154 | //服务的配置
155 | unsigned short svr_port;
156 |
157 | st_env_config()
158 | {
159 | };
160 |
161 | st_env_config(const std::string& db_ip, unsigned int db_port, const std::string& db_user, \
162 | const std::string& db_pwd, const std::string& db_name, unsigned short svr_port)
163 | {
164 | this->db_ip = db_ip;
165 | this->db_port = db_port;
166 | this->db_user = db_user;
167 | this->db_pwd = db_pwd;
168 | this->db_name = db_name;
169 | this->svr_port = svr_port;
170 | };
171 |
172 | st_env_config& operator =(const st_env_config& config)
173 | {
174 | if (this != &config)
175 | {
176 | this->db_ip = config.db_ip;
177 | this->db_port = config.db_port;
178 | this->db_user = config.db_user;
179 | this->db_pwd = config.db_pwd;
180 | this->db_name = config.db_name;
181 | this->svr_port = config.svr_port;
182 |
183 | }
184 | return *this;
185 | }
186 | }_st_env_config;
187 |
188 | bool Iniconfig::loadfile(const std::string& path)
189 | {
190 | dictionary* ini = NULL;
191 |
192 | ini = iniparser_load(path.c_str());
193 | if (ini==NULL)
194 | {
195 | LOG_ERROR("cannot parse file: %s\n", path.c_str());
196 | return false;
197 | }
198 |
199 | char* ip = iniparser_getstring(ini, "database:ip", "127.0.0.1");
200 | int port = iniparser_getint(ini, "database:port", 3306);
201 | char* user = iniparser_getstring(ini, "database:user", "root");
202 | char* pwd = iniparser_getstring(ini, "database:pwd", "123456");
203 | char* db = iniparser_getstring(ini, "database:db", "dongnaobike");
204 | int sport = iniparser_getint(ini, "server:port", 9090);
205 |
206 | _config = st_env_config(std::string(ip), port, std::string(user), \
207 | std::string(pwd), std::string(db), sport);
208 |
209 | iniparser_freedict(ini);
210 |
211 | _isloaded = true;
212 |
213 | return true;
214 | }
215 |
216 | ```
--------------------------------------------------------------------------------
/chapter10/10.2_xml配置.md:
--------------------------------------------------------------------------------
1 | # 10.2 xml配置
2 |
3 |
--------------------------------------------------------------------------------
/chapter10/10.3_lua配置.md:
--------------------------------------------------------------------------------
1 | # 10.3 lua配置
2 |
3 |
--------------------------------------------------------------------------------
/chapter10/10_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter10/10_1.png
--------------------------------------------------------------------------------
/chapter10/README.md:
--------------------------------------------------------------------------------
1 | # 第十节 项目配置文件
2 |
3 | 对于很多程序中要用的参数如果是可变的,那么最好的处理方式就是通过main函数参数传递,或者从别的地方去获取,这其中之一就是配置文件,但是在一个成熟和架构完善的系统,一般都会做到自动配置,自动部署,所以有的系统里会有一个单独的配置服务存在,每个其它的服务的配置信息从配置中心服务获取,然后运维人员通过操作界面把配置信息下发给配置中心服务,其余服务从配置中心获取变更信息,比如zookeeper就可以充当这样的配置中心服务,好了,这是后话,在架构师课程里会有介绍。
--------------------------------------------------------------------------------
/chapter11/11.1_重定向.md:
--------------------------------------------------------------------------------
1 | # 11.1 重定向
2 |
3 | * [stdin, stdout, stderr是什么](#1)
4 | * [重定向](#2)
5 |
6 |
7 |
8 | stdin, stdout, stderr是什么
9 | 在通常情况下,UNIX每个程序在开始运行的时刻,都会有3个已经打开的stream. 分别用来输入,输出,打印诊断和错误信息。通常他们会被连接到用户终端(tty(4)). 但也可以改变到其它文件或设备。这取决于双亲进程的选择和设置。
10 | 这3个symbols都是stdio(3) macro,类型为指向FILE的指针。可以被fprintf、fread等函数使用。
11 | 既然这几个是文件,那么他们在程序开始启动后,stdin, stdout, and stderr 的文件描述符是 0, 1和2,其它的文件描述符则排在其后。
12 | Linux的本质就是一切皆文件,输入输出设备也是以文件形式存在和管理的。
13 | stderr是不缓存的,stdout是行间缓存的。行间缓存是什么意思呢?我们来看一段代码:
14 |
15 | ```
16 | #include "stdio.h"
17 |
18 | int main(int argc, char** argv)
19 | {
20 | for(int i = 0; i < 10; i++)
21 | {
22 | fprintf(stdout, "This is stdout[%d]", i);
23 | fprintf(stderr, "This is stderr[%d]", i);
24 | }
25 |
26 | return 0;
27 | }
28 |
29 | ```
30 | 还有一段程序如下:
31 |
32 | ```
33 | #include "stdio.h"
34 |
35 | int main(int argc, char** argv)
36 | {
37 | for(int i = 0; i < 10; i++)
38 | {
39 | fprintf(stdout, "This is stdout[%d]\n", i);
40 | fprintf(stderr, "This is stderr[%d]\n", i);
41 | }
42 |
43 | return 0;
44 | }
45 |
46 | ```
47 | 细心的朋友们一定会发现,我们就是在打印的时候加上了"\n"而已,哦去敲入代码吧,看看有什么不一样。
48 | 我们很多同学喜欢用printf打印信息来调试程序,但是如果终端关掉了,那怎么显示printf的调试信息呢?
49 |
50 | 重定向
51 | 我们来先看这段程序(文件名test.c):
52 |
53 | ```
54 | #include
55 |
56 | int main(int argc, char** argv)
57 | {
58 | printf("welcome to dongnao!\n");
59 | fprintf(stdout, "i am lee ge!\n");
60 | perror("are you all ready!\n");
61 | fprintf(stderr, "Lee ge always stay with you!\n");
62 |
63 | return 0;
64 | }
65 |
66 | ```
67 | 编译好后(gcc -o test test.c),我们试试下面不同的运行方式:
68 | ./test > test.txt
69 | ./test 1 > testout.txt
70 | ./test 2 > testerr.txt
71 | ./test > test.txt 2>&1
72 |
73 | 当然除了使用“>”重定向外,我们还可以试下“>>”,另外我们还可以在代码中实现重定向,比如:
74 |
75 | ```
76 | #include
77 |
78 | int main(void)
79 | {
80 | FILE *out = freopen("stdout.txt", "w", stdout);
81 | printf("%s\n", "hello verybody!!!");
82 |
83 | return 0;
84 | }
85 | ```
86 | 总的来说,stdin,stdout和stderr还是和终端有密切关系,通常在生产环境时,会将这3个流重定向到其它文件。比如编写守护进程的时候,因为守护进程和终端无关,所以往往会将stdin,stdout和stderr重定向到/dev/null去。
87 |
88 | 如果我们实在要用printf或者fprintf去生成日志的话,最好还是加上这些信息,\_\_FILE__ \_\_LINE__ \_\_FUNCTION__, \_\_DATE__, \_\_TIME__。
89 |
90 | 当然我们一定要明白,printf设计到文件,这会引起IO中断,因此执行printf比一般的指令的效率要低很多。
--------------------------------------------------------------------------------
/chapter11/11.2_log4cpp.md:
--------------------------------------------------------------------------------
1 | # 11.2 log4cpp
2 |
3 | 在上文,我们使用重定向去输入日志到日志文件中,但是这样一种处理方式,还是有很多不足,比如没有办法输出到多个文件,没有办法设置日志文件大小,不太方便设置日志的级别,如果想远程输出到日志服务器怎么办呢?等等这些问题怎么解决呢?
4 | log4cpp就提供了很多的功能,帮助我们应用程序更方便地记录日志,OK,我们先来看一个简单的实现吧:
5 |
6 | * step 1 : 安装log4cpp
7 | log4cpp的官网是:[http://log4cpp.sourceforge.net/](http://log4cpp.sourceforge.net/)
8 |
9 | ```
10 | wget https://nchc.dl.sourceforge.net/project/log4cpp/log4cpp-1.1.x%20%28new%29/log4cpp-1.1/log4cpp-1.1.3.tar.gz
11 | tar xzvf log4cpp-1.1.3.tar.gz
12 | cd log4cpp-1.1.3
13 | ./configure
14 | make
15 | make install
16 | ```
17 |
18 | * step 2 : 包含头文件
19 |
20 | ```
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | ```
27 |
28 | * step 3 : 初始化日志输出的目的地(appenders)
29 |
30 | ```
31 | // 输出到std::cout
32 | log4cpp::Appender *appender = new log4cpp::OstreamAppender("root", &std::cout);
33 | // 输出到log文件
34 | //log4cpp::Appender *appender = new log4cpp::FileAppender("root", "test.log");
35 |
36 | ```
37 |
38 | appender有以下这些:
39 | > log4cpp::IdsaAppender // 发送到IDS或者
40 | log4cpp::FileAppender // 输出到文件
41 | log4cpp::RollingFileAppender // 输出到回卷文件,即当文件到达某个大小后回卷
42 | log4cpp::OstreamAppender // 输出到一个ostream类
43 | log4cpp::RemoteSyslogAppender // 输出到远程syslog服务器
44 | log4cpp::StringQueueAppender // 内存队列
45 | log4cpp::SyslogAppender // 本地syslog
46 | log4cpp::Win32DebugAppender // 发送到缺省系统调试器
47 | log4cpp::NTEventLogAppender // 发送到win 事件日志
48 |
49 | 上文,我们说过日志输出到终端或者文件中实际上是很慢的,会引起IO中断,所以我们可以输出到内存里StringQueueAppender,然后从StringQueueAppender输出到其它地方,这样我们的线程执行是比较高效的。
50 |
51 | * step 4 : 设置日志输出的格式
52 |
53 | ```
54 | log4cpp::PatternLayout *patternLayout = new log4cpp::PatternLayout();
55 | patternLayout->setConversionPattern("%d [%p] - %m%n");
56 | appender->setLayout(patternLayout);
57 |
58 | ```
59 |
60 | 日志输出格式控制有:
61 | PatternLayout supports following set of format characters:
62 | %% - a single percent sign
63 | %c - the category
64 | %d - the date\n Date format: The date format character may be followed by a date format specifier enclosed between braces. For example, %d{\%\H:%M:%S,%l} or %d{\%\d %m %Y %H:%\M:%S,%l}. If no date format specifier is given then the following format is used: "Wed Jan 02 02:03:55 1980". The date format specifier admits the same syntax as the ANSI C function strftime, with 1 addition. The addition is the specifier %l for milliseconds, padded with zeros to make 3 digits.
65 | %m - the message
66 | %n - the platform specific line separator
67 | %p - the priority
68 | %r - milliseconds since this layout was created.
69 | %R - seconds since Jan 1, 1970
70 | %u - clock ticks since process start
71 | %x - the NDC
72 | %t - thread name
73 | By default, ConversionPattern for PatternLayout is set to "%m%n".
74 |
75 | * step 4 : 设置类别输出的(category)和日志优先级(priority)
76 |
77 | ```
78 | log4cpp::Category &root = log4cpp::Category::getRoot();
79 | root.setPriority(log4cpp::Priority::NOTICE);
80 | root.addAppender(appender);
81 |
82 | ```
83 | 日志的级别总共有:NOTSET < DEBUG < INFO < NOTICE < WARN < ERROR < CRIT < ALERT < FATAL = EMERG。日志级别的意思是低于该级别的日志不会被记录。
84 |
85 | * step 5 : 定义一个宏
86 |
87 | ```
88 | #define LOG(__level) log4cpp::Category::getRoot() << log4cpp::Priority::__level << __FILE__ << " " << __LINE__ << ": "
89 | ```
90 | 当然也可以使用Category定义的函数:
91 |
92 | ```
93 | /**
94 | * Log a message with the specified priority.
95 | * @param priority The priority of this log message.
96 | * @param stringFormat Format specifier for the string to write
97 | * in the log file.
98 | * @param ... The arguments for stringFormat
99 | **/
100 | virtual void log(Priority::Value priority, const char* stringFormat,
101 | ...) throw();
102 |
103 | /**
104 | * Log a message with the specified priority.
105 | * @param priority The priority of this log message.
106 | * @param message string to write in the log file
107 | **/
108 | virtual void log(Priority::Value priority,
109 | const std::string& message) throw();
110 |
111 | void debug(const char* stringFormat, ...) throw();
112 | void debug(const std::string& message) throw();
113 | void info(const char* stringFormat, ...) throw();
114 | ...
115 | ```
116 | * step 6 : 使用宏定义记录日志
117 |
118 | ```
119 | LOG(DEBUG) << "i am happy.";
120 | LOG(INFO) << "oh, you happy, we happy.";
121 | LOG(NOTICE)<< "please do not contact me. ";
122 | LOG(WARN) << "i am very busy now.";
123 | LOG(ERROR) << "oh, what happed?";
124 |
125 | ```
126 | 当然我们在使用过程中,可以封装一个单例,oh,单例是什么?别急,我们课堂上会讲。
127 |
128 | 当然在实际工程上应用,我们是使用日志配置文件去控制日志记录的。那么我们来看一个日志配置吧:
129 | ```
130 | #定义Root category的属性
131 | log4cpp.rootCategory=DEBUG, RootLog
132 |
133 | #定义RootLog属性
134 | log4cpp.appender.RootLog=RollingFileAppender
135 | log4cpp.appender.RootLog.layout=PatternLayout
136 | <<<<<<< HEAD
137 | log4cpp.appender.RootLog.layout.ConversionPattern=%d{% m-%d %H:%M:%S %l} [%t][%p]%m%n
138 | =======
139 | log4cpp.appender.RootLog.layout.ConversionPattern=%d{\%\m-%d %H:%M:%S %l} [%t][%p]%m%n
140 | >>>>>>> ac666e2a90e167f86b8b827e78f650b852e03ad0
141 | log4cpp.appender.RootLog.fileName=/var/log/brk.log
142 | log4cpp.appender.RootLog.maxFileSize=268435456 #256MB
143 | log4cpp.appender.RootLog.fileNamePattern=brk_%i.log
144 | log4cpp.appender.RootLog.maxBackupIndex=256
145 |
146 | ```
147 | 那如何使用配置文件定义log呢?
148 |
149 | ```
150 | BOOL Logger::init(const std::string & log_conf_file)
151 | {
152 | try
153 | {
154 | log4cpp::PropertyConfigurator::configure(log_conf_file);
155 | }
156 | catch(log4cpp::ConfigureFailure& f)
157 | {
158 | std::cerr << " load log config file " << log_conf_file.c_str() << " failed with result : " << f.what()<< std::endl;
159 | return FALSE;
160 | }
161 |
162 | m_Category = &log4cpp::Category::getRoot();
163 |
164 | return TRUE;
165 | }
166 |
167 | ```
168 |
169 |
170 |
--------------------------------------------------------------------------------
/chapter11/11.3_大型项目的日志架构.md:
--------------------------------------------------------------------------------
1 | # 11.3 分布式系统的集中式日志解决方案
2 |
3 | log4cpp很好用,能给我们的程序提供比较完备的日志方案,but,设想我们是一个分布式集群部署的系统,那么服务实例(可以简单理解有很多的服务器上运行了相同或者不同的服务程序)有很多个,那么我们定位问题时需要多个服务实例的日志信息,那在这样的系统,log4cpp很难解决这样的应用场景,有很多朋友也许会反映,log4cpp不是可以远程输出log吗,大家都往一个服务器上输出log不就可以了?
4 | 也许,上文提到的log4cpp远程输出能够解决这个问题,but,有一个开源的和更好的解决方案,江湖人称ELK,这个就是集中式日志解决方案。那ELK是啥?我们首先看下日志收集的一个Web界面。
5 | 
6 | ELK 不是一款软件,而是 Elasticsearch、Logstash 和 Kibana 三种软件产品的首字母缩写。这三者都是开源软件,通常配合使用,而且又先后归于 Elastic.co 公司名下,所以被简称为 ELK Stack。根据 Google Trend 的信息显示,ELK Stack 已经成为目前最流行的集中式日志解决方案。
7 |
8 | * Elasticsearch:分布式搜索和分析引擎,具有高可伸缩、高可靠和易管理等特点。基于 Apache Lucene 构建,能对大容量的数据进行接近实时的存储、搜索和分析操作。通常被用作某些应用的基础搜索引擎,使其具有复杂的搜索功能;
9 |
10 | * Logstash:数据收集引擎。它支持动态的从各种数据源搜集数据,并对数据进行过滤、分析、丰富、统一格式等操作,然后存储到用户指定的位置;
11 | * Kibana:数据分析和可视化平台。通常与 Elasticsearch 配合使用,对其中数据进行搜索、分析和以统计图表的方式展示;
12 | * Filebeat:ELK 协议栈的新成员,一个轻量级开源日志文件数据搜集器,基于 Logstash-Forwarder 源代码开发,是对它的替代。在需要采集日志数据的 server 上安装 Filebeat,并指定日志目录或日志文件后,Filebeat 就能读取数据,迅速发送到 Logstash 进行解析,亦或直接发送到 Elasticsearch 进行集中式存储和分析。
13 | 那么这是解决方案是如何架构设计的呢?
14 |
15 | * 简单的架构
16 | 一个 Logstash、Elasticsearch 和 Kibana 实例。Logstash 通过输入插件从多种数据源(比如日志文件、标准输入 Stdin 等)获取数据,再经过滤插件加工数据,然后经 Elasticsearch 输出插件输出到 Elasticsearch,通过 Kibana 展示:
17 | 
18 |
19 | * Logstash 作为日志搜集器
20 | 这种架构是对上面架构的扩展,把一个 Logstash 数据搜集节点扩展到多个,分布于多台机器,将解析好的数据发送到 Elasticsearch server 进行存储,最后在 Kibana 查询、生成日志报表等,如下图:
21 | 
22 |
23 | * 基于 Filebeat 架构
24 | Filebeat比起Logstash更加轻量,而且更加方便,所以在工业级项目中我们一般使用Filebeat替代logstash的日志采集器,那么这种架构是怎么样的呢?因为免费的 ELK 没有任何安全机制,所以这里使用了 Nginx 作反向代理,避免用户直接访问 Kibana 服务器。加上配置 Nginx 实现简单的用户认证,一定程度上提高安全性。另外,Nginx 本身具有负载均衡的作用,能够提高系统访问性能。
25 | 
--------------------------------------------------------------------------------
/chapter11/11_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter11/11_1.png
--------------------------------------------------------------------------------
/chapter11/11_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter11/11_2.png
--------------------------------------------------------------------------------
/chapter11/README.md:
--------------------------------------------------------------------------------
1 | # 第十一节 项目日志
2 |
3 | 我们在项目开发时,最必要且必须要解决的就是日志如何管理,这是每一个系统一开始就要面对的问题,因为我们在开发中可能会留下很多的缺陷(BUG),那系统运行过程中记录的日志就有可能帮我们定位问题,同时也有利于我们定位出网络攻击,除此以外,日志还可以帮助我们处理系统故障,比如有时候系统出故障后,我们从日志中恢复到出故障前继续运行。好了,那我们系统如何记录日志?若知后事如何,且听下回分解...... :)
--------------------------------------------------------------------------------
/chapter11/elk_stack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter11/elk_stack.png
--------------------------------------------------------------------------------
/chapter11/log_web.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter11/log_web.jpg
--------------------------------------------------------------------------------
/chapter12/12.2_如何产生一个进程.md:
--------------------------------------------------------------------------------
1 | # 12.2 如何产生和销毁一个进程
2 |
3 | 创建进程很简单,直接调用fork函数,
4 |
5 | ```
6 | #include
7 |
8 | pid_t fork(void);
9 | ```
10 | ok,我们来着重讲讲这个函数如何用的:
11 |
12 | ```
13 | #include
14 | #include
15 | int main ()
16 | {
17 | pid_t fpid; //fpid表示fork函数返回的值
18 | int count=0;
19 | fpid=fork();
20 | if (fpid < 0)
21 | printf("error in fork!");
22 | else if (fpid == 0) {
23 | printf("i am the child process, my process id is %d/n",getpid());
24 | printf("我是爹的儿子/n");//对某些人来说中文看着更直白。
25 | count++;
26 | }
27 | else {
28 | printf("i am the parent process, my process id is %d/n",getpid());
29 | printf("我是孩子他爹/n");
30 | count++;
31 | }
32 | printf("统计结果是: %d/n",count);
33 | return 0;
34 | }
35 |
36 | ```
37 | 调用fork函数后,会创建一个子进程,并且父子两个进程都从fork处执行,fork函数有两个返回值,对于父进程会返回子进程的pid,此时pid会大于0,对于子进程来说,pid会等于0。
38 | Oh,多进程编程so so so easy,但是,我们上一节说过进程是内核调度资源的基本单位,那父子进程管理的资源有什么关系呢?
39 | 传统的linux操作系统以统一的方式对待所有的进程:子进程复制父进程所拥有的所有资源,这种方法使得创建进程非常非常非常慢,因为子进程需要拷贝父进程的所有的地址空间,那现代的操作系统,是如何处理的呢?主要有以下三种方式:
40 | * 写时复制
41 | * 轻量级进程允许父子进程共享每进程在内核的很多数据结构,比如地址空间、打开文件表和信号处理。
42 | * vfrok系统调用创建的进程能共享其父进程的内存地址空间,为了防止父进程重写子进程需要的数据,阻塞父进程的执行,一直到子进程退出为止。
43 |
44 | 可能很多同学会问,轻量级进程是什么东西啊?
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/chapter12/12.3_僵尸孤儿守护进程.md:
--------------------------------------------------------------------------------
1 | # 12.3 孤儿僵尸守护进程
2 |
3 | * 孤儿进程:
4 | 一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程所收养,并由init进程对它们完成状态收集工作。
5 |
6 | 想想我们如何模仿一个孤儿进程?
7 |
8 | * 僵尸进程:
9 | 一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。
10 |
11 | 1. 僵尸进程怎样产生的:
12 |
13 | 一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个称为僵尸进程(Zombie)的数据结构(系统调用 exit,它的作用是使进程退出,但也仅仅限于将一个正常的进程变成一个僵尸进程,并不能将其完全销毁)。
14 |
15 | 在Linux进程的状态中,僵尸进程是非常特殊的一种,它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程列表中保留一个位置,记载该进程的退出状态等信息供其他进程收集,除此之外,僵尸进程不再占有任何内存空间。它需要它的父进程来为它收尸,如果他的父进程没安装 SIGCHLD信号处理函数调用wait或waitpid()等待子进程结束,又没有显式忽略该信号,那么它就一直保持僵尸状态,如果这时父进程结束了, 那么init进程自动会接手这个子进程,为它收尸,它还是能被清除的。但是如果如果父进程是一个循环,不会结束,那么子进程就会一直保持僵尸状态,这就是 为什么系统中有时会有很多的僵尸进程。
16 |
17 | 2. 怎么查看僵尸进程:
18 |
19 | 利用命令ps,可以看到有标记为Z的进程就是僵尸进程。
20 |
21 | 3. 怎样来清除僵尸进程:
22 |
23 | 改写父进程,在子进程死后要为它收尸。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用 wait,内核也会向它发送SIGCHLD消息,尽管对的默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
24 | 把父进程杀掉。父进程死后,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。它产生的所有僵尸进程也跟着消失。
25 |
26 | * 守护进程
27 |
28 | 不与任何终端关联的进程,通常情况下守护进程在系统启动时就在运行,它们以root用户或者其他特殊用户(apache和postfix)运行,并能处理一些系统级的任务。守护进程脱离于终端,是为了避免进程在执行过程中的信息在任何终端上显示,并且进程也不会被任何终端所产生的终端信息所打断(比如关闭终端等)。那如何成为一个守护进程呢?
29 | 步骤如下:
30 | >1.调用fork(),创建新进程,它会是将来的守护进程.
31 | >2.在父进程中调用exit,保证子进程不是进程组长
32 | >3.调用setsid()创建新的会话区
33 | >4.将当前目录改成跟目录(如果把当前目录作为守护进程的目录,当前目录不能被卸载他作为守护进程的工作目录)
34 | >5.将标准输入,标注输出,标准错误重定向到/dev/null.
35 |
36 | 我们来看这个代码:
37 | ```
38 | #include
39 | #include
40 | #include "daemon.h"
41 |
42 | int daemon(int nochdir, int noclose)
43 | {
44 | int fd;
45 |
46 | switch (fork()) {
47 | case -1:
48 | return (-1);
49 | case 0:
50 | break;
51 | default:
52 | _exit(0);
53 | }
54 |
55 | if (setsid() == -1)
56 | return (-1);
57 |
58 | if (!nochdir)
59 | (void)chdir("/");
60 |
61 | if (!noclose && (fd = open("/dev/null", O_RDWR, 0)) != -1) {
62 | (void)dup2(fd, STDIN_FILENO);
63 | (void)dup2(fd, STDOUT_FILENO);
64 | (void)dup2(fd, STDERR_FILENO);
65 | if (fd > 2)
66 | (void)close (fd);
67 | }
68 | return (0);
69 | }
70 |
71 | ```
72 |
--------------------------------------------------------------------------------
/chapter14/14.1_俗话网络通信.md:
--------------------------------------------------------------------------------
1 | # 14.1 俗话网络通信
2 |
3 | 话说最近幼儿园大班里来了一位女同学,叫风玲草同学,恰逢周末,MT同学在家也很无聊,也很喜欢新来的风玲草同学,所以想约风玲草同学去游乐场玩,但是因为MT同学比较小(小就是还没有发育完全的意思),必须经过父母的同意,那在这里,MT同学的父母就是网关(gateway),所有我们要发送出去的数据都必须经过网关,有了网关后,网关要知道把数据发到哪里去,也就是MT同学的父母要知道风铃草同学的电话号码,哦,不对,因为风玲草同学也比较小,她那边也要有一个网关(也就是她的父母,在此就是她父母的电话)。MT同学哭闹一定马上要去和风铃草同学玩,MT的父母很无奈,因为不知道风玲草同学父母的电话,但是他们知道幼儿园班主任的电话号码,于是打电话问班主任风铃草同学父母的电话号码,在此,班主任就是我们的DNS服务器(域名服务器,网络上所有要通信的主机就必须把自己的IP地址和对应的域名注册到DNS服务器),因为班主任这里登记了所有同学的父母联系电话和家庭住址等等信息。
4 |
5 | 有了风铃草同学父母的电话号码,MT同学父母开始拨号给她们,我们看起来一个很简单的拨号,实际上是有电话局接通了MT家里的电话信号和风玲草同学家里的电话信号,让她们之间能够相互连通信号,这里电话局就是我们通信网络上的交换设备,传输信号的电话线就是我们的同轴光缆,有了网络上的交换设备和光缆,我们网络之间的通信才变成了可能。当然在这个网络上通信就像我们打电话一样,需要一个电话号码,而网络上就是一个IP地址。
6 | 上文中提到了IP地址,IP地址是用来标识一台主机的(电脑)或者称网络设备吧,最初我们的IP地址是4个字节的(也就是我们常见的xxx.ooo.fff.kkk),后来我们网络上通信的设备和主机越来越多,发送4个字节不够用了,于是诞生了IPV6的编址技术,也就是IP地址用6个字节表示。那有了IP地址来标识主机是不是就够了呢?也不是的,因为我们的网络是分层的也是分范围的(比如局域网A和局域网B),比如我们的IP地址是192.168.1.109,那么这个IP地址,我们可以经常变换,比如从192.168.1网段的局域网迁移到192.168.2.子局域网的时候,我们就需要修改IP地址为192.168.2.109,这样才能成为192.168.2的子局域网中的一台主机与其他的主机通信。既然IP地址容易变换,所以我们一个不太经常变换的地址来标识主机,以便于那些网络上的交换设备能够找到它,这个就是MAC地址,一般MAC地址在设备出厂的时候就设置了(其实MAC地址可以修改,其实有了IP地址为什么还要MAC地址的这段表述不是很严谨)。
7 | Oh,继续我们的那个风铃草和MT同学要好好玩耍的故事,电话局接通了两个家里的电话信号,此时MT同学父母开始讲话了“喂,您好,是风铃草父母吗?”这段话,是怎么发送到风铃草同学父母那的呢?我们都知道同轴光缆只能传输光信号,光信号与我们0和1的编码就像如下所示:
8 | 
9 |
10 | 我们要传输的任何内容都会转化成二进制的比特流,这个二进制流会经过类似我们电话系统中的调制解调器一样,转换成光信号。对端再反调解析就行了,当然我们可以使用光缆(比如海底光缆)传输信号,也可以使用电缆传输信号(比如我们的网线),只不过后者有信号衰减,中间需要安装一个特殊的设备就是中继器,事实上,我们也不会用电缆传输很远,一般最长不超过100米。再者,我们的物理层有不同的设备,有不同的设备就会有不同的电气标准,比如我们的以太网的LAN标准IEEE802.3,又比如我们的无线WLAN标准802.11、802.ac等等,那我们就需要通过的协议和标准让不同的厂家按照同样的标准来生产,也需要按照相关标准和协议去进行物理层信号的转换,这就是我们物理层协议干的事情。他们解决信号如何在物理设备上传输和解析,也既然我们能够转换成物理信号,那么我们在网络上转发数据,是不是只要这两层就够了呢,一个是上层应用层,一个就是物理层?
11 |
12 | 物理层仅仅负责将比特流转换成信号并传递给线缆(光缆、电缆、xxoo缆),既不知道信号要发送给谁,也不在意信号发送中比特信息是否会在某处丢失,因为是流式传输,所以也不知道应用层发送的一条消息的开始和结束,那么数据链路层恰好就是去解决这三个问题的。如何解决呢?
13 | 首先我们来看看数据链路层的协议内容:
14 | 
15 | * 8个字节的前导码:告诉对端链路层这是一帧数据的开始,标志了消息的开始和结束。
16 | * 6个字节目的地址(MAC地址):告诉这一帧是发到哪里去的。
17 | * 6个字节的源地址(MAC地址):告诉这一帧数据是谁发送的。
18 | * 2字节的类型:
19 | * 0x0800:ipv4协议
20 | * 0x0806:ARP协议
21 | * 0x86DD:ipv6协议
22 | * 4字节的FCS:Frame Check Sequence,帧校验序列,其实就是CRC校验,发送数据时计算数据部分FCS的值填充在FCS, 对端接收的时候也进行同样的计算,然后进行比较,如果一致则丢弃。
23 |
24 | 有同学可能会问,这个目的MAC地址从哪而来,因为我们在发送数据的只知道一个目的IP地址?说来话长,这就需要ARP协议了,ARP协议是啥呢?你可以这么理解,MT同学站在院子大喊一身:“风铃草同学,我想和你玩,你家的地址是什么”,那么如果风铃草同学住在附近,也听到了MT同学的野狼般的喊声,就回应:“我家住在天心阁小区4栋1单元3009。” 专业点儿讲,其实就是要知道目的IP地址对应的MAC地址,也就是ARP协议是通过网络层的逻辑IP地址找到对应的物理层的物理MAC地址的协议,它的工作方式,就是主机收到,广播(除网关外)发送ARP请求,如果其它端口认为匹配则回复IP和MAC地址,否则就丢弃不理会,如果都没有回复,则认为该IP与该网段不在同一个子网,此时就L2交换机向**默认网关**发送ARP请求,此时目的MAC地址就是默认网关的MAC地址。简单一句话,就是APR是询问IP地址对应的MAC地址。当然还有一种就是GARP协议。
25 |
26 | 有了源MAC地址和目的MAC地址,我们只是知道了地址,但是我们并不知道从哪个网口发送出去,其实在每个通信的设备上必须建立一个mac与网口的对应表,怎么建立的呢? 我们课上在分享。
27 |
28 | 通过上面,我们可以知道数据链路层只是将同一网段中的节点连接起来而已,如果我们需要连接国外的WEB服务器,由于网段不同,在数据链路层这个层面是无法连接起来的。也就是说如果,如果风铃草同学,住在MT同学附近,那么MT同学大喊一身风铃草是知道的并能回应,如果风铃草同学住的很远,那就没有办法用“大喊”一声的方式建立通信的。那网络层是如何把一个个不同的网段连接成一个大的“互联网”的呢?
29 | 我们先来看网络层对数据做了那些封装呢?
30 | 
31 | 其它的都不管,在网络层加了IP地址信息,就是有了源IP地址和目的IP地址,IP地址是一个逻辑地址,IP地址是由4个字节32位组成(ipv4),当然开篇我们就介绍了有IPv6地址。比如IP地址有192.168.1.1,IP地址都是由两个部分组成,一个是网络部分,一个是主机部分,他们是使用子网掩码区分的,比如子网掩码是255.255.0.0的话,那么网络部分就是192.168,而主机部分就是1.1,网络部分说明的网段,主机部分标明了主机所在网段的那一台主机。当然,IP地址会有一些特殊的地址值存在,如:
32 | * 广播地址:如上面的192.168.255.255就是广播地址,用来发送广播数据的。
33 | * 环回地址:比如127.0.0.1就是一个环回地址,实际上只要第一个字节是127就可以,后面是啥都无所谓。如果我们是发送一个环回IP地址的话,我们可以用wirshark去抓数据看看,实际上是抓不到数据的。
34 |
35 | 有了IP地址,那我们是如何从一个网段发送到另外一个网段的呢?这里就不得不提路由表和L3交换机的了,在L3交换机中保存了一个路由表,比如下文要把1.1.1.1/24节点的数据发送给3.3.3.3/24节点,那么他们的发送是通过路由表找到下一跳的位置。如图:
36 | <<<<<<< HEAD
37 | 
38 | =======
39 | 
40 | >>>>>>> ed61b1b024acc64bd7b9d41ddc30e23b20cae365
41 |
42 | OK,我们总结一下,L2交换机利用MAC地址表解决了同一网段的交换,L3交换机利用了ARP缓存信息表和路由表解决了跨网段的数据交换。在通信过程了,目的IP地址一直不变化,但是源IP地址会不断修改为当前发送的节点(包含交换设备),MAC地址每跨一个网段就要去变化一次。
43 | 也许很多同学还会疑问,我们的L3交换机上的路由表是如何建立的呢?
44 | * 手动建立:就是手工去交换机上建立路由表,但是这种方式对于大型网络并不好,因为如果下一跳的地址信息发生变化或者有新增网段,我们就必须去修改本端的路由表。
45 | * 自动建立:使用路由协议。Oh,如何使用路由协议去建立路由表就不细说了,总之跟我们上面提到的度量值是有关系的,而且有多重路由协议。
46 |
47 | 有了路由协议和ARP协议是不是就是万事大吉了呢?比如,我现在主机的ip地址是192.168.1.109,我们要想要阿里云服务47.106.79.26发送数据,47.106.79.26是一个公网IP地址,这个我们是能通的,但是47.106.79.26的数据如果在网络层填写的目的IP为192.168.1.109,这样子数据能到达我这里吗?其实有一个路由跟踪命令traceroute,我现在本地192.168.1.109上敲入traceroute 47.106.79.26,结果显示如下:
48 | 
49 | 但是我在阿里云上去traceroute 192.168.1.109显示却如下:
50 | 
51 |
52 | 这里就必须谈到了一个协议,就是NAT,地址转换协议,这个协议,其实也是节省了公网IP地址资源。好了,这个我们在后面单独列一堂课来讲。
53 |
54 | 在网络层还有一种很重要的协议就是DHCP协议,DHCP协议是用来给尚未分配IP地址的机器分配IP地址的,怎么做的呢?简单理解下,就是MT同学打喊一声,我没有IP地址,谁可以给我分配一个IP地址,那么收到的那一方DHCP服务器就给MT同学分配一个IP地址。特别要强调的是,因为还没有一个IP地址,所以只能去广播一个分配IP地址的请求。
55 |
56 | 有了网络层,我们解决了主机与主机之间的通信,但是如果我们同一台主机有多个应用程序,那么返回的数据到底由那个应用程序接收呢?这是网络层解决不了的,如果网络上有丢包或者乱序,这也是网络层解决不了的,那这些问题如何解决呢?
57 | 首先在传输层定义了一个端口号,这个端口号与一个进程进行了绑定,也就是识别了进程从哪个端口发送数据出去,比如我们来看看传输层的TCP协议和UDP协议的报头:
58 | 
59 |
60 | 
61 |
--------------------------------------------------------------------------------
/chapter14/14.2_网络7层协议与4层协议.md:
--------------------------------------------------------------------------------
1 | # 14.2 网络7层协议与4层协议
2 |
3 | 从OSI的角度来看看7层协议:
4 | 
5 |
6 | 从TCP/IP概念角度分的4层协议:
7 | 
--------------------------------------------------------------------------------
/chapter14/14_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_1.png
--------------------------------------------------------------------------------
/chapter14/14_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_10.png
--------------------------------------------------------------------------------
/chapter14/14_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_11.png
--------------------------------------------------------------------------------
/chapter14/14_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_2.png
--------------------------------------------------------------------------------
/chapter14/14_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_3.png
--------------------------------------------------------------------------------
/chapter14/14_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_4.png
--------------------------------------------------------------------------------
/chapter14/14_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_5.png
--------------------------------------------------------------------------------
/chapter14/14_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_6.png
--------------------------------------------------------------------------------
/chapter14/14_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_7.png
--------------------------------------------------------------------------------
/chapter14/14_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_8.png
--------------------------------------------------------------------------------
/chapter14/14_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter14/14_9.png
--------------------------------------------------------------------------------
/chapter14/README.md:
--------------------------------------------------------------------------------
1 | # 网络协议
2 |
3 | ## 写在前面
4 | 网络上有很多介绍网络协议的文章,也有很多专业的书籍去介绍,但是看过很多后,我归纳总结一句话:TMD,太专业了,让人难以理解,不符合我等。这篇,打算尽我最大努力来用最直白的话跟大家讲个大概。
5 |
--------------------------------------------------------------------------------
/chapter15/15.1_socket接口介绍.md:
--------------------------------------------------------------------------------
1 | # 15.1 socket接口介绍
2 |
3 | * [socket函数](#socket)
4 | * [bind函数](#bind)
5 | * [listen函数](#listen)
6 | * [connect函数](#connect)
7 | * [accept函数](#accept)
8 | * [send和recv函数](#send_recv)
9 |
10 | socket函数
11 |
12 | socket()函数的原型如下,这个函数建立一个协议族为domain、协议类型为type、协议编号为protocol的套接字文件描述符。如果函数调用成功,会返回一个标识这个套接字的文件描述符,失败的时候返回-1。
13 |
14 | ```
15 | #include /* See NOTES */
16 | #include
17 |
18 | int socket(int domain, int type, int protocol);
19 | ```
20 | * domain
21 | 函数socket()的参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。通信协议族在文件sys/socket.h中定义。
22 |
23 | | 名称 | 含义 | 名称 | 含义 |
24 | |--------|----------|--------|-----------|
25 | |PF_UNIX PF_LOCAL | 本地通信 | PF_X25 | ITU-T X25 / ISO-8208协议|
26 | |AF_INET,PF_INET | IPv4 Internet协议 | PF_AX25 | Amateur radio AX.25 |
27 | |PF_INET6 | IPv6 Internet协议 | PF_ATMPVC | 原始ATM PVC访问 |
28 | |PF_IPX | IPX-Novell协议 | PF_APPLETALK | Appletalk |
29 | |PF_NETLINK|内核用户界面设备|PF_PACKET| 底层包访问|
30 |
31 | * type
32 | 函数socket()的参数type用于设置套接字通信的类型,主要有SOCKET_STREAM(流式套接字)、SOCK——DGRAM(数据包套接字)等。
33 |
34 | | 名称 | 含义 |
35 | |--------|---------|
36 | |SOCK_STREAM| Tcp连接,提供序列化的、可靠的、双向连接的字节流。支持带外数据传输 |
37 | |SOCK_DGRAM |支持UDP连接(无连接状态的消息)|
38 | |SOCK_SEQPACKET | 序列化包,提供一个序列化的、可靠的、双向的基本连接的数据传输通道,数据长度定常。每次调用读系统调用时数据需要将全部数据读出 |
39 | |SOCK_RAW | RAW类型,提供原始网络协议访问|
40 | |SOCK_RDM | 提供可靠的数据报文,不过可能数据会有乱序 |
41 |
42 | 并不是所有的协议族都实现了这些协议类型,例如,AF_INET协议族就没有实现SOCK_SEQPACKET协议类型。
43 |
44 | * protocol
45 | 函数socket()的第3个参数protocol用于制定某个协议的特定类型,即type类型中的某个类型。通常某协议中只有一种特定类型,这样protocol参数仅能设置为0;但是有些协议有多种特定的类型,就需要设置这个参数来选择特定的类型。
46 |
47 | * errno
48 | 函数socket()并不总是执行成功,有可能会出现错误,错误的产生有多种原因,可以通过errno获得:
49 |
50 | |值 |含义|
51 | |-----|------|
52 | |EACCES| 没有权限建立制定的domain的type的socket |
53 | |EAFNOSUPPORT | 不支持所给的地址类型 |
54 | |EINVAL | 不支持此协议或者协议不可用 |
55 | |EMFILE | 进程文件表溢出 |
56 | |ENFILE |已经达到系统允许打开的文件数量,打开文件过多|
57 | |ENOBUFS/ENOMEM| 内存不足。socket只有到资源足够或者有进程释放内存|
58 | |EPROTONOSUPPORT| 制定的协议type在domain中不存在|
59 |
60 | 比如我们建立一个流式套接字可以这样:
61 | ```
62 | int sock = socket(AF_INET, SOCK_STREAM, 0);
63 | ```
64 |
65 | bind函数
66 |
67 | 在套接口中,一个套接字只是用户程序与内核交互信息的枢纽,它自身没有太多的信息,也没有网络协议地址和 端口号等信息,在进行网络通信的时候,必须把一个套接字与一个地址相关联,这个过程就是地址绑定的过程。许多时候内核会我们自动绑定一个地址,然而有时用 户可能需要自己来完成这个绑定的过程,以满足实际应用的需要,最典型的情况是一个服务器进程需要绑定一个众所周知的地址或端口以等待客户来连接。这个事由 bind的函数完成。
68 | ```
69 | int bind( int sockfd, struct sockaddr* addr, socklen_t addrlen)
70 | ```
71 | * sockfd
72 | 就是我们调用socket函数后创建的socket 句柄或者称文件描述符号。
73 | * addr
74 | addr是指向一个结构为sockaddr参数的指针,sockaddr中包含了地址、端口和IP地址的信息。在进行地址绑定的时候,需要弦将地址结构中的IP地址、端口、类型等结构struct sockaddr中的域进行设置之后才能进行绑定,这样进行绑定后才能将套接字文件描述符与地址等接合在一起。
75 | 由于历史原因,我们前后有两个地址结构:
76 | **struct sockaddr**
77 | 该结构定义如下:
78 | ```
79 | struct sockaddr {
80 | uint8_t sa_len;
81 | unsigned short sa_family; /* 地址家族, AF_xxx */
82 | char sa_data[14]; /*14字节协议地址*/
83 | };
84 |
85 | ```
86 | 其实这个结构逐渐被舍弃,但是也还是因为历史原因,在很多的函数,比如connect、bind等还是用这个作为声明,实际上现在用的是第二个结构,我们需要把第二个结构强转成sockaddr。
87 | **struct sockaddr_in**
88 | 其定义如下:
89 | ```
90 | struct sockaddr_in {
91 | uint8_t sa_len; /* 结构体长度*/
92 | short int sin_family; /* 通信类型 */
93 | unsigned short int sin_port; /* 端口 */
94 | struct in_addr sin_addr; /* Internet 地址 */
95 | unsigned char sin_zero[8]; /* 未使用的*/
96 | };
97 |
98 | struct in_addr { //sin_addr的结构体类型in_addr 原型
99 | unsigned long s_addr; /*存4字节的 IP 地址(使用网络字节顺序)。*/
100 | };
101 |
102 | ```
103 | 在使用的时候我们必须指定通信类型,也必须把端口号和地址转换成网络序的字节序(这是什么意思呢?)。
104 |
105 | * addrlen
106 | addr结构的长度,可以设置成sizeof(struct sockaddr)。使用sizeof(struct sockaddr)来设置套接字的类型和其对已ing的结构。
107 |
108 | bind()函数的返回值为0时表示绑定成功,-1表示绑定失败,errno的错误值如表1所示。
109 |
110 | |值 | 含义| 备注 |
111 | |----|---------|-------|
112 | |EADDRINUSE | 给定地址已经使用
113 | |EBADF| sockfd不合法
114 | |EINVAL| sockfd已经绑定到其他地址
115 | |ENOTSOCK| sockfd是一个文件描述符,不是socket描述符
116 | |EACCES |地址被保护,用户的权限不足
117 | |EADDRNOTAVAIL| 接口不存在或者绑定地址不是本地| UNIX协议族,AF_UNIX
118 | |EFAULT |my_addr指针超出用户空间 |UNIX协议族,AF_UNIX
119 | |EINVAL |地址长度错误,或者socket不是AF_UNIX族| UNIX协议族,AF_UNIX
120 | |ELOOP| 解析my_addr时符号链接过多| UNIX协议族,AF_UNIX
121 | |ENAMETOOLONG| my_addr过长| UNIX协议族,AF_UNIX
122 | |ENOENT| 文件不存在| UNIX协议族,AF_UNIX
123 | |ENOMEN| 内存内核不足| UNIX协议族,AF_UNIX
124 | |ENOTDIR| 不是目录| UNIX协议族,AF_UNIX
125 |
126 | 比如这样:
127 | ```
128 | struct sockaddr_in addr;
129 | memset(&addr, 0, sizeof(struct sockaddr_in));
130 |
131 | addr.sin_family = AF_INET;
132 | addr.sin_port = htons(port);
133 | addr.sin_addr.s_addr = INADDR_ANY;
134 |
135 | if (bind(sfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0)
136 | {
137 | perror("bind");
138 | exit(1);
139 | }
140 | ```
141 |
142 | bind函数
143 |
144 | ```
145 | int listen(int sockfd, int backlog);
146 |
147 | ```
148 | listen()函数将sockfd标记为被动打开的套接字,并作为accept的参数用来接收到达的连接请求。
149 |
150 | * sockfd是一个套接字类型的文件描述符,具体类型为SOCK_STREAM或者SOCK_SEQPACKET。
151 |
152 | * backlog参数用来描述sockfd的等待连接队列能够达到的最大值。当一个请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,或者如果底层协议支持重传(比如tcp协议),本次请求会被丢弃不作处理,在下次重试时期望能连接成功(下次重传的时候队列可能已经腾出空间)。
153 |
154 | 说起这个backlog就有一点儿历史了,等下文描述。
155 |
156 | * errno
157 |
158 | |值 | 含义 |
159 | |----|--------|
160 | | EADDRINUSE | 另一个套接字已经绑定在相同的端口上。|
161 | |EBADF | 参数sockfd不是有效的文件描述符。|
162 | |ENOTSOCK | 参数sockfd不是套接字。|
163 | |EOPNOTSUPP | 参数sockfd不是支持listen操作的套接字类型。|
164 |
165 | connect函数
166 |
167 | **声明如下**
168 | ```
169 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
170 | ```
171 | **参数说明如下**
172 | * sockfd 是系统调用 socket() 返回的套接字文件描述符。
173 | * serv_addr 是 保存着目的地端口和 IP 地址的数据结构 struct sockaddr_in。
174 | * addrlen 设置 为 sizeof(struct sockaddr_in)
175 |
176 | **errno**
177 | connect函数在调用失败的时候返回值-1,并会设置全局错误变量 errno。
178 | |值 | 含义|
179 | |----|------|
180 | |EBADF| 参数sockfd 非合法socket处理代码|
181 | |EFAULT| 参数serv_addr指针指向无法存取的内存空间|
182 | |ENOTSOCK| 参数sockfd为一文件描述词,非socket。|
183 | |EISCONN |参数sockfd的socket已是连线状态|
184 | |ECONNREFUSED |连线要求被server端拒绝。|
185 | |ETIMEDOUT| 企图连线的操作超过限定时间仍未有响应。|
186 | |ENETUNREACH |无法传送数据包至指定的主机。|
187 | |EAFNOSUPPORT |sockaddr结构的sa_family不正确。|
188 |
189 | accept函数
190 | **函数声明**
191 |
192 | ```
193 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
194 |
195 | ```
196 | **参数说明**
197 | sockfd是由socket函数返回的套接字描述符,参数addr和addrlen用来返回已连接的对端进程(客户端)的协议地址。如果我们对客户端的协议地址不感兴趣,可以把arrd和addrlen均置为空指针。
198 |
199 | **返回值**
200 |
201 | 成功时,返回非负整数,该整数是接收到套接字的描述符;出错时,返回-1,相应地设定全局变量errno。
202 |
203 | |值 | 含义 |
204 | |----|--------|
205 | |EBADF | 非法的socket|
206 | |EFAULT | 参数addr指针指向无法存取的内存空间|
207 | |ENOTSOCK | 参数s为一文件描述词,非socket|
208 | |EOPNOTSUPP |指定的socket并非SOCK_STREAM|
209 | |EPERM |防火墙拒绝此连线|
210 | |ENOBUFS | 系统的缓冲内存不足|
211 | |ENOMEM | 核心内存不足|
212 |
213 | 特别需要说明下的是,这个accept是一个阻塞式的函数,对于一个阻塞的套套接字,一直阻塞,或者返回一个错误值,对于非阻塞套接字。accept有可能返回-1,但是如果errno的值为,EAGAIN或者EWOULDBLOCK,此时需要重新调用一次accept函数。
214 |
215 | send和recv函数
216 |
217 | **函数申明如下**
218 |
219 | ```
220 | ssize_t recv(int sockfd, void *buf, size_t len, int flags);
221 | ssize_t send(int sockfd, const void *buf, size_t len, int flags);
222 | ```
223 | * sockfd :套接字
224 | * buf : 待发送或者接收的缓存
225 | * len : 如果是recv指期望接收的长度,如果是send指要发送的长度。
226 | * flags : 标志位,取值如下表:
227 |
228 | |flags | 说明 | recv | send |
229 | |-------|-----------|--------|---------|
230 | | MSG_DONTROUTE | 绕过路由表查找 | | • |
231 | | MSG_DONTWAIT | 仅本操作非阻塞 | • | •|
232 | | MSG_OOB | 发送或接收带外数据 | • | •|
233 | | MSG_PEEK | 窥看外来消息 | • | |
234 | | MSG_WAITALL | 等待所有数据 | • |
235 |
236 | **errno**
237 | | 值 | 含义 |
238 | |-----|-------|
239 | |EAGAIN | 套接字已标记为非阻塞,而接收操作被阻塞或者接收超时 |
240 | |EBADF | sock不是有效的描述词 |
241 | |ECONNREFUSE | 远程主机阻绝网络连接 |
242 | | EFAULT | 内存空间访问出错 |
243 | |EINTR |操作被信号中断 |
244 | |EINVAL |参数无效 |
245 | |ENOMEM |内存不足 |
246 | |ENOTCONN |与面向连接关联的套接字尚未被连接上 |
247 | |ENOTSOCK|sock索引的不是套接字 当返回值是0时,为正常关闭连接;|
248 |
249 | 当返回值为-1时是不是一定就错误了,当返回值为0时该怎么做呢?
250 | 如何正确判断一个对端已经关闭了连接?
--------------------------------------------------------------------------------
/chapter15/15.2_socket选项介绍.md:
--------------------------------------------------------------------------------
1 | # 15.2 TCP选项
2 |
3 | 可参见链接地址:
4 | [tcp选项](https://blog.csdn.net/ctthuangcheng/article/details/25595117)
--------------------------------------------------------------------------------
/chapter15/15.3_socket编程实现.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15.3_socket编程实现.md
--------------------------------------------------------------------------------
/chapter15/15.4_TCP原理介绍.md:
--------------------------------------------------------------------------------
1 | # 15.4 TCP原理介绍
2 |
3 | * [TCP协议头解释](#tcpheader)
4 | * [TCP建立连接的三次握手](#tcp3handshak)
5 | * [TCP连接断开的四次挥手](#tcp4bye)
6 | * [TCP的状态机](#tcp_state)
7 | * [listen时的指定的backlog含义](#listen)
8 |
9 |
10 |
11 |
12 | tcp协议头格式如下:
13 |
14 | 
15 |
16 | **详细解析**
17 | 16位源端口号和16位目的端口号。
18 | 32位序号:一次TCP通信过程中某一个传输方向上的字节流的每个字节的编号,通过这个来确认发送的数据有序,比如现在序列号为1000,发送了1000,下一个序列号就是2000。
19 | 32位确认号:用来响应TCP报文段,给收到的TCP报文段的序号加1,三握时还要携带自己的序号。
20 | 4位头部长度:标识该TCP头部有多少个4字节,共表示最长15\*4=60字节。同IP头部。
21 | 6位保留。6位标志。URG(紧急指针是否有效)ACK(表示确认号是否有效)PSH(提示接收端应用程序应该立即从TCP接收缓冲区读走数据)RST(表示要求对方重新建立连接)SYN(表示请求建立一个连接)FIN(表示通知对方本端要关闭连接)
22 | 16位窗口大小:TCP流量控制的一个手段,用来告诉对端TCP缓冲区还能容纳多少字节。
23 | 16位校验和:由发送端填充,接收端对报文段执行CRC算法以检验TCP报文段在传输中是否损坏。
24 | 16位紧急指针:一个正的偏移量,它和序号段的值相加表示最后一个紧急数据的下一字节的序号。
25 |
26 | TCP建立连接的三次握手
27 |
28 | 
29 |
30 |
31 | 为什么需要三次握手?
32 |
33 | **三次握手:**
34 | “喂,你听得到吗?”
35 | “我听得到呀,你听得到我吗?”
36 | “我能听到你,今天balabala……”
37 |
38 | **两次握手:**
39 | “喂,你听得到吗?”
40 | “我听得到呀”
41 | “喂喂,你听得到吗?”
42 | “草,我听得到呀!!!!”
43 | “你TM能不能听到我讲话啊!!喂!”
44 | “……”
45 |
46 | **四次握手:**
47 | “喂,你听得到吗?”
48 | “我听得到呀,你听得到我吗?”
49 | “我能听到你,你能听到我吗?”
50 | “……不想跟傻逼说话”
51 |
52 | TCP作为一种可靠传输控制协议,其核心思想:**既要保证数据可靠传输,又要提高传输的效率**,而用三次恰恰可以满足以上两方面的需求!TCP可靠传输的精髓:TCP连接的一方A,由操作系统动态随机选取一个32位长的序列号(Initial Sequence Number),假设A的初始序列号为1000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,1001,1002,1003…,并把自己的初始序列号ISN告诉B,让B有一个思想准备,什么样编号的数据是合法的,什么编号是非法的,比如编号900就是非法的,同时B还可以对A每一个编号的字节数据进行确认。如果A收到B确认编号为2001,则意味着字节编号为,共1000个字节已经安全到达。同理B也是类似的操作,假设B的初始序列号ISN为2000,以该序列号为原点,对自己将要发送的每个字节的数据进行编号,2001,2002,2003…,并把自己的初始序列号ISN告诉A,以便A可以确认B发送的每一个字节。如果B收到A确认编号为4001,则意味着字节编号为,共2000个字节已经安全到达。
53 | **一句话概括,TCP连接握手,握的是啥?通信双方数据原点的序列号!**
54 |
55 | TCP连接断开的四次挥手
56 |
57 | 
58 |
59 | TCP连接的释放一共需要四步,因此称为『四次挥手』。
60 | 我们知道,TCP连接是双向的,因此在四次挥手中,前两次挥手用于断开一个方向的连接,后两次挥手用于断开另一方向的连接。
61 | * 第一次挥手
62 | 若A认为数据发送完成,则它需要向B发送连接释放请求。该请求只有报文头,头中携带的主要参数为: FIN=1,seq=u。此时,A将进入FIN-WAIT-1状态。PS1:FIN=1表示该报文段是一个连接释放请求。PS2:seq=u,u-1是A向B发送的最后一个字节的序号。
63 | * 第二次挥手
64 | B收到连接释放请求后,会通知相应的应用程序,告诉它A向B这个方向的连接已经释放。此时B进入CLOSE-WAIT状态,并向A发送连接释放的应答,其报文头包含: ACK=1,seq=v,ack=u+1。PS1:ACK=1:除TCP连接请求报文段以外,TCP通信过程中所有数据报的ACK都为1,表示应答。PS2:seq=v,v-1是B向A发送的最后一个字节的序号。PS3:ack=u+1表示希望收到从第u+1个字节开始的报文段,并且已经成功接收了前u个字节。A收到该应答,进入FIN-WAIT-2状态,等待B发送连接释放请求。第二次挥手完成后,A到B方向的连接已经释放,B不会再接收数据,A也不会再发送数据。但B到A方向的连接仍然存在,B可以继续向A发送数据。
65 | * 第三次挥手
66 | 当B向A发完所有数据后,向A发送连接释放请求,请求头:FIN=1,ACK=1,seq=w,ack=u+1。B便进入LAST-ACK状态。
67 | * 第四次挥手
68 | A收到释放请求后,向B发送确认应答,此时A进入TIME-WAIT状态。该状态会持续2MSL时间,若该时间段内没有B的重发请求的话,就进入CLOSED状态,撤销TCB。当B收到确认应答后,也便进入CLOSED状态,撤销TCB。
69 | * 为什么A要先进入TIME-WAIT状态,等待2MSL时间后才进入CLOSED状态?
70 | 为了保证B能收到A的确认应答。 若A发完确认应答后直接进入CLOSED状态,那么如果该应答丢失,B等待超时后就会重新发送连接释放请求,但此时A已经关闭了,不会作出任何响应,因此B永远无法正常关闭。
71 |
72 | TCP的状态机
73 |
74 | 
75 |
76 | [TCP的状态机](https://www.cnblogs.com/yhp-smarthome/p/7102488.html)
77 |
78 |
79 | listen时的指定的backlog含义
80 |
81 | 我们先来看下listen的声明:
82 | ```
83 | int listen(int sockfd, int backlog);
84 | ```
85 | 有关于第二个参数含义的问题网上有好几种说法,我总结了下主要有这么3种:
86 |
87 | * Kernel会为LISTEN状态的socket维护一个队列,其中存放SYN RECEIVED和ESTABLISHED状态的套接字,backlog就是这个队列的大小。
88 | * Kernel会为LISTEN状态的socket维护两个队列,一个是SYN RECEIVED状态,另一个是ESTABLISHED状态,而backlog就是这两个队列的大小之和。
89 | * 第三种和第二种模型一样,但是backlog是队列ESTABLISHED的长度。
90 |
91 | 有关上面说的两个状态SYN RECEIVED状态和ESTABLISHED状态,是TCP三次握手过程中的状态转化,具体可以参考下面的图(在新窗口打开图片):
92 | 
93 |
94 | 当一个应用使用listen系统调用让socket进入LISTEN状态时,它需要为该套接字指定一个backlog。backlog通常被描述为连接队列的限制。
95 | 由于TCP使用的3次握手,连接在到达ESTABLISHED状态之前经历中间状态SYN RECEIVED,并且可以由accept系统调用返回到应用程序。这意味着TCP / IP堆栈有两个选择来为LISTEN状态的套接字实现backlog队列:
96 | 1:使用单个队列实现,其大小由listen syscall的backlog参数确定。 当收到SYN数据包时,它发送回SYN/ACK数据包,并将连接添加到队列。 当接收到相应的ACK时,连接将其状态改变为已建立。 这意味着队列可以包含两种不同状态的连接:SYN RECEIVED和ESTABLISHED。 只有处于后一状态的连接才能通过accept syscall返回给应用程序。
97 |
98 | 2 : 使用两个队列实现,一个SYN队列(或半连接队列)和一个accept队列(或完整的连接队列)。 处于SYN RECEIVED状态的连接被添加到SYN队列,并且当它们的状态改变为ESTABLISHED时,即当接收到3次握手中的ACK分组时,将它们移动到accept队列。 显而易见,accept系统调用只是简单地从完成队列中取出连接。 在这种情况下,listen syscall的backlog参数表示完成队列的大小。
99 |
100 | 历史上,BSD 派生系统实现的TCP使用第一种方法。 该选择意味着当达到最大backlog时,系统将不再响应于SYN分组发送回SYN/ACK分组。 通常,TCP的实现将简单地丢弃SYN分组,使得客户端重试。
101 |
102 | 在Linux上,是和上面不同的。如在listen系统调用的手册中所提到的:
103 | 在Linux内核2.2之后,socket backlog参数的形为改变了,现在它指等待accept的完全建立的套接字的队列长度,而不是不完全连接请求的数量。 不完全连接的长度可以使用/proc/sys/net/ipv4/tcp_max_syn_backlog设置。这意味着当前Linux版本使用上面第二种说法,有两个队列:具有由系统范围设置指定的大小的SYN队列 和 应用程序(也就是backlog参数)指定的accept队列。
104 |
105 | OK,说到这里,相信backlog含义已经解释的非常清楚了,下面我们用实验验证下这种说法:
106 |
107 | OK,这个著名的设计,给我们带来了很大的麻烦,这个麻烦就是DDOS攻击。
108 | 可参见链接:
109 | 
--------------------------------------------------------------------------------
/chapter15/15_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15_5.png
--------------------------------------------------------------------------------
/chapter15/15_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15_6.png
--------------------------------------------------------------------------------
/chapter15/15_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15_7.png
--------------------------------------------------------------------------------
/chapter15/15_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15_8.png
--------------------------------------------------------------------------------
/chapter15/15_9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter15/15_9.png
--------------------------------------------------------------------------------
/chapter16/16.1_UDP协议介绍.md:
--------------------------------------------------------------------------------
1 | # 16.1 UDP协议介绍
2 |
3 | UDP协议分为首部字段和数据字段,其中首部字段只占用8个字节,分别是个占用两个字节的源端口、目的端口、长度和检验和。具体协议字段信息如下:
4 | 
5 |
6 | * 长度:UDP报文的整个大小,最小为8个字节(仅为首部)
7 | * 检验和:在进行检验和计算时,会添加一个伪首部一起进行运算。伪首部(占用12个字节)为:4个字节的源IP地址、4个字节的目的IP地址、1个字节的0、一个字节的数字17、以及占用2个字节UDP长度。这个伪首部不是报文的真正首部,只是引入为了计算校验和。相对于IP协议的只计算首部,UDP检验和会把首部和数据一起进行校验。接收端进行的校验和与UDP报文中的校验和相与,如果无差错应该全为1。如果有误,则将报文丢弃或者发给应用层、并附上差错警告。
8 |
9 |
--------------------------------------------------------------------------------
/chapter16/16.2_UDP的实现.md:
--------------------------------------------------------------------------------
1 | # 16.2 UDP的实现
2 |
3 | 另外两个API接口:
4 |
5 | ```
6 | ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
7 |
8 | ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
9 | ```
10 |
11 | * dest_addr : 是接收端的IP地址和端口信息;
12 | * src_addr : 是发送端的IP地址和端口信息;
13 |
--------------------------------------------------------------------------------
/chapter16/16.3_UDP的connect的意义.md:
--------------------------------------------------------------------------------
1 | # 16.3 UDP的connect的意义
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/chapter16/16_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter16/16_1.jpg
--------------------------------------------------------------------------------
/chapter16/README.md:
--------------------------------------------------------------------------------
1 | # 16 UDP通信
2 |
3 | 我们很多同学也知道,TCP是可靠的,UDP是不可靠的,那既然不可靠,为什么有了TCP后,还需要UDP?
4 | 事实上,TCP和IP协议是差不多同时诞生的,随着后面不断的开发,人们逐渐认识到TCP和IP是独立的,对于很多开发我们只需要像IP这种协议一样做到“尽可能传输”就可以了,不需要像TCP协议去做很多可靠性的保障,于是UDP协议应运而生,它只是在IP协议上添加了端口信息而已,然后就通过网络层发送出去,不管包是否到达,是否乱序,是否重传,所有的这些都由应用层去实现,或者不实现,这是十分有必要的,比如视频传输,我们丢失了一些帧或者一部分,再去做重传是没有什么意义的,因为下一个完整帧立马就会发送过来了。UDP协议比起TCP协议来说最大的优势就是快,天下武功为快不破嘛!!!
--------------------------------------------------------------------------------
/chapter17/17.1_什么是长连接和短连接.md:
--------------------------------------------------------------------------------
1 | # 17.1 什么是长连接和短连接
2 |
3 |
4 | * [短连接](#shortconnect)
5 | * [长连接](#longconnect)
6 | * [长短连接的适用情况](#proper)
7 |
8 |
9 | 短连接
10 | 短连接:客户端与服务器建立连接 --> 客户端发送数据 --> 服务器回应数据 --> 客户端与服务器关闭连接 --> ·········· 建立连接 -->发送数据 --> 回应数据 --> 关闭连接。用个生活中的例子就是,我寂寞难耐时,就去谈谈上约一次,如果约不着就去大保健,爽完付费走人,要想爽随时都可以来。
11 |
12 |
13 | 短连接的优点是什么:
14 | * 不长期占用服务器的内存,那么服务器能处理的连接数量是比较多的。
15 |
16 | 短连接的缺点是什么:
17 | * 因为等到要发送数据或者获取资源时,才去请求建立连接发送数据,否则就是端开连接的,那么如果服务器要想往客户端发送数据时怎么办?凉伴,没有任何办法,或者要等到下一次要请求数据时,才发送,比如我们采用轮询(30秒或者更长)拉取消息,那么服务器与客户端通信的实时性就丧失了。
18 | * 客户端采用轮询来实时获取信息,或者说大量的客户端使用短连接的方式通信,那么就浪费了大量的CPU和带宽资源用于建立连接和释放连接,存在资源浪费。比如经典的http长轮询(微信网页客户端端)。
19 |
20 | 长连接
21 | 长连接:客户端与服务器一直保持这种连接,客户端和服务端一直不关闭这个连接,这种连接方式叫做TCP长连接。那如何正确地能保持一个连接不关闭呢?下一个小节会讲到,我们先来讨论这种设计的优缺点吧。
22 |
23 | 优点:
24 | * 传输数据快
25 | * 服务器能够主动第一时间传输数据到客户端
26 |
27 | 缺点:
28 | * 因为客户端与服务器一直保持这种连接,那么在高并发分布式集群系统中客户端数量会越来越多,占用很多的系统资源。
29 | * TCP本身是一种有状态的数据,在高并发分布式系统会导致后台设计比较难做。
30 |
31 | 长短连接的适用情况
32 |
33 |
34 | 思考题:我们共享单车应该采用长连接还是短连接?
35 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/chapter17/17.2_keepalive机制.md:
--------------------------------------------------------------------------------
1 | # 17.2 keepalive机制
2 |
3 | 先来一个问题,TCP连接一旦建立后,是不是这个连接可以一直保持?
4 | 答案是否定的,操作系统在实现TCP协议的时候都做了一个限制,这个限制可以参考配置:
5 |
6 | ```
7 | cat /proc/sys/net/ipv4/tcp_keepalive_time
8 | cat /proc/sys/net/ipv4/tcp_keepalive_intvl
9 | cat /proc/sys/net/ipv4/tcp_keepalive_probes
10 | ```
11 | 我们看到默认这个tcp_keepalive_time的值为7200s,也就是2个小时,这个值代表如果TCP连接发送完最后一个ACK包后,如果超过2个小时,没有数据往来,那么这个连接会断掉。那么我们如何才能保持住这个连接呢?实际上,这就是TCP的keepalive机制,哦,说法不严谨,TCP协议并没有规定如此,但是很多的操作系统内核实现TCP协议时,都加上了这个keepalive机制,那么这个功能默认是关闭的,那这个keepalive机制到底是如何的呢?也就是,如果TCP之间没有任何数据来往了在tcp_keepalive_time(7200s,2h)后,服务器给客户端发送一个探测包,如果对方有回应,说明这个连接还存活,否则继续每隔tcp_keepalive_intvl(默认为75s)给对方发送探测包,如果连续tcp_keepalive_probes(默认为9)次后,依然没有收到对端的回复,那么则认为这个连接已经关闭。
12 |
13 | tcp keepalive默认不是开启的,如果想使用KeepAlive,需要在你的应用中设置SO_KEEPALIVE才可以生效。当然事先我们可以修改这些默认值,方法如下:
14 |
15 | 在Linux中我们可以通过修改 /etc/sysctl.conf 的全局配置:
16 | ```
17 | net.ipv4.tcp_keepalive_time=7200
18 | net.ipv4.tcp_keepalive_intvl=75
19 | net.ipv4.tcp_keepalive_probes=9
20 | ```
21 | 添加上面的配置后输入 sysctl -p 使其生效,你可以使用 sysctl -a | grep keepalive 命令来查看当前的默认配置
22 |
23 |
24 | tcp keepalive可以通过设置TCP选项设置,设置方法如下:
25 | ```
26 | #include
27 |
28 | int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);
29 |
30 | ```
31 | 我们在需要使能Keepalive的socket上面调用setsockopt函数便可以打开该socket上面的keepalive。
32 |
33 | 第一个参数是要设置的套接字
34 | 第二个参数是SOL_SOCKET
35 | 第三个参数必须是SO_KEEPALIVE
36 | 第四个参数必须是一个布尔整型值,0表示关闭,1表示打开
37 | 最后一个参数是第四个参数值的大小。
38 |
39 | 示例:
40 | ```
41 | int keepalive = 1;
42 | setsockopt(incomingsock,SOL_SOCKET,SO_KEEPALIVE,(void*)(&keepalive),(socklen_t)sizeof(keepalive));
43 |
44 | int keepalive_time = 30;
45 | setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPIDLE,(void*)(&keepalive_time),(socklen_t)sizeof(keepalive_time));
46 | int keepalive_intvl = 3;
47 | setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPINTVL,(void*)(&keepalive_intvl),(socklen_t)sizeof(keepalive_intvl));
48 | int keepalive_probes= 3;
49 | setsockopt(incomingsock, IPPROTO_TCP, TCP_KEEPCNT,(void*)(&keepalive_probes),(socklen_t)sizeof(keepalive_probes));
50 | 设置SO_KEEPALIVE选项来开启KEEPALIVE,然后通过TCP_KEEPIDLE、TCP_KEEPINTVL和TCP_KEEPCNT设置keepalive的保活时间、间隔、次数等参数。
51 |
52 | ```
--------------------------------------------------------------------------------
/chapter17/README.md:
--------------------------------------------------------------------------------
1 | # 17 TCP的长连接和短连接
2 |
3 |
4 | 我们再来回顾一下TCP协议,使用TCP协议时,会在客户端和服务器之间建立一条虚拟的信道,这条虚拟信道就是指连接,而建议这条连接需要3次握手,拆毁这条连接需要4次挥手,可见,我们建立这条连接是有成本的,这个成本就是效率成本,简单点说就是时间成本,你要想发送一段数据,必须先3次握手(来往3个包),然后才能发送数据,发送完了,你需要4次挥手(来往4个包)来断开这个连接。
5 |
6 | 其二,CPU资源成本,三次握手和4次挥手和发送数据都是从网卡里发送出去和接收的,还有其余的设备,比如防火墙,路由器等等,站在操作系统内核的角度来讲,如果我们是一个高并发系统的话,如果大量的数据包都经历过这么一个过程,那是很耗CPU的。
7 |
8 | 其三,每个socket是需要耗费系统缓存的,比如系统提供了一些接口设置socket缓存的,比如:
9 | ```
10 | /proc/sys/net/ipv4/tcp_rmem
11 | /proc/sys/net/ipv4/tcp_wmem
12 | /proc/sys/net/ipv4/tcp_mem
13 |
14 | ```
15 |
16 | 因为TCP的可靠传输,所以我们有大量的应用程序使用TCP协议作为通信,但是每个应用因为产品功能的原因,对TCP的使用是不一样的,比如即时聊天系统(微信,钉钉,探探),比如另外一类
17 |
18 |
--------------------------------------------------------------------------------
/chapter18/15_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/15_4.jpg
--------------------------------------------------------------------------------
/chapter18/18.1_滑动窗口.md:
--------------------------------------------------------------------------------
1 | # 18.1 滑动窗口
2 |
3 | 我们再来看这个比喻:
4 | 
5 |
6 | 网络仅仅是保证了整个网络的连通性,我们我们基于整个网络去传输,那么是不是我想发送多少数据就发送多少数据呢?如果是这样的话,是不是就会像我们的从一个池塘抽水去灌到另外一个池塘一样,想抽多少水就抽多少水,这是肯定不行的,因为我们不能让接收水的池塘溢出水了。那么在TCP协议中是如何控制的?
7 |
8 | 对于每一个socket,都有一个接收缓存,和发送缓存,对于接收端来讲,我们必须确保这个接收缓存不能溢出,为了做到如此,那么接收端在回复ACK时,必须通告这个接收缓存的可用空间有多大,OK,我们来看看TCP协议。
9 | 
10 | TCP头里定义了这个窗口大小的值。是由接收端回复ACK时填充的。于是发送端就可以根据这个接收端的窗口大小来发送数据,而不会导致接收端处理不过来。好了,为了说明整个滑动窗口,我们来看看整个tcp协议控制的数据结构。
11 | 
12 |
13 | 从上图我们可知:
14 | * 接收端LastByteRead指向了TCP缓冲区中读到的位置,NextByteExpected指向的地方是收到的连续包的最后一个位置,LastByteRcved指向的是收到的包的最后一个位置,我们可以看到中间有些数据还没有到达,所以有数据空白区。
15 | * 发送端的LastByteAcked指向了被接收端Ack过的位置(表示成功发送确认),LastByteSent表示发出去了,但还没有收到成功确认的Ack,LastByteWritten指向的是上层应用正在写的地方。
16 | 于是:
17 | * 接收端在给发送端回ACK中会汇报自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
18 | * 而发送方会根据这个窗口来控制发送数据的大小,以保证接收方可以处理。
19 |
20 | 那接收端是如何控制发送端的发送呢?首先,我们来看这个AdvertisedWindow在发送端是如何控制的,首先看这个布局:
21 | 
22 |
23 | 上图中分成了四个部分,分别是:(其中那个黑模型就是滑动窗口)
24 |
25 | #1 已收到ack确认的数据。
26 | #2 发还没收到ack的。
27 | #3 在窗口中还没有发出的(接收方还有空间)。
28 | #4 窗口以外的数据(接收方没空间)
29 |
30 | 既然说是滑动窗口,那这个窗口是如何调整的呢?且看下图:
31 | 
32 |
33 | 下面我们来看一下整个控制流程:
34 | 
35 |
36 | **Zero Window**
37 | 上图,我们可以看到一个处理缓慢的Server(接收端)是怎么把Client(发送端)的TCP Sliding Window给降成0的。此时,你一定会问,如果Window变成0了,TCP会怎么样?是不是发送端就不发数据了?是的,发送端就不发数据了,你可以想像成“Window Closed”,那你一定还会问,如果发送端不发数据了,接收方一会儿Window size 可用了,怎么通知发送端呢?
38 |
39 | 解决这个问题,TCP使用了Zero Window Probe技术,缩写为ZWP,也就是说,发送端在窗口变成0后,会发ZWP的包给接收方,让接收方来ack他的Window尺寸,一般这个值会设置成3次,第次大约30-60秒(不同的实现可能会不一样)。如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。
40 |
41 | 注意:只要有等待的地方都可能出现DDoS攻击,Zero Window也不例外,一些攻击者会在和HTTP建好链发完GET请求后,就把Window设置为0,然后服务端就只能等待进行ZWP,于是攻击者会并发大量的这样的请求,把服务器端的资源耗尽。
42 |
43 | 另外,Wireshark中,你可以使用tcp.analysis.zero_window来过滤包,然后使用右键菜单里的follow TCP stream,你可以看到ZeroWindowProbe及ZeroWindowProbeAck的包。
44 |
45 | **Silly Window Syndrome**
46 |
47 | Silly Window Syndrome翻译成中文就是“糊涂窗口综合症”。正如你上面看到的一样,如果我们的接收方太忙了,来不及取走Receive Windows里的数据,那么,就会导致发送方越来越小。到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的window,而我们的发送方会义无反顾地发送这几个字节。
48 |
49 | 要知道,我们的TCP+IP头有40个字节,为了几个字节,要达上这么大的开销,这太不经济了。
50 |
51 | 另外,你需要知道网络上有个MTU,对于以太网来说,MTU是1500字节,除去TCP+IP头的40个字节,真正的数据传输可以有1460,这就是所谓的MSS(Max Segment Size)注意,TCP的RFC定义这个MSS的默认值是536,这是因为 RFC 791里说了任何一个IP设备都得最少接收576尺寸的大小(实际上来说576是拨号的网络的MTU,而576减去IP头的20个字节就是536)。
52 |
53 | 如果你的网络包可以塞满MTU,那么你可以用满整个带宽,如果不能,那么你就会浪费带宽。(大于MTU的包有两种结局,一种是直接被丢了,另一种是会被重新分块打包发送) 你可以想像成一个MTU就相当于一个飞机的最多可以装的人,如果这飞机里满载的话,带宽最高,如果一个飞机只运一个人的话,无疑成本增加了,也而相当二。
54 |
55 | 所以,Silly Windows Syndrome这个现像就像是你本来可以坐200人的飞机里只做了一两个人。 要解决这个问题也不难,就是避免对小的window size做出响应,直到有足够大的window size再响应,这个思路可以同时实现在sender和receiver两端:
56 |
57 | * 如果这个问题是由Receiver端引起的,那么就会使用 David D Clark’s 方案。在receiver端,如果收到的数据导致window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来,等到receiver端处理了一些数据后windows size 大于等于了MSS,或者,receiver buffer有一半为空,就可以把window打开让send 发送数据过来。
58 | * 如果这个问题是由Sender端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size>=MSS 或是 Data Size >=MSS,2)收到之前发送数据的ack回包,他才会发数据,否则就是在攒数据。
59 |
60 | 另外,Nagle算法默认是打开的,所以,对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性比较强的程序,你需要关闭这个算法。你可以在Socket设置TCP_NODELAY选项来关闭这个算法(关闭Nagle算法没有全局参数,需要根据每个应用自己的特点来关闭)
61 | ```
62 | setsockopt(sock_fd, IPPROTO_TCP, TCP_NODELAY, (char \*)&value,sizeof(int));
63 | ```
64 | 另外,网上有些文章说TCP_CORK的socket option是也关闭Nagle算法,这不对。TCP_CORK其实是更新激进的Nagle算法,完全禁止小包发送,而Nagle算法没有禁止小包发送,只是禁止了大量的小包发送。最好不要两个选项都设置。
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/chapter18/18.2_MTU.md:
--------------------------------------------------------------------------------
1 | # 18.2 MTU
2 |
3 | MTU(Maximum Transmission Unit)是什么鬼东西呢?实际上我们再来看看数据链路层的结构。
4 | 
5 | MTU就是上图红色框部分的大小,这一部分规定了网络层数据的大小最大为1500个字节,网络层必须确保不超过1500个字节,否则数据链路层会把这个包给舍弃掉。于此,对于TCP协议来讲,因为IP头为20个字节,TCP头为20个字节,所以,TCP协议能传输的数据最大为1460(1500 - 20 - 20),这个值称为MSS(Maxitum Segment Size)。
6 |
7 | 那么这个值MSS或者说MTU对我们编程实现有什么作用呢?且听课堂分解。
8 |
--------------------------------------------------------------------------------
/chapter18/18.3_拥塞控制.md:
--------------------------------------------------------------------------------
1 | # 18.3 拥塞控制
2 |
3 | 我们在向对端发送数据时,并不是一股脑子任意发送,因为TCP建立连接后,就是建立了一根管道,这跟管道上,实际上有很多的工作设备,比如路由器和交换机等等,他们都会对接收到的TCP包进行缓存,以便实现排序,然后发送,但是这些设备并不是只为一个TCP连接中转数据包,大量的网络包也许会耗尽存储空间,从而导致TCP连接的吞吐量急剧下降。为了避免这种情况的发送,TCP的设计必须是一种无私的协议,它必须去探测这种网络拥塞的问题,否则我们想想,一旦出现拥塞(判断是否丢包或者是否发生重传),如果TCP只能做重传,那么重传数据包会使得网络上的包更多,网络的负担更重,于是导致更大的延迟以及丢更多的包,于是会进入一个恶性循环,如果网络上的所有TCP连接都是如此行事的话,那么马上就会形成“网络风暴”,会拖垮整个网络,这也是一个灾难。那么TCP就应该能够检测出来这种状况,当拥塞出现时,要做自我牺牲,就像交通阻塞一样,每一辆车都应该把路给让出来,而不是再去抢路了。这说的就是拥塞控制。那是如何控制的呢?
4 |
5 | 首先,我们得看TCP是如何充分利用网络的,TCP实际上就是逐步探测这个通道的传输的最大能力,这个逐步探索就是我们要讲的慢启动算法,这个慢启动算法就是:新建立的连接不能一开始就大量发送数据包,而是应该根据网络状况,逐步地增加每次发送数据包的量。
6 |
7 | 具体的工作步骤就是:
8 |
9 | **慢启动算法:**
10 | 1. 发送方维护一个拥塞窗口,刚开始时,这个拥塞窗口(cwnd,congestion window)设置为1,这个1代表是一个MSS个字节。
11 |
12 | 2. 如果每收到一个ACK,那么就指数增长这个cwnd(2,4,8,16,32,64)等,
13 |
14 | 3. 实际上不会这么一直指数级增长下去,TCP会设置一个慢启动的阈值(ssthresh,slow start threshold,65535个字节) ,当cwnd >= ssthresh时,进入拥塞避免阶段。
15 |
16 | **拥塞避免阶段**
17 |
18 | 1. 每收到一个ACK时,cwnd = cwnd + 1/cwnd;
19 |
20 | 2. 每当每过一个RTT时,cwnd = cwnd + 1;
21 |
22 | 这样放缓了拥塞窗口的增长速率,避免增长过快导致网络拥塞,慢慢的增加调整到网络的最佳值。在这个过程中如果出现了拥塞,则进入拥塞状态。
23 |
24 | **拥塞状态**
25 | 那是如何判断出现拥塞状态呢?只要出现丢包就认为进入了拥塞状态。进入拥塞状态也分两种情况:
26 | 1) 等到RTO超时(重传超时),重传数据包。TCP认为这种情况太糟糕,反应也很强烈:
27 | * sshthresh = cwnd /2
28 | * cwnd 重置为 1
29 | * 进入慢启动过程
30 |
31 | **快速重传**
32 | 2)连续收到3个duplicate ACK时,重传数据包,无须等待RTO。此情况即为下面的快速重传。
33 |
34 | 【问题】什么情况下会出现3个duplicate ACK?
35 |
36 | TCP在收到一个乱序的报文段时,会立即发送一个重复的ACK,并且此ACK不可被延迟。
37 |
38 | 如果连续收到3个或3个以上重复的ACK,TCP会判定此报文段丢失,需要重新传递,而无需等待RTO。这就叫做快速重传。
39 |
40 | TCP Tahoe的实现和RTO超时一样。
41 | TCP Reno的实现是:
42 | * sshthresh = cwnd
43 | * cwnd = cwnd /2
44 | * 进入快速恢复算法——Fast Recovery
45 |
46 | 上面我们可以看到RTO超时后,sshthresh会变成cwnd的一半,这意味着,如果cwnd<=sshthresh时出现的丢包,那么TCP的sshthresh就会减了一半,然后等cwnd又很快地以指数级增涨爬到这个地方时,就会成慢慢的线性增涨。我们可以看到,TCP是怎么通过这种强烈地震荡快速而小心得找到网站流量的平衡点的。
47 |
48 | **快速恢复算法**
49 |
50 | ### TCP Reno
51 |
52 | 这个算法定义在RFC5681。快速重传和快速恢复算法一般同时使用。快速恢复算法是认为,你还有3个Duplicated Acks说明网络也不那么糟糕,所以没有必要像RTO超时那么强烈。 注意,正如前面所说,进入Fast Recovery之前,cwnd 和 sshthresh已被更新:
53 |
54 | * sshthresh = cwnd
55 | * cwnd = cwnd /2
56 |
57 | 然后,真正的Fast Recovery算法如下:
58 |
59 | * cwnd = sshthresh + 3 * MSS (3的意思是确认有3个数据包被收到了)
60 | * 重传Duplicated ACKs指定的数据包
61 | * 如果再收到 duplicated Acks,那么cwnd = cwnd +1
62 | * 如果收到了新的Ack,那么,cwnd = sshthresh ,代表恢复过程结束,然后就进入了拥塞避免的算法了。
63 |
64 | 如果我们仔细思考一下上面的这个算法,你就会知道,上面这个算法也有问题,那就是——它依赖于3个重复的Acks。注意,3个重复的Acks并不代表只丢了一个数据包,很有可能是丢了好多包。但这个算法只会重传一个,而剩下的那些包只能等到RTO超时,于是,进入了恶梦模式——超时一个窗口就减半一下,多个超时会超成TCP的传输速度呈级数下降,而且也不会触发Fast Recovery算法了。
65 |
66 | ### TCP New Reno
67 |
68 | 于是,1995年,TCP New Reno(参见 RFC 6582 )算法提出来:
69 | * 当sender这边收到了3个Duplicated Acks,进入Fast Retransimit模式,开发重传重复Acks指示的那个包。如果只有这一个包丢了,那么,重传这个包后回来的Ack会把整个已经被sender传输出去的数据ack回来。如果没有的话,说明有多个包丢了。我们叫这个ACK为Partial ACK。
70 | * 一旦Sender这边发现了Partial ACK出现,那么,sender就可以推理出来有多个包被丢了,于是乎继续重传sliding window里未被ack的第一个包。直到再也收不到了Partial Ack,才真正结束Fast Recovery这个过程。
71 |
72 | 我们可以看到,这个“Fast Recovery的变更”是一个非常激进的玩法,他同时延长了Fast Retransmit和Fast Recovery的过程。
--------------------------------------------------------------------------------
/chapter18/18.4_BBR算法.md:
--------------------------------------------------------------------------------
1 | # 18.4 BBR算法
2 |
3 | 上一小节,我们接收了两种拥塞控制算法,一个是Reno、一个是New Reno,现如今,google研发了新的拥塞控制算法BBR,详情见:[https://www.zhihu.com/question/53559433](https://www.zhihu.com/question/53559433)
4 |
5 | 对于BBR算法的测试结果可以参考:
6 | [https://www.zhihu.com/question/52933117](https://www.zhihu.com/question/52933117)
7 |
8 | 那我们如何修改拥塞控制算法呢?
9 | * 首先我们要确保我们是否升级到了4.9版本,使用uname -r 或者uname -a
10 | ```
11 | lizhiyong@ubuntu:~$ uname -r
12 | 4.4.0-87-generic
13 | lizhiyong@ubuntu:~$ uname -a
14 | Linux ubuntu 4.4.0-87-generic #110-Ubuntu SMP Tue Jul 18 12:55:35 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
15 | lizhiyong@ubuntu:~$
16 |
17 | ```
18 |
19 | * 下载linux内核代码方法:到官网下载[https://www.kernel.org/](https://www.kernel.org/)
20 | 或者git:[https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/](https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/)
21 |
22 | 升级内核版本的方法:[https://kernelnewbies.org/KernelBuild](https://kernelnewbies.org/KernelBuild)
23 |
24 | 修改内核使用的拥塞控制算法:
25 |
26 | 更新完系统内核之后,就可以开启TCP-BBR了。
27 |
28 | 1) 编辑/etc/sysctl.conf,并保存
29 |
30 | #在文件最底部添加如下两行,如果之前已经有这些内容,请酌情删掉或修改
31 | net.core.default_qdisc=fq
32 | net.ipv4.tcp_congestion_control=bbr
33 |
34 | 2) 执行sysctl -p使修改后的配置文件生效
35 | root@linode:~# sysctl -p
36 | net.core.default_qdisc = fq
37 | net.ipv4.tcp_congestion_control = bbr
38 |
39 | 3) 查看BBR是否开启
40 |
41 | 首先,执行下边的命令来确定内核已经开启BBR(如果返回的结果中含有BBR则证明内核已经开启):
42 |
43 | sysctl net.ipv4.tcp_available_congestion_control
44 | 然后,执行下边的命令,看到有tcp_bbr模块,则说明BBR已经启动:
45 |
46 | lsmod | grep bbr
47 |
48 |
49 |
--------------------------------------------------------------------------------
/chapter18/18_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_1.png
--------------------------------------------------------------------------------
/chapter18/18_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_2.png
--------------------------------------------------------------------------------
/chapter18/18_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_3.png
--------------------------------------------------------------------------------
/chapter18/18_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_5.png
--------------------------------------------------------------------------------
/chapter18/18_6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_6.png
--------------------------------------------------------------------------------
/chapter18/18_7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_7.png
--------------------------------------------------------------------------------
/chapter18/18_8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter18/18_8.png
--------------------------------------------------------------------------------
/chapter18/README.md:
--------------------------------------------------------------------------------
1 | # 第18节 TCP的滑动窗口和拥塞控制
2 |
3 | 我们说TCP可靠,除了对乱序包进行排序,对丢失的包进行重发,使用ACE确认收到的包以外,还有很多更加复杂的算法,我们可以把TCP类比如下:
4 | 
5 |
6 | 也就是说TCP协议就像一根管道,连接着两个池塘,保证从一个池塘往另一个池塘输送水,那么在这个过程中就要保证两件事情,一个是保证接收水的池塘的水不能溢出池塘了,这个手段在TCP中使用的是滑动窗口,它要控制发送和接收端处理速率达到一个平衡;另一个是要保证尽可能快的把送水池塘的水输送到接收水的池塘,因为如果TCP这跟管道里如果水没有塞满,那输送水肯定很慢,如果输送水过快,那管子会爆裂,后果就是谁都输送不了水,这个手段在TCP中是使用拥塞控制算法。
7 |
8 | OK,如果你说你不懂滑动窗口和拥塞控制,你就不懂TCP协议,而且也不懂如何使用。试想一下,如果你要做一个软件升级,或者是一些音视频传输,或者是后台各个服务模块之间传输数据,那如何尽可能提高整个系统的处理能力和处理的效率呢?这就必须要了解TCP的各个特性了,你才能更好地解决系统的性能瓶颈。
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/chapter19/19.1_http协议介绍.md:
--------------------------------------------------------------------------------
1 | # 19.1 http协议介绍
2 |
3 | HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,最开始是用于从万维网(WWW:World Wide Web )服务器传输超文本到本地浏览器的传送协议。说它是超文本的意思是,它所有的信息都是文本信息,都是明文传输的。
4 |
5 | * [请求数据格式](#request)
6 | * [应答数据格式](#response)
7 | * [http的原理](#theory)
8 |
9 | 请求数据格式
10 |
11 | 另外,http是一种请求应答模式,一请求一应答的模式,那么我们首先来看下请求数据包的格式吧:
12 | 
13 |
14 | 一行为单位对数据进行组织的。OK,我们先来看一个实例吧:
15 | ```
16 | GET / HTTP/1.1
17 | Host: wx.qq.com
18 | Connection: keep-alive
19 | Cache-Control: max-age=0
20 | Upgrade-Insecure-Requests: 1
21 | User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
22 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
23 | Accept-Encoding: gzip, deflate, br
24 | Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
25 |
26 | ```
27 |
28 | 或者:
29 | ```
30 |
31 | GET /qrcode/QaJNAK9NLg== HTTP/1.1
32 | Host: login.weixin.qq.com
33 | Connection: keep-alive
34 | User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36
35 | Accept: image/webp,image/apng,image/*,*/*;q=0.8
36 | Referer: https://wx.qq.com/
37 | Accept-Encoding: gzip, deflate, br
38 | Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
39 |
40 | ```
41 | * 请求行
42 |
43 | 请求行以一个方法符号开头,以空格分开,后面跟着请求的URL和协议的版本。
44 | http支持的请求方法如下:
45 | |序号 | 方法 | 描述 |
46 | |-------|---------|------|
47 | |1 |GET |请求指定的页面信息,并返回实体主体。|
48 | |2 |HEAD |类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头|
49 | |3 |POST |向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。|
50 | |4 |PUT |从客户端向服务器传送的数据取代指定的文档的内容。|
51 | |5 |DELETE |请求服务器删除指定的页面。|
52 | |6 |CONNECT |HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。|
53 | |7 |OPTIONS |允许客户端查看服务器的性能。|
54 | |8 |TRACE |回显服务器收到的请求,主要用于测试或诊断。|
55 |
56 | **URL是uniform resource locator,统一资源定位器,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。**
57 | URL是Internet上用来描述信息资源的字符串,主要用在各种WWW客户程序和服务器程序上,特别是著名的Mosaic。
58 | 采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。URL一般由三部组成:
59 | ① 协议(或称为服务方式)
60 | ② 存有该资源的主机IP地址(有时也包括端口号)
61 | ③ 主机资源的具体地址。如目录和文件名等
62 |
63 | 2. 请求头部
64 |
65 | 请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。
66 | 常用的请求报头
67 | **Accept**
68 | Accept请求报头域用于指定客户端接受哪些类型的信息。eg:Accept:image/gif,表明客户端希望接受GIF图象格式的资源;Accept:text/html,表明客户端希望接受html文本。
69 | **Accept-Charset**
70 | Accept-Charset请求报头域用于指定客户端接受的字符集。eg:Accept-Charset:iso-8859-1,gb2312.如果在请求消息中没有设置这个域,缺省是任何字符集都可以接受。
71 | **Accept-Encoding**
72 | Accept-Encoding请求报头域类似于Accept,但是它是用于指定可接受的内容编码。eg:Accept-Encoding:gzip.deflate.如果请求消息中没有设置这个域服务器假定客户端对各种内容编码都可以接受。
73 | **Accept-Language**
74 | Accept-Language请求报头域类似于Accept,但是它是用于指定一种自然语言。eg:Accept-Language:zh-cn.如果请求消息中没有设置这个报头域,服务器假定客户端对各种语言都可以接受。
75 | **Authorization**
76 | Authorization请求报头域主要用于证明客户端有权查看某个资源。当浏览器访问一个页面时,如果收到服务器的响应代码为401(未授权),可以发送一个包含Authorization请求报头域的请求,要求服务器对其进行验证。
77 | **Host(发送请求时,该报头域是必需的)**
78 | Host请求报头域主要用于指定被请求资源的Internet主机和端口号,它通常从HTTP URL中提取出来的,eg:
79 | 我们在浏览器中输入:http://www.guet.edu.cn/index.html
80 | 浏览器发送的请求消息中,就会包含Host请求报头域,如下:
81 | Host:www.guet.edu.cn
82 | 此处使用缺省端口号80,若指定了端口号,则变成:Host:www.guet.edu.cn:指定端口号
83 | **User-Agent**
84 | 我们上网登陆论坛的时候,往往会看到一些欢迎信息,其中列出了你的操作系统的名称和版本,你所使用的浏览器的名称和版本,这往往让很多人感到很神奇,实际上,服务器应用程序就是从User-Agent这个请求报头域中获取到这些信息。User-Agent请求报头域允许客户端将它的操作系统、浏览器和其它属性告诉服务器。不过,这个报头域不是必需的,如果我们自己编写一个浏览器,不使用User-Agent请求报头域,那么服务器端就无法得知我们的信息了。
85 | **Content-Type**
86 | 关于字符的编码,1.0版规定,头信息必须是 ASCII 码,后面的数据可以是任何格式。因此,服务器回应的时候,必须告诉客户端,数据是什么格式,这就是Content-Type字段的作用。
87 |
88 | 下面是一些常见的Content-Type字段的值:
89 | ```
90 | text/plain
91 | text/html
92 | text/css
93 | image/jpeg
94 | image/png
95 | image/svg+xml
96 | audio/mp4
97 | video/mp4
98 | application/javascript
99 | application/pdf
100 | application/zip
101 | application/atom+xml
102 |
103 | ```
104 |
105 | **Content-Encoding**
106 | 由于发送的数据可以是任何格式,因此可以把数据压缩后再发送。Content-Encoding字段说明数据的压缩方法。
107 | ```
108 | Content-Encoding: gzip
109 | Content-Encoding: compress
110 | Content-Encoding: deflate
111 |
112 | ```
113 | 客户端在请求时,用Accept-Encoding字段说明自己可以接受哪些压缩方法。
114 | ```
115 | Accept-Encoding: gzip, deflate
116 |
117 | ```
118 |
119 | * 空行,请求头部之后的空行是必须的
120 |
121 | * 请求数据:请求数据也叫消息体,可以添加任意的其他数据。
122 |
123 | 应答数据格式
124 | 应答数据格式如下:
125 |
126 | 
127 |
128 |
129 | 例子:
130 | ```
131 | HTTP/1.1 200 OK
132 | Connection: keep-alive
133 | Content-Type: text/javascript
134 | Content-Length: 16
135 |
136 | window.code=408;
137 |
138 | ```
139 |
140 | 第一部分:状态行,由HTTP协议版本号, 状态码, 状态消息 三部分组成。
141 | 第一行为状态行,(HTTP/1.1)表明HTTP版本为1.1版本,状态码为200,状态消息为(ok)
142 |
143 | 第二部分:消息报头,用来说明客户端要使用的一些附加信息
144 |
145 | 第二行到第四行为消息报头,
146 | Content-Type:指定了消息体的内容为text/javascript格式的;
147 | Content-Length:
148 |
149 | 第三部分:空行,消息报头后面的空行是必须的
150 |
151 | 第四部分:响应正文,服务器返回给客户端的文本信息。
152 | 例子中是“window.code=408”
153 |
154 | 其实,请求头和回应头都是一样格式的。
155 |
156 | 状态代码有三位数字组成,第一个数字定义了响应的类别,共分五种类别:
157 |
158 | 1xx:指示信息--表示请求已接收,继续处理
159 | 2xx:成功--表示请求已被成功接收、理解、接受
160 | 3xx:重定向--要完成请求必须进行更进一步的操作
161 | 4xx:客户端错误--请求有语法错误或请求无法实现
162 | 5xx:服务器端错误--服务器未能实现合法的请求
163 |
164 | 常见的状态码有:
165 | ```
166 | 200 OK //客户端请求成功
167 | 400 Bad Request //客户端请求有语法错误,不能被服务器所理解
168 | 401 Unauthorized // 请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用
169 | 403 Forbidden //服务器收到请求,但是拒绝提供服务
170 | 404 Not Found //请求资源不存在,eg:输入了错误的URL
171 | 500 Internal Server Error //服务器发生不可预期的错误
172 | 503 Server Unavailable //服务器当前不能处理客户端的请求,一段时间后可能恢复正常
173 | ```
174 |
175 | http的原理
176 |
177 | http属于应用层的消息,底层的传输协议是tcp,晚上我们会抓包来具体分析下?
178 |
179 | 也正因为http是明文传输的,相对来说,浪费了很多的带宽(相对那些使用TLV格式定义的协议,以TCP协议承载传输来说),也正是因为这一点,导致http协议不是一种安全的协议,因为我们完全可以在网络上抓取一个http包,然后进行重放攻击。
180 |
181 | 那如何做到http的安全传输呢?
182 |
183 | 这是一个很长的话题,我们先把这个记下来,将来在服务器开发的尾端来介绍。
184 |
185 |
--------------------------------------------------------------------------------
/chapter19/19.2_http应用.md:
--------------------------------------------------------------------------------
1 | # 19.2 http应用
2 |
3 | libcurl:[https://curl.haxx.se/](https://curl.haxx.se/)
4 | 中文介绍可以参考:[https://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html](https://www.cnblogs.com/moodlxs/archive/2012/10/15/2724318.html)
5 |
--------------------------------------------------------------------------------
/chapter19/19.3_短信验证码.md:
--------------------------------------------------------------------------------
1 | # 19.3 短信验证码
2 |
3 | 用户使用短信验证码登录系统,一者验证了用户手机的合法性,比如整个手机号码正在被所持有者使用等等,防止了盗取了别人账户信息等,强迫输入短信验证码,也避免了一部分攻击。当然除了短信验证码的,我们还看见过使用电子邮箱进行验证的。
4 |
5 | * 6位数字的随机码如何生成?
6 |
7 | ```
8 | std::string UserEventHandler::code_gen()
9 | {
10 | unsigned int code = 0;
11 | srand((unsigned int)time(NULL));
12 |
13 | code = (unsigned int)(rand() % (999999 - 100000) + 1000000);
14 |
15 | char buffer[20] = {0};
16 | sprintf(buffer, "%d", code);
17 | strcpy(buffer, buffer + 1);
18 |
19 | return std::string(buffer);
20 | }
21 |
22 | ```
23 |
24 | * 如何发送短信验证码?
25 |
26 | 发送短信时,我们一定需要短信服务提供商,我们向提供商发送短信服务请求,由他们向目的手机发送短信验证码。具体的工作流程为:
27 |
28 | 
29 |
--------------------------------------------------------------------------------
/chapter19/19_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter19/19_1.png
--------------------------------------------------------------------------------
/chapter19/19_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter19/19_2.jpg
--------------------------------------------------------------------------------
/chapter19/19_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter19/19_3.png
--------------------------------------------------------------------------------
/chapter19/README.md:
--------------------------------------------------------------------------------
1 | # 第19节 http协议介绍
2 |
3 | 我们现在的浏览器浏览网页时,都是使用http协议,可以这么说,我们接触最频繁的也是http协议,同时由于互联网的繁荣,我们出现了各种云平台,那么这些云平台的开放接口设计也大多采用了http协议。
--------------------------------------------------------------------------------
/chapter2/2.1_安装和配置linux系统.md:
--------------------------------------------------------------------------------
1 | # 2.1 安装和配置linux系统
2 |
3 |
--------------------------------------------------------------------------------
/chapter2/2.2_linux命令.md:
--------------------------------------------------------------------------------
1 | # 2.2 linux命令
2 |
3 | linux命令是用户使用Linux的桥梁.它通过Shell访问操作系统内核提供的服务,掌握基本的linux命令是我们和Linux沟通的必备条件.本节介绍以下内容:
4 |
5 | * [1.命令行操作相关](#1)
6 | * [2.基本命令](#2)
7 | * [3.软件安装和卸载命令](#3)
8 |
9 |
10 |
11 | 1.命令行操作相关
12 |
13 | * 行首“$” 或 “#” - 命令行提示符
14 | * 行中 ## - 视为注释开始
15 | * **命令行是区分大小写的!**
16 | * 使用命令行补全(Tab键)和通配符可以提高输入效率
17 | * 通配符共有3个: “\*”,“?”,“\[\]”等3种, \*:用于匹配文件名中任意长度的字符串; ?:只匹配一个字符; \[\]:用于匹配所有出现在方括号内的字符,可以使用短线“-”来指定字符集范围,如:ls text\[1-3\] 或 ls test\[a-z\].
18 |
19 | 2.基本命令
20 |
21 | Linux基本命令有以下几个:
22 |
23 | **man [OPTION] [SECTION] cmd**
24 |
25 | 作用:Linux 为所有命令和系统调用编写了帮助手册。使用man 命令可以方便地获取某个命令的帮助信息。
26 | 用法:man [手册编号] 命令名。
27 | man 命令在显示手册页时实际调用less 完成显示,J K可以上下翻动,空格用于向下翻页。Q键退出。
28 | 手册总共分为9 节,各部分内容如下:
29 |
30 | * 1./usr/share/man/man1 普通命令和应用程序
31 | * 2./usr/share/man/man2 系统调用
32 | * 3./usr/share/man/man3 库调用,主要是libc函数的使用说明
33 | * 4./usr/share/man/man4 设备驱动和网络协议
34 | * 5./usr/share/man/man5 文件的详细格式信息
35 | * 6./usr/share/man/man6 游戏文件
36 | * 7./usr/share/man/man7 文档使用说明
37 | * 8./usr/share/man/man8 系统管理命令
38 | * 9./usr/share/man/man9 内核源代码或模块的技术指标
39 |
40 | __特权命令 sudo__
41 | 作用: 提升当前命令的执行权限,以root身份执行它。
42 | 用法: sudo 命令
43 | 例子: sudo su #切换到root 用户
44 |
45 | __ls [OPTION] [FILE]__
46 | 作用:查看文件信息或者显示目录下的文件及目录
47 | 用法:1.不带任何参数时,显示当前目录下的所有文件及目录
48 | 2.-a参数,显示隐含文件
49 | 3.-l参数,查看文件的各种属性
50 |
51 | __pwd__
52 | 作用:显示当前目录的全路径
53 | 用法:在想查看当前路径的目录下执行该命令即可
54 |
55 | __cd 目录路径__
56 | 作用:切换到目的路径下
57 | 用法:cd 目录路径
58 | 例子:cd /home/
59 |
60 | __dir和vdir__
61 | 作用: 查看当前目录下的文件信息, vdir相当于\"ls -l\" 命令
62 | 用法: 直接执行,或者带一些可选参数\"-a\"
63 |
64 | __mkdir__
65 | 作用:在指定目录下创建一个目录,也可不指定目录,默认在当前目录下创建
66 | 用法:1.不带任何参数 创建相应目录,如果目录的父级目录路径不存在,则创建失败
67 | 2.-p 创建相应目录,如果目录的父级目录路径不存在,则一起创建
68 | 例子:mkdir /home/DN;或者 mkdir DN
69 |
70 | __mv . . .[OPTION] 源文件 目标文件__
71 | 作用:重命名文件或者目录, 将文件或者目录移动到指定目录下
72 | 用法:1.不带任何参数 将源文件移动到目标文件,注意:如果目标文件存在则替换
73 | 2.-i 将源文件移动到目标文件,如果目标文件存在则提示是否替换
74 | 3.-b 将源文件移动到目标文件,如果目标文件存在则不进行覆盖,而是在目标文件后加\"~\"
75 | 例子: mv /home/DN /home/DN2 重命名目录DN为DN2
76 | mv /home/a.txt /home/b.txt 将a.txt重命名为b.txt
77 | mv /home/DN /home/DN2/ 将目录DN移动到DN2目录下
78 |
79 | __cp . . .[OPTION] 源文件 目标文件__
80 | 作用:拷贝文件或者目录到指定文件或者目录下
81 | 用法:1.不带任何参数 将源文件复制到目标文件,注意:如果目标文件存在则替换
82 | 2.-i 将源文件复制到目标文件,如果目标文件存在则提示是否替换
83 | 3.-b 将源文件复制到目标文件,如果目标文件存在则不进行覆盖,而是在目标文件后加\"~\"
84 | 4.-r 将子目录及其中的文件一起复制到另一个子目录下
85 |
86 | __rm . . .[OPTION]. . . [FILE]. . .__
87 | 作用:删除命令可以一次永久性删除一个或几个文件(包含目录)
88 | 用法:1.不带任何参数 删除文件或相应目录,不给予任何提示;
89 | 2.-i 删除文件或相应目录,删除时进行提醒;
90 | 3.-f 强制性删除文件或相应目录;
91 | 4.-r 将子目录及其中的文件一并删除。(慎用!特别时在 root)
92 |
93 | __ln . . .[OPTION] 源文件 目标文件__
94 | 作用:创建源文件的链接
95 | 用法:1.不带任何参数 创建硬链接,ls -i 查看可以看到两个文件的inode值一致
96 | 2.-s 创建软链接,即别名,如果源文件删除,则软链接(别名)也无法访问。
97 |
98 | __chown . . .[OPTION] [OWNER][:[GROUP]] FILE . . .__
99 | 作用:用于改变文件的所有权
100 | 用法:1.不带任何参数 改变单个或多个文件的属主和属组
101 | 2.-r 改变一个目录及其下所有文件(和子目录)的所有权设置
102 |
103 | __chgrp . . .[GROUP] FILE . . .__
104 | 作用: 单独设置文件的属组
105 |
106 | __chmod . . .[OPTION] . . .[FILE]. . .__
107 | 作用: 用于改变文件或目录的访问权限
108 | 用法: chmod 权限 文件或者目录,, 它以“用户组 +/- 权限”的表达方式来增加/删除相应的权限。具体来说,用户组包括了文件属主(u)、文件属组(g)、其他人(o) 和所有人(a),而权限则包括读取(r、w、x)
109 |
110 | __head/tail . . .[OPTION]. . . FILE__
111 | 作用:阅读文件的头部和尾部
112 | 用法:1.不带任何参数 显示文件的头部/尾部10行
113 | 2.-n 按指定的行数显示文件的头部/尾部
114 |
115 | __cat 文件名__
116 | 作用: 一次性查看全部文本文件的内容
117 | 用法: 后跟文件名作为参数,也可以带上 -n 显示每行的行号
118 |
119 | __more 文件名__
120 | 作用: 用来分页查看文本文件
121 | 用法: 空格翻页;回车向下滚动一行;Q 键退出
122 |
123 | __less 文件名__
124 | 作用: 查看文本文件
125 | 用法: 与more使用的方法相同,但是对于较大文件,打开会更快
126 |
127 | __vim 文件名__
128 | 作用: 使用vim打开文件,vim是常用的编辑工具,功能较为强大
129 | 用法: 3种模式:输入模式,底行模式,命令模式,在不同的模式下可进行不同的操作
130 |
131 | __ps__
132 | 作用: 列出系统中当前运行的那些进程, 能列出系统中运行的进程,包括进程号、命令、CPU使用量、内存使用量等
133 | 用法: 1.-a - 列出所有运行中/激活进程
134 | 用法: 2.ps -aux - 显示进程信息,包括无终端的(x)和针对用户(u)的进程
135 |
136 | __find [OPTION] [path . . .] [expression]__
137 | 作用:在指定范围内查找文件
138 | 用法:1.-type 查找时指定文件的类型,可使用参数如下表
139 | 2.-atime n 查找最后一次使用在n天前的文件,n 使用负数表示
140 | 3.-mtime n 查找最后一次修改在n天前的文件
141 |
142 | __locate [expression]__
143 | 作用: 快速定位文件
144 | 用法: 并不进入子目录进行搜索,它通过检索数据库来确定文件的位置。可以使用 updatedb来更新检索数据库。
145 |
146 | __grep [OPTIONS] PATTERN [FILE...]__
147 | 作用: 在文件中寻找某些信息
148 | 用法: 常用的文本处理工具
149 |
150 |
151 |
152 | 3.软件安装和卸载命令
153 |
154 | * apt-get install softname1 softname2 softname3…… # 安装软件
155 | * apt-get remove softname1 softname2 softname3…… # 卸载软件
156 | * apt-get remove --purge softname1 # 卸载并清除配置
157 | * apt-get update #更新软件信息数据库
158 | * apt-get upgrade #进行系统升级
159 | * apt-cache search softname1 softname2 softname3…… #搜索软件包
160 | Deb软件包相关安装与卸载
161 | * dpkg -i xxx.deb #安装deb软件包
162 | * dpkg -r xxx.deb #删除软件包
163 | * dpkg -r --purge xxx.deb #连同配置文件一起删除
164 | * dpkg -info xxx.deb #查看软件包信息
165 | * dpkg -L xxx.deb #查看文件拷贝详情
166 | * dpkg –l #查看系统中已安装软件包信息
167 | * dpkg-reconfigure xxx #重新配置软件包
168 |
169 |
--------------------------------------------------------------------------------
/chapter2/2.3_安装开发工具.md:
--------------------------------------------------------------------------------
1 | # 2.3 安装开发工具
2 |
3 |
--------------------------------------------------------------------------------
/chapter2/2.4_搭建开发环境.md:
--------------------------------------------------------------------------------
1 | # 2.4 搭建开发环境
2 |
3 |
--------------------------------------------------------------------------------
/chapter2/README.md:
--------------------------------------------------------------------------------
1 | # 第二节 搭建开发环境
2 |
3 |
--------------------------------------------------------------------------------
/chapter20/20.3_protobuf序列化.md:
--------------------------------------------------------------------------------
1 | # protobuf序列化
2 |
3 | protobuf是一种比json和xml等序列化工具更加轻量和高效的结构化数据存储格式,性能比json和xml真的强很多,毕竟是google出品的。
4 |
5 | ## 参考链接
6 | [protocol buffer官网](https://developers.google.com/protocol-buffers/)
7 |
8 | ## protobuf的安装
9 | ```
10 | $ git clone https://github.com/protocolbuffers/protobuf.git
11 | $ cd protobuf
12 | $ git submodule update --init --recursive
13 | $ ./autogen.sh
14 | $ ./configure
15 | $ make
16 | $ make check
17 | $ sudo make install
18 | $ sudo ldconfig
19 | ```
20 |
21 | ## protobuf的应用
22 |
23 | ### 定义message
24 | 所有的message必须定义到一个文件中,且文件的后缀名为.proto。例如我们定义的proto文件
25 |
26 | ```
27 | syntax = "proto2";
28 |
29 | package tutorial;
30 |
31 | message rsponse_result
32 | {
33 | required int32 code = 1; //返回编码
34 | required string msg = 2; //返回信息
35 | optional string data = 3; //其他数据
36 | }
37 |
38 | message mobile_request
39 | {
40 | required string mobile = 1;
41 | }
42 |
43 | message login_request
44 | {
45 | required string mobile = 1; // 手机号码
46 | required int32 code = 2; // 验证码
47 | }
48 |
49 | message recharge_request
50 | {
51 | required string mobile = 1;
52 | required int32 amount = 2;
53 | }
54 |
55 | message account_balance_request
56 | {
57 | required string mobile = 1;
58 | }
59 |
60 | message account_balance_response
61 | {
62 | required rsponse_result ret = 1;
63 | required int32 balance = 2;
64 | }
65 |
66 | message list_account_records_request
67 | {
68 | required string mobile = 1;
69 | }
70 |
71 | message list_account_records_response
72 | {
73 | required rsponse_result ret = 1;
74 | message account_record
75 | {
76 | required int32 type = 1; // 0 : 骑行消费, 1 : 充值, 2 : 退款
77 | required int32 limit = 2; // 消费或者充值金额
78 | required uint64 timestamp = 3; // 记录发生时的时间戳
79 | }
80 |
81 | repeated account_record records = 2;
82 | }
83 |
84 | message list_travel_records_request
85 | {
86 | required string mobile = 1;
87 | }
88 |
89 | message list_travel_records_rsponse
90 | {
91 | required rsponse_result ret = 1;
92 | message travel_record
93 | {
94 | required uint64 stm = 1; // start timestamp
95 | required uint32 duration = 2; // 骑行时长
96 | required uint32 amount = 3; // 所耗金额
97 | }
98 |
99 | required double mileage = 2; // 里程
100 | required double discharge = 3; // 排放
101 | required double calorie = 4; // 卡路里
102 | repeated travel_record records = 5;
103 | }
104 |
105 | ```
106 |
107 | ### 编译mesage文件
108 | 编译语法:
109 |
110 | ```
111 | protoc -I=$SRC_DIR --cpp_out=$DST_DIR $SRC_DIR/addressbook.proto
112 |
113 | ```
114 | SRC_DIR 表示proto文件所在的目录,cpp_out指定了生成的代码的路径,$SRC_DIR/addressbook.proto指proto文件。
115 | ```
116 | protoc -I=./ --cpp_out=./ protocol.proto
117 | ```
118 | 这样在当前目录生成了protocol.pb.cc和protocol.pb.h两个文件。
119 |
120 | ### 应用protobuf
121 |
122 | 把生成了protocol.pb.cc和protocol.pb.h加入到工程,那么接着就是调用一些API,完成序列化和反序列化。参考:
123 | [API说明](https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.message)
124 |
125 | ### protobuf的原理
126 |
127 | 
128 |
129 |
130 | ### proto2和proto3的区别
131 |
132 | 总的来说,proto3 比 proto2 支持更多语言但 更简洁。去掉了一些复杂的语法和特性,更强调约定而弱化语法。如果是首次使用 Protobuf ,建议使用 proto3 。
133 |
134 | 1. 在第一行非空白非注释行,必须写:
135 | syntax = "proto3";
136 | 字段规则移除了 “required”,并把 “optional” 改名为 “singular”1;
137 |
138 | 2. 在 proto2 中 required 也是不推荐使用的。proto3 直接从语法层面上移除了 required 规则。其实可以做的更彻底,把所有字段规则描述都撤销,原来的 repeated 改为在类型或字段名后加一对中括号。这样是不是更简洁?
139 |
140 | 3. “repeated”字段默认采用 packed 编码;
141 | 在 proto2 中,需要明确使用 [packed=true] 来为字段指定比较紧凑的 packed 编码方式。
142 |
143 | 4. 语言增加 Go、Ruby、JavaNano 支持;
144 | 5. 移除了 default 选项;
145 | 在 proto2 中,可以使用 default 选项为某一字段指定默认值。在 proto3 中,字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。
146 | 在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。
147 | 但这样就无法区分某字段是根本没赋值,还是赋值了默认值。这在 proto3 中问题不大,但在 proto2 中会有问题。
148 | 比如,在更新协议的时候使用 default 选项为某个字段指定了一个与原来不同的默认值,旧代码获取到的该字段的值会与新代码不一样。
149 | 另一个重约定而弱语法的例子是 Go 语言里的公共/私有对象。Go 语言约定,首字母大写的为公共对象,否则为私有对象。所以在 Go 语言中是没有 public、private 这样的语法的。
150 |
151 | 5. 枚举类型的第一个字段必须为 0 ;
152 | 这也是一个约定。
153 |
154 | 7. 移除了对分组的支持;
155 | 分组的功能完全可以用消息嵌套的方式来实现,并且更清晰。在 proto2 中已经把分组语法标注为『过期』了。这次也算清理垃圾了。
156 |
157 | 8.旧代码在解析新增字段时,会把不认识的字段丢弃,再序列化后新增的字段就没了;
158 | 在 proto2 中,旧代码虽然会忽视不认识的新增字段,但并不会将其丢弃,再序列化的时候那些字段会被原样保留。
159 | 我觉得还是 proto2 的处理方式更好一些。能尽量保持兼容性和扩展能力,或许实现起来也更简单。proto3 现在的处理方式,没有带来明显的好处,但丢掉了部分兼容性和灵活性。
160 | 经过漫长的讨论,官方终于同意在 proto3 中恢复 proto2 的处理方式了。 可以通过这个文档了解前因后果及时间线。
161 |
162 | 9. 移除了对扩展的支持,新增了 Any 类型;
163 | Any 类型是用来替代 proto2 中的扩展的。目前还在开发中。
164 | proto2 中的扩展特性很像 Swift 语言中的扩展。理解起来有点困难,使用起来更是会带来不少混乱。
165 | 相比之下,proto3 中新增的 Any 类型有点像 C/C++ 中的 void* ,好理解,使用起来逻辑也更清晰。
166 |
167 | 10. 增加了 JSON 映射特性;
168 | 语言的活力来自于与时俱进。当前,JSON 的流行有其充分的理由。很多『现代化』的语言都内置了对 JSON 的支持,比如 Go、PHP 等。而 C++ 这种看似保罗万象的学院派语言,因循守旧、故步自封,以致于现出了式微的苗头。
--------------------------------------------------------------------------------
/chapter20/20_pb1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter20/20_pb1.png
--------------------------------------------------------------------------------
/chapter20/brks接口文档.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter20/brks接口文档.docx
--------------------------------------------------------------------------------
/chapter20/协议设计.vsdx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter20/协议设计.vsdx
--------------------------------------------------------------------------------
/chapter20/课程笔记.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter20/课程笔记.docx
--------------------------------------------------------------------------------
/chapter21/多路复用的课堂笔记.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter21/多路复用的课堂笔记.docx
--------------------------------------------------------------------------------
/chapter21/阻塞非阻塞异步同步笔记.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter21/阻塞非阻塞异步同步笔记.docx
--------------------------------------------------------------------------------
/chapter22/libevent.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter22/libevent.docx
--------------------------------------------------------------------------------
/chapter22/libevent.ppt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter22/libevent.ppt
--------------------------------------------------------------------------------
/chapter23/23.1_创建线程和相关属性.md:
--------------------------------------------------------------------------------
1 | # 23.1 创建线程和相关属性
2 | ### 创建线程
3 | ```
4 | int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
5 | void *(*start_routine) (void *), void *arg);
6 |
7 | ```
8 |
9 | ### 设置线程的优先级
10 |
11 | Linux内核的三种调度策略:
12 |
13 | 1,SCHED_OTHER 分时调度策略:
14 | 它是默认的线程分时调度策略,所有的线程的优先级别都是0,线程的调度是通过分时来完成的。简单地说,如果系统使用这种调度策略,程序将无法设置线程的优先级。请注意,这种调度策略也是抢占式的,当高优先级的线程准备运行的时候,当前线程将被抢占并进入等待队列。这种调度策略仅仅决定线程在可运行线程队列中的具有相同优先级的线程的运行次序。
15 |
16 | 2,SCHED_FIFO实时调度策略,先到先服务。
17 | 它是一种实时的先进先出调用策略,且只能在超级用户下运行。这种调用策略仅仅被使用于优先级大于0的线程。它意味着,使用SCHED_FIFO的可运行线程将一直抢占使用SCHED_OTHER的运行线程J。此外SCHED_FIFO是一个非分时的简单调度策略,当一个线程变成可运行状态,它将被追加到对应优先级队列的尾部((POSIX 1003.1)。当所有高优先级的线程终止或者阻塞时,它将被运行。对于相同优先级别的线程,按照简单的先进先运行的规则运行。我们考虑一种很坏的情况,如果有若干相同优先级的线程等待执行,然而最早执行的线程无终止或者阻塞动作,那么其他线程是无法执行的,除非当前线程调用如pthread_yield之类的函数,所以在使用SCHED_FIFO的时候要小心处理相同级别线程的动作。
18 |
19 | 3,SCHED_RR实时调度策略,时间片轮转。当进程的时间片用完,系统将重新分配时间片,并置于就绪队列尾。放在队列尾保证了所有具有相同优先级的RR任务的调度公平。
20 |
21 | 系统创建线程时,默认的线程是SCHED_OTHER。所以如果我们要改变线程的调度策略的话,可以通过下面的这个函数实现。
22 |
23 | ```
24 | int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
25 | int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
26 | int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param);
27 | ```
28 |
29 | ### 线程的分离状态
30 |
31 | 1. 非分离状态,这种情况下,原有的线程等待创建的线程结束。只有当pthread_join()函数返回时,创建的线程才算终止,才能释放自己占用的系统资源。
32 | 2. 而分离线程不是这样子的,它没有被其他的线程所等待,自己运行结束了,线程也就终止了,马上释放系统资源。
33 |
34 | ```
35 | int pthread_attr_setdetachstate(pthread_attr_t * attr, int state);
36 | int pthread_attr_getdetachstate(pthread_attr_t const * attr, int * state);
37 |
38 | ```
39 |
40 | ### 线程CPU的亲缘性
41 | CPU的亲和性, 就是进程和线程要在指定的 CPU 上尽量长时间地运行而不被迁移到其他处理器,也称为CPU关联性;再简单的点的描述就将指定的进程或线程绑定到相应的cpu上;在多核运行的机器上,每个CPU本身自己会有缓存,缓存着进程和线程使用的信息,而进程或线程可能会被OS调度到其他CPU上,如此,CPU cache命中率就低了,当绑定CPU后,进程或线程就会一直在指定的cpu跑,不会由内核调度到其他CPU上,性能有一定的提高。
42 |
43 | ```
44 | int pthread_setaffinity_np(pthread_t thread, size_t cpusetsize,
45 | const cpu_set_t *cpuset);
46 | int pthread_getaffinity_np(pthread_t thread, size_t cpusetsize,
47 | cpu_set_t *cpuset);
48 | ```
49 |
50 | ### 线程栈的大小与私有数据
51 | 1. 线程栈:默认是1M,但是可以通过以下接口设置大小:
52 |
53 | ```
54 | int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
55 | int pthread_attr_getstacksize(const pthread_attr_t *attr, size_t *stacksize);
56 | ```
57 |
58 | 2. 线程私有数据:
59 | 设计中有必要提供线程私有的全局变量,这个变量仅在线程中有效,但却可以跨过多个函数访问。比如在程序里可能需要每个线程维护一个链表,而会使用相同的函数来操作这个链表,最简单的方法就是使用同名而不同变量地址的线程相关数据结构。这样的数据结构可以由 Posix 线程库维护,成为线程私有数据 (Thread-specific Data,或称为 TSD)。
60 |
61 | ```
62 | int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
63 | int pthread_key_delete(pthread_key_t key);
64 | int pthread_setspecific(pthread_key_t key, const void *value);
65 | void *pthread_getspecific(pthread_key_t key);
66 |
67 | errno :key - value(线程设置errno的key,并且设置errno的值)
68 |
69 | ```
70 |
71 | ### 栈溢出保护区
72 | 出于以下两个原因,为应用程序提供了 guardsize 属性:
73 |
74 | 溢出保护可能会导致系统资源浪费。如果应用程序创建大量线程,并且已知这些线程永远不会溢出其栈,则可以关闭溢出保护区。通过关闭溢出保护区,可以节省系统资源。
75 |
76 | 线程在栈上分配大型数据结构时,可能需要较大的溢出保护区来检测栈溢出。
77 |
78 | guardsize 参数提供了对栈指针溢出的保护。如果创建线程的栈时使用了保护功能,则实现会在栈的溢出端分配额外内存。此额外内存的作用与缓冲区一样,可以防止栈指针的栈溢出。如果应用程序溢出到此缓冲区中,这个错误可能会导致 SIGSEGV 信号被发送给该线程。
79 |
80 | 如果 guardsize 为零,则不会为使用 attr 创建的线程提供溢出保护区。如果 guardsize 大于零,则会为每个使用 attr 创建的线程提供大小至少为 guardsize 字节的溢出保护区。缺省情况下,线程具有实现定义的非零溢出保护区。
81 |
82 | 允许合乎惯例的实现,将 guardsize 的值向上舍入为可配置的系统变量 PAGESIZE 的倍数。请参见 sys/mman.h 中的 PAGESIZE。如果实现将 guardsize 的值向上舍入为 PAGESIZE 的倍数,则以 guardsize(先前调用 pthread_attr_setguardsize() 时指定的溢出保护区大小)为单位存储对指定 attr 的 pthread_attr_getguardsize() 的调用。
83 |
84 | ```
85 | int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
86 | int pthread_attr_getguardsize(const pthread_attr_t *attr, size_t *guardsize);
87 |
88 | ```
89 |
90 | ### 线程的竞争范围
91 |
92 | 线程的说使用资源的竞争范围(PTHREAD_SCOPE_SYSTEM 或 PTHREAD_SCOPE_PROCESS)。 使用 PTHREAD_SCOPE_SYSTEM 时,此线程将与系统中的所有线程进行竞争。使用 PTHREAD_SCOPE_PROCESS 时,此线程将与进程中的其他线程进行竞争。
93 | ```
94 | POSIX.1 requires that an implementation support at least one of these contention scopes. Linux supports PTHREAD_SCOPE_SYSTEM, but not PTHREAD_SCOPE_PROCESS.
95 |
96 | ```
97 | ```
98 | int pthread_attr_setscope(pthread_attr_t *attr, int scope);
99 | int pthread_attr_getscope(const pthread_attr_t *attr, int *scope);
100 |
101 | ```
--------------------------------------------------------------------------------
/chapter24/线程池的设计.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter24/线程池的设计.docx
--------------------------------------------------------------------------------
/chapter3/3.1_如何架构设计.md:
--------------------------------------------------------------------------------
1 | # 3.2 如何架构设计
2 |
3 |
--------------------------------------------------------------------------------
/chapter3/3.1_如何管理需求.md:
--------------------------------------------------------------------------------
1 | # 3.1 如何管理需求
2 |
3 |
--------------------------------------------------------------------------------
/chapter3/3.3_共享单车架构设计.md:
--------------------------------------------------------------------------------
1 | # 3.3 共享单车的架构
2 |
3 |
--------------------------------------------------------------------------------
/chapter3/README.md:
--------------------------------------------------------------------------------
1 | # 第三节 项目文档
2 |
3 |
--------------------------------------------------------------------------------
/chapter4/4.1_敏捷概述.md:
--------------------------------------------------------------------------------
1 | # 4.1 敏捷概述
2 |
3 |
--------------------------------------------------------------------------------
/chapter4/4.2_SCRUM介绍.md:
--------------------------------------------------------------------------------
1 | # 4.2 SCRUM介绍
2 |
3 |
--------------------------------------------------------------------------------
/chapter4/4.3_我们应该认识到.md:
--------------------------------------------------------------------------------
1 | # 4.3 我们应该认识到
2 |
3 |
--------------------------------------------------------------------------------
/chapter4/README.md:
--------------------------------------------------------------------------------
1 | # 第四节 敏捷开发
2 |
3 |
--------------------------------------------------------------------------------
/chapter5/5.2_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/5.2_1.png
--------------------------------------------------------------------------------
/chapter5/5.3_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/5.3_1.jpg
--------------------------------------------------------------------------------
/chapter5/5.3_搭建持续集成环境.md:
--------------------------------------------------------------------------------
1 | # 5.3 搭建持续集成环境
2 |
3 |
4 | * [前言](#1)
5 | * [传统开发方式的不足](#2)
6 | * [持续集成的好处](#3)
7 |
8 |
9 | 前言
10 |
11 | 什么是持续集成,为什么要持续集成?例如我们搭建起来的一个持续集成环境:
12 | 
13 |
14 | 传统开发方式的不足
15 |
16 | 
17 |
18 | 不足:
19 | * 代码是静态的,只有测试人员手工测试才能发现问题,甚至有的时候根本就不知道是否能够编译出一个可测试的程序
20 |
21 | * 所有的测试依赖手工测试,大量的测试活动重复进行;
22 |
23 | * 大型团队大家相互之间的依赖很强,但是却不能确定自己是否基于一个完整和高质量的版本上开发。
24 |
25 | * 如果有问题,并不能及时发现;
26 |
27 | 持续集成的好处
28 |
29 | 1.频繁检出代码。
30 | 有时候代码冲突无可避免,频繁检出代码,可以让本地的副本和代码库中的版本最小差异化。
31 |
32 | 2.频繁提交代码。
33 | 与1的原理类似,频繁提交代码,可以让其他人检出副本和代码库中的版本最小差异化。
34 |
35 | 3.减少分支,回归主干。
36 | 多个分支并行应及早将变更集成到主干中,避免同时维护软件的多个版本。
37 |
38 | 4.使用自动化构建。
39 | 可以使用Maven、Ant等来实现自动化构建,可以在构建过程中实现自动化测试。前提是有写单元测试用例。
40 |
41 | 5.提交测试。
42 | 在提交工作之前,每个程序员必须本地集成所有代码,做一个完整的构建和运行,并通过所有的单元测试,这样能减少集成测试在集成服务器上构建失败的风险。
43 |
44 | 6.当前状态对每个人都可见。
45 | 集成服务器在持续集成过程中发现问题应及时发送邮件和短信给相关的干系人。
46 |
47 | 7.如果发现有代码的问题,可以revert掉某个人的提交
48 |
49 | 8.可以直接部署到生产环境,而且是自动化的。
50 |
51 | 9.可以做很多的静态检查,比如圈复杂度、测试覆盖率、cpp pclint等等,哦太多丰富的插件去帮大家管理代码了。
52 | 来个thrift的持续集成图吧:
53 | [https://travis-ci.org/apache/thrift](https://travis-ci.org/apache/thrift)
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/chapter5/README.md:
--------------------------------------------------------------------------------
1 | # 第五节 持续集成
2 |
3 |
--------------------------------------------------------------------------------
/chapter5/_book/5.1_lua语言介绍.md:
--------------------------------------------------------------------------------
1 | # 5.1 lua语言介绍
2 |
3 |
--------------------------------------------------------------------------------
/chapter5/_book/5.2_gtest单元测试.md:
--------------------------------------------------------------------------------
1 | # 5.2 gtest单元测试
2 |
3 |
--------------------------------------------------------------------------------
/chapter5/_book/5.3_搭建持续集成环境.md:
--------------------------------------------------------------------------------
1 | # 5.3 搭建持续集成环境
2 |
3 |
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/fonts/fontawesome/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/_book/gitbook/fonts/fontawesome/FontAwesome.otf
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhiyong0804/server_develop_guide_book/8056015d0bb505994f1e3787f16decf182092203/chapter5/_book/gitbook/fonts/fontawesome/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/gitbook-plugin-fontsettings/fontsettings.js:
--------------------------------------------------------------------------------
1 | require(['gitbook', 'jquery'], function(gitbook, $) {
2 | // Configuration
3 | var MAX_SIZE = 4,
4 | MIN_SIZE = 0,
5 | BUTTON_ID;
6 |
7 | // Current fontsettings state
8 | var fontState;
9 |
10 | // Default themes
11 | var THEMES = [
12 | {
13 | config: 'white',
14 | text: 'White',
15 | id: 0
16 | },
17 | {
18 | config: 'sepia',
19 | text: 'Sepia',
20 | id: 1
21 | },
22 | {
23 | config: 'night',
24 | text: 'Night',
25 | id: 2
26 | }
27 | ];
28 |
29 | // Default font families
30 | var FAMILIES = [
31 | {
32 | config: 'serif',
33 | text: 'Serif',
34 | id: 0
35 | },
36 | {
37 | config: 'sans',
38 | text: 'Sans',
39 | id: 1
40 | }
41 | ];
42 |
43 | // Return configured themes
44 | function getThemes() {
45 | return THEMES;
46 | }
47 |
48 | // Modify configured themes
49 | function setThemes(themes) {
50 | THEMES = themes;
51 | updateButtons();
52 | }
53 |
54 | // Return configured font families
55 | function getFamilies() {
56 | return FAMILIES;
57 | }
58 |
59 | // Modify configured font families
60 | function setFamilies(families) {
61 | FAMILIES = families;
62 | updateButtons();
63 | }
64 |
65 | // Save current font settings
66 | function saveFontSettings() {
67 | gitbook.storage.set('fontState', fontState);
68 | update();
69 | }
70 |
71 | // Increase font size
72 | function enlargeFontSize(e) {
73 | e.preventDefault();
74 | if (fontState.size >= MAX_SIZE) return;
75 |
76 | fontState.size++;
77 | saveFontSettings();
78 | }
79 |
80 | // Decrease font size
81 | function reduceFontSize(e) {
82 | e.preventDefault();
83 | if (fontState.size <= MIN_SIZE) return;
84 |
85 | fontState.size--;
86 | saveFontSettings();
87 | }
88 |
89 | // Change font family
90 | function changeFontFamily(configName, e) {
91 | if (e && e instanceof Event) {
92 | e.preventDefault();
93 | }
94 |
95 | var familyId = getFontFamilyId(configName);
96 | fontState.family = familyId;
97 | saveFontSettings();
98 | }
99 |
100 | // Change type of color theme
101 | function changeColorTheme(configName, e) {
102 | if (e && e instanceof Event) {
103 | e.preventDefault();
104 | }
105 |
106 | var $book = gitbook.state.$book;
107 |
108 | // Remove currently applied color theme
109 | if (fontState.theme !== 0)
110 | $book.removeClass('color-theme-'+fontState.theme);
111 |
112 | // Set new color theme
113 | var themeId = getThemeId(configName);
114 | fontState.theme = themeId;
115 | if (fontState.theme !== 0)
116 | $book.addClass('color-theme-'+fontState.theme);
117 |
118 | saveFontSettings();
119 | }
120 |
121 | // Return the correct id for a font-family config key
122 | // Default to first font-family
123 | function getFontFamilyId(configName) {
124 | // Search for plugin configured font family
125 | var configFamily = $.grep(FAMILIES, function(family) {
126 | return family.config == configName;
127 | })[0];
128 | // Fallback to default font family
129 | return (!!configFamily)? configFamily.id : 0;
130 | }
131 |
132 | // Return the correct id for a theme config key
133 | // Default to first theme
134 | function getThemeId(configName) {
135 | // Search for plugin configured theme
136 | var configTheme = $.grep(THEMES, function(theme) {
137 | return theme.config == configName;
138 | })[0];
139 | // Fallback to default theme
140 | return (!!configTheme)? configTheme.id : 0;
141 | }
142 |
143 | function update() {
144 | var $book = gitbook.state.$book;
145 |
146 | $('.font-settings .font-family-list li').removeClass('active');
147 | $('.font-settings .font-family-list li:nth-child('+(fontState.family+1)+')').addClass('active');
148 |
149 | $book[0].className = $book[0].className.replace(/\bfont-\S+/g, '');
150 | $book.addClass('font-size-'+fontState.size);
151 | $book.addClass('font-family-'+fontState.family);
152 |
153 | if(fontState.theme !== 0) {
154 | $book[0].className = $book[0].className.replace(/\bcolor-theme-\S+/g, '');
155 | $book.addClass('color-theme-'+fontState.theme);
156 | }
157 | }
158 |
159 | function init(config) {
160 | // Search for plugin configured font family
161 | var configFamily = getFontFamilyId(config.family),
162 | configTheme = getThemeId(config.theme);
163 |
164 | // Instantiate font state object
165 | fontState = gitbook.storage.get('fontState', {
166 | size: config.size || 2,
167 | family: configFamily,
168 | theme: configTheme
169 | });
170 |
171 | update();
172 | }
173 |
174 | function updateButtons() {
175 | // Remove existing fontsettings buttons
176 | if (!!BUTTON_ID) {
177 | gitbook.toolbar.removeButton(BUTTON_ID);
178 | }
179 |
180 | // Create buttons in toolbar
181 | BUTTON_ID = gitbook.toolbar.createButton({
182 | icon: 'fa fa-font',
183 | label: 'Font Settings',
184 | className: 'font-settings',
185 | dropdown: [
186 | [
187 | {
188 | text: 'A',
189 | className: 'font-reduce',
190 | onClick: reduceFontSize
191 | },
192 | {
193 | text: 'A',
194 | className: 'font-enlarge',
195 | onClick: enlargeFontSize
196 | }
197 | ],
198 | $.map(FAMILIES, function(family) {
199 | family.onClick = function(e) {
200 | return changeFontFamily(family.config, e);
201 | };
202 |
203 | return family;
204 | }),
205 | $.map(THEMES, function(theme) {
206 | theme.onClick = function(e) {
207 | return changeColorTheme(theme.config, e);
208 | };
209 |
210 | return theme;
211 | })
212 | ]
213 | });
214 | }
215 |
216 | // Init configuration at start
217 | gitbook.events.bind('start', function(e, config) {
218 | var opts = config.fontsettings;
219 |
220 | // Generate buttons at start
221 | updateButtons();
222 |
223 | // Init current settings
224 | init(opts);
225 | });
226 |
227 | // Expose API
228 | gitbook.fontsettings = {
229 | enlargeFontSize: enlargeFontSize,
230 | reduceFontSize: reduceFontSize,
231 | setTheme: changeColorTheme,
232 | setFamily: changeFontFamily,
233 | getThemes: getThemes,
234 | setThemes: setThemes,
235 | getFamilies: getFamilies,
236 | setFamilies: setFamilies
237 | };
238 | });
239 |
240 |
241 |
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/gitbook-plugin-fontsettings/website.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Theme 1
3 | */
4 | .color-theme-1 .dropdown-menu {
5 | background-color: #111111;
6 | border-color: #7e888b;
7 | }
8 | .color-theme-1 .dropdown-menu .dropdown-caret .caret-inner {
9 | border-bottom: 9px solid #111111;
10 | }
11 | .color-theme-1 .dropdown-menu .buttons {
12 | border-color: #7e888b;
13 | }
14 | .color-theme-1 .dropdown-menu .button {
15 | color: #afa790;
16 | }
17 | .color-theme-1 .dropdown-menu .button:hover {
18 | color: #73553c;
19 | }
20 | /*
21 | * Theme 2
22 | */
23 | .color-theme-2 .dropdown-menu {
24 | background-color: #2d3143;
25 | border-color: #272a3a;
26 | }
27 | .color-theme-2 .dropdown-menu .dropdown-caret .caret-inner {
28 | border-bottom: 9px solid #2d3143;
29 | }
30 | .color-theme-2 .dropdown-menu .buttons {
31 | border-color: #272a3a;
32 | }
33 | .color-theme-2 .dropdown-menu .button {
34 | color: #62677f;
35 | }
36 | .color-theme-2 .dropdown-menu .button:hover {
37 | color: #f4f4f5;
38 | }
39 | .book .book-header .font-settings .font-enlarge {
40 | line-height: 30px;
41 | font-size: 1.4em;
42 | }
43 | .book .book-header .font-settings .font-reduce {
44 | line-height: 30px;
45 | font-size: 1em;
46 | }
47 | .book.color-theme-1 .book-body {
48 | color: #704214;
49 | background: #f3eacb;
50 | }
51 | .book.color-theme-1 .book-body .page-wrapper .page-inner section {
52 | background: #f3eacb;
53 | }
54 | .book.color-theme-2 .book-body {
55 | color: #bdcadb;
56 | background: #1c1f2b;
57 | }
58 | .book.color-theme-2 .book-body .page-wrapper .page-inner section {
59 | background: #1c1f2b;
60 | }
61 | .book.font-size-0 .book-body .page-inner section {
62 | font-size: 1.2rem;
63 | }
64 | .book.font-size-1 .book-body .page-inner section {
65 | font-size: 1.4rem;
66 | }
67 | .book.font-size-2 .book-body .page-inner section {
68 | font-size: 1.6rem;
69 | }
70 | .book.font-size-3 .book-body .page-inner section {
71 | font-size: 2.2rem;
72 | }
73 | .book.font-size-4 .book-body .page-inner section {
74 | font-size: 4rem;
75 | }
76 | .book.font-family-0 {
77 | font-family: Georgia, serif;
78 | }
79 | .book.font-family-1 {
80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
81 | }
82 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal {
83 | color: #704214;
84 | }
85 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal a {
86 | color: inherit;
87 | }
88 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
89 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2,
90 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h3,
91 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h4,
92 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h5,
93 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
94 | color: inherit;
95 | }
96 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h1,
97 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h2 {
98 | border-color: inherit;
99 | }
100 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal h6 {
101 | color: inherit;
102 | }
103 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal hr {
104 | background-color: inherit;
105 | }
106 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal blockquote {
107 | border-color: inherit;
108 | }
109 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal pre,
110 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal code {
111 | background: #fdf6e3;
112 | color: #657b83;
113 | border-color: #f8df9c;
114 | }
115 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal .highlight {
116 | background-color: inherit;
117 | }
118 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table th,
119 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table td {
120 | border-color: #f5d06c;
121 | }
122 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr {
123 | color: inherit;
124 | background-color: #fdf6e3;
125 | border-color: #444444;
126 | }
127 | .book.color-theme-1 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
128 | background-color: #fbeecb;
129 | }
130 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal {
131 | color: #bdcadb;
132 | }
133 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal a {
134 | color: #3eb1d0;
135 | }
136 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
137 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2,
138 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h3,
139 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h4,
140 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h5,
141 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
142 | color: #fffffa;
143 | }
144 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h1,
145 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h2 {
146 | border-color: #373b4e;
147 | }
148 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal h6 {
149 | color: #373b4e;
150 | }
151 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal hr {
152 | background-color: #373b4e;
153 | }
154 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal blockquote {
155 | border-color: #373b4e;
156 | }
157 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal pre,
158 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal code {
159 | color: #9dbed8;
160 | background: #2d3143;
161 | border-color: #2d3143;
162 | }
163 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal .highlight {
164 | background-color: #282a39;
165 | }
166 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table th,
167 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table td {
168 | border-color: #3b3f54;
169 | }
170 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr {
171 | color: #b6c2d2;
172 | background-color: #2d3143;
173 | border-color: #3b3f54;
174 | }
175 | .book.color-theme-2 .book-body .page-wrapper .page-inner section.normal table tr:nth-child(2n) {
176 | background-color: #35394b;
177 | }
178 | .book.color-theme-1 .book-header {
179 | color: #afa790;
180 | background: transparent;
181 | }
182 | .book.color-theme-1 .book-header .btn {
183 | color: #afa790;
184 | }
185 | .book.color-theme-1 .book-header .btn:hover {
186 | color: #73553c;
187 | background: none;
188 | }
189 | .book.color-theme-1 .book-header h1 {
190 | color: #704214;
191 | }
192 | .book.color-theme-2 .book-header {
193 | color: #7e888b;
194 | background: transparent;
195 | }
196 | .book.color-theme-2 .book-header .btn {
197 | color: #3b3f54;
198 | }
199 | .book.color-theme-2 .book-header .btn:hover {
200 | color: #fffff5;
201 | background: none;
202 | }
203 | .book.color-theme-2 .book-header h1 {
204 | color: #bdcadb;
205 | }
206 | .book.color-theme-1 .book-body .navigation {
207 | color: #afa790;
208 | }
209 | .book.color-theme-1 .book-body .navigation:hover {
210 | color: #73553c;
211 | }
212 | .book.color-theme-2 .book-body .navigation {
213 | color: #383f52;
214 | }
215 | .book.color-theme-2 .book-body .navigation:hover {
216 | color: #fffff5;
217 | }
218 | /*
219 | * Theme 1
220 | */
221 | .book.color-theme-1 .book-summary {
222 | color: #afa790;
223 | background: #111111;
224 | border-right: 1px solid rgba(0, 0, 0, 0.07);
225 | }
226 | .book.color-theme-1 .book-summary .book-search {
227 | background: transparent;
228 | }
229 | .book.color-theme-1 .book-summary .book-search input,
230 | .book.color-theme-1 .book-summary .book-search input:focus {
231 | border: 1px solid transparent;
232 | }
233 | .book.color-theme-1 .book-summary ul.summary li.divider {
234 | background: #7e888b;
235 | box-shadow: none;
236 | }
237 | .book.color-theme-1 .book-summary ul.summary li i.fa-check {
238 | color: #33cc33;
239 | }
240 | .book.color-theme-1 .book-summary ul.summary li.done > a {
241 | color: #877f6a;
242 | }
243 | .book.color-theme-1 .book-summary ul.summary li a,
244 | .book.color-theme-1 .book-summary ul.summary li span {
245 | color: #877f6a;
246 | background: transparent;
247 | font-weight: normal;
248 | }
249 | .book.color-theme-1 .book-summary ul.summary li.active > a,
250 | .book.color-theme-1 .book-summary ul.summary li a:hover {
251 | color: #704214;
252 | background: transparent;
253 | font-weight: normal;
254 | }
255 | /*
256 | * Theme 2
257 | */
258 | .book.color-theme-2 .book-summary {
259 | color: #bcc1d2;
260 | background: #2d3143;
261 | border-right: none;
262 | }
263 | .book.color-theme-2 .book-summary .book-search {
264 | background: transparent;
265 | }
266 | .book.color-theme-2 .book-summary .book-search input,
267 | .book.color-theme-2 .book-summary .book-search input:focus {
268 | border: 1px solid transparent;
269 | }
270 | .book.color-theme-2 .book-summary ul.summary li.divider {
271 | background: #272a3a;
272 | box-shadow: none;
273 | }
274 | .book.color-theme-2 .book-summary ul.summary li i.fa-check {
275 | color: #33cc33;
276 | }
277 | .book.color-theme-2 .book-summary ul.summary li.done > a {
278 | color: #62687f;
279 | }
280 | .book.color-theme-2 .book-summary ul.summary li a,
281 | .book.color-theme-2 .book-summary ul.summary li span {
282 | color: #c1c6d7;
283 | background: transparent;
284 | font-weight: 600;
285 | }
286 | .book.color-theme-2 .book-summary ul.summary li.active > a,
287 | .book.color-theme-2 .book-summary ul.summary li a:hover {
288 | color: #f4f4f5;
289 | background: #252737;
290 | font-weight: 600;
291 | }
292 |
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/gitbook-plugin-highlight/ebook.css:
--------------------------------------------------------------------------------
1 | pre,
2 | code {
3 | /* http://jmblog.github.io/color-themes-for-highlightjs */
4 | /* Tomorrow Comment */
5 | /* Tomorrow Red */
6 | /* Tomorrow Orange */
7 | /* Tomorrow Yellow */
8 | /* Tomorrow Green */
9 | /* Tomorrow Aqua */
10 | /* Tomorrow Blue */
11 | /* Tomorrow Purple */
12 | }
13 | pre .hljs-comment,
14 | code .hljs-comment,
15 | pre .hljs-title,
16 | code .hljs-title {
17 | color: #8e908c;
18 | }
19 | pre .hljs-variable,
20 | code .hljs-variable,
21 | pre .hljs-attribute,
22 | code .hljs-attribute,
23 | pre .hljs-tag,
24 | code .hljs-tag,
25 | pre .hljs-regexp,
26 | code .hljs-regexp,
27 | pre .hljs-deletion,
28 | code .hljs-deletion,
29 | pre .ruby .hljs-constant,
30 | code .ruby .hljs-constant,
31 | pre .xml .hljs-tag .hljs-title,
32 | code .xml .hljs-tag .hljs-title,
33 | pre .xml .hljs-pi,
34 | code .xml .hljs-pi,
35 | pre .xml .hljs-doctype,
36 | code .xml .hljs-doctype,
37 | pre .html .hljs-doctype,
38 | code .html .hljs-doctype,
39 | pre .css .hljs-id,
40 | code .css .hljs-id,
41 | pre .css .hljs-class,
42 | code .css .hljs-class,
43 | pre .css .hljs-pseudo,
44 | code .css .hljs-pseudo {
45 | color: #c82829;
46 | }
47 | pre .hljs-number,
48 | code .hljs-number,
49 | pre .hljs-preprocessor,
50 | code .hljs-preprocessor,
51 | pre .hljs-pragma,
52 | code .hljs-pragma,
53 | pre .hljs-built_in,
54 | code .hljs-built_in,
55 | pre .hljs-literal,
56 | code .hljs-literal,
57 | pre .hljs-params,
58 | code .hljs-params,
59 | pre .hljs-constant,
60 | code .hljs-constant {
61 | color: #f5871f;
62 | }
63 | pre .ruby .hljs-class .hljs-title,
64 | code .ruby .hljs-class .hljs-title,
65 | pre .css .hljs-rules .hljs-attribute,
66 | code .css .hljs-rules .hljs-attribute {
67 | color: #eab700;
68 | }
69 | pre .hljs-string,
70 | code .hljs-string,
71 | pre .hljs-value,
72 | code .hljs-value,
73 | pre .hljs-inheritance,
74 | code .hljs-inheritance,
75 | pre .hljs-header,
76 | code .hljs-header,
77 | pre .hljs-addition,
78 | code .hljs-addition,
79 | pre .ruby .hljs-symbol,
80 | code .ruby .hljs-symbol,
81 | pre .xml .hljs-cdata,
82 | code .xml .hljs-cdata {
83 | color: #718c00;
84 | }
85 | pre .css .hljs-hexcolor,
86 | code .css .hljs-hexcolor {
87 | color: #3e999f;
88 | }
89 | pre .hljs-function,
90 | code .hljs-function,
91 | pre .python .hljs-decorator,
92 | code .python .hljs-decorator,
93 | pre .python .hljs-title,
94 | code .python .hljs-title,
95 | pre .ruby .hljs-function .hljs-title,
96 | code .ruby .hljs-function .hljs-title,
97 | pre .ruby .hljs-title .hljs-keyword,
98 | code .ruby .hljs-title .hljs-keyword,
99 | pre .perl .hljs-sub,
100 | code .perl .hljs-sub,
101 | pre .javascript .hljs-title,
102 | code .javascript .hljs-title,
103 | pre .coffeescript .hljs-title,
104 | code .coffeescript .hljs-title {
105 | color: #4271ae;
106 | }
107 | pre .hljs-keyword,
108 | code .hljs-keyword,
109 | pre .javascript .hljs-function,
110 | code .javascript .hljs-function {
111 | color: #8959a8;
112 | }
113 | pre .hljs,
114 | code .hljs {
115 | display: block;
116 | background: white;
117 | color: #4d4d4c;
118 | padding: 0.5em;
119 | }
120 | pre .coffeescript .javascript,
121 | code .coffeescript .javascript,
122 | pre .javascript .xml,
123 | code .javascript .xml,
124 | pre .tex .hljs-formula,
125 | code .tex .hljs-formula,
126 | pre .xml .javascript,
127 | code .xml .javascript,
128 | pre .xml .vbscript,
129 | code .xml .vbscript,
130 | pre .xml .css,
131 | code .xml .css,
132 | pre .xml .hljs-cdata,
133 | code .xml .hljs-cdata {
134 | opacity: 0.5;
135 | }
136 |
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/gitbook-plugin-livereload/plugin.js:
--------------------------------------------------------------------------------
1 | (function() {
2 | var newEl = document.createElement('script'),
3 | firstScriptTag = document.getElementsByTagName('script')[0];
4 |
5 | if (firstScriptTag) {
6 | newEl.async = 1;
7 | newEl.src = '//' + window.location.hostname + ':35729/livereload.js';
8 | firstScriptTag.parentNode.insertBefore(newEl, firstScriptTag);
9 | }
10 |
11 | })();
12 |
--------------------------------------------------------------------------------
/chapter5/_book/gitbook/gitbook-plugin-lunr/lunr.min.js:
--------------------------------------------------------------------------------
1 | /**
2 | * lunr - http://lunrjs.com - A bit like Solr, but much smaller and not as bright - 0.5.12
3 | * Copyright (C) 2015 Oliver Nightingale
4 | * MIT Licensed
5 | * @license
6 | */
7 | !function(){var t=function(e){var n=new t.Index;return n.pipeline.add(t.trimmer,t.stopWordFilter,t.stemmer),e&&e.call(n,n),n};t.version="0.5.12",t.utils={},t.utils.warn=function(t){return function(e){t.console&&console.warn&&console.warn(e)}}(this),t.EventEmitter=function(){this.events={}},t.EventEmitter.prototype.addListener=function(){var t=Array.prototype.slice.call(arguments),e=t.pop(),n=t;if("function"!=typeof e)throw new TypeError("last argument must be a function");n.forEach(function(t){this.hasHandler(t)||(this.events[t]=[]),this.events[t].push(e)},this)},t.EventEmitter.prototype.removeListener=function(t,e){if(this.hasHandler(t)){var n=this.events[t].indexOf(e);this.events[t].splice(n,1),this.events[t].length||delete this.events[t]}},t.EventEmitter.prototype.emit=function(t){if(this.hasHandler(t)){var e=Array.prototype.slice.call(arguments,1);this.events[t].forEach(function(t){t.apply(void 0,e)})}},t.EventEmitter.prototype.hasHandler=function(t){return t in this.events},t.tokenizer=function(t){return arguments.length&&null!=t&&void 0!=t?Array.isArray(t)?t.map(function(t){return t.toLowerCase()}):t.toString().trim().toLowerCase().split(/[\s\-]+/):[]},t.Pipeline=function(){this._stack=[]},t.Pipeline.registeredFunctions={},t.Pipeline.registerFunction=function(e,n){n in this.registeredFunctions&&t.utils.warn("Overwriting existing registered function: "+n),e.label=n,t.Pipeline.registeredFunctions[e.label]=e},t.Pipeline.warnIfFunctionNotRegistered=function(e){var n=e.label&&e.label in this.registeredFunctions;n||t.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},t.Pipeline.load=function(e){var n=new t.Pipeline;return e.forEach(function(e){var i=t.Pipeline.registeredFunctions[e];if(!i)throw new Error("Cannot load un-registered function: "+e);n.add(i)}),n},t.Pipeline.prototype.add=function(){var e=Array.prototype.slice.call(arguments);e.forEach(function(e){t.Pipeline.warnIfFunctionNotRegistered(e),this._stack.push(e)},this)},t.Pipeline.prototype.after=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");i+=1,this._stack.splice(i,0,n)},t.Pipeline.prototype.before=function(e,n){t.Pipeline.warnIfFunctionNotRegistered(n);var i=this._stack.indexOf(e);if(-1==i)throw new Error("Cannot find existingFn");this._stack.splice(i,0,n)},t.Pipeline.prototype.remove=function(t){var e=this._stack.indexOf(t);-1!=e&&this._stack.splice(e,1)},t.Pipeline.prototype.run=function(t){for(var e=[],n=t.length,i=this._stack.length,o=0;n>o;o++){for(var r=t[o],s=0;i>s&&(r=this._stack[s](r,o,t),void 0!==r);s++);void 0!==r&&e.push(r)}return e},t.Pipeline.prototype.reset=function(){this._stack=[]},t.Pipeline.prototype.toJSON=function(){return this._stack.map(function(e){return t.Pipeline.warnIfFunctionNotRegistered(e),e.label})},t.Vector=function(){this._magnitude=null,this.list=void 0,this.length=0},t.Vector.Node=function(t,e,n){this.idx=t,this.val=e,this.next=n},t.Vector.prototype.insert=function(e,n){this._magnitude=void 0;var i=this.list;if(!i)return this.list=new t.Vector.Node(e,n,i),this.length++;if(en.idx?n=n.next:(i+=e.val*n.val,e=e.next,n=n.next);return i},t.Vector.prototype.similarity=function(t){return this.dot(t)/(this.magnitude()*t.magnitude())},t.SortedSet=function(){this.length=0,this.elements=[]},t.SortedSet.load=function(t){var e=new this;return e.elements=t,e.length=t.length,e},t.SortedSet.prototype.add=function(){var t,e;for(t=0;t1;){if(r===t)return o;t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o]}return r===t?o:-1},t.SortedSet.prototype.locationFor=function(t){for(var e=0,n=this.elements.length,i=n-e,o=e+Math.floor(i/2),r=this.elements[o];i>1;)t>r&&(e=o),r>t&&(n=o),i=n-e,o=e+Math.floor(i/2),r=this.elements[o];return r>t?o:t>r?o+1:void 0},t.SortedSet.prototype.intersect=function(e){for(var n=new t.SortedSet,i=0,o=0,r=this.length,s=e.length,a=this.elements,h=e.elements;;){if(i>r-1||o>s-1)break;a[i]!==h[o]?a[i]h[o]&&o++:(n.add(a[i]),i++,o++)}return n},t.SortedSet.prototype.clone=function(){var e=new t.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},t.SortedSet.prototype.union=function(t){var e,n,i;return this.length>=t.length?(e=this,n=t):(e=t,n=this),i=e.clone(),i.add.apply(i,n.toArray()),i},t.SortedSet.prototype.toJSON=function(){return this.toArray()},t.Index=function(){this._fields=[],this._ref="id",this.pipeline=new t.Pipeline,this.documentStore=new t.Store,this.tokenStore=new t.TokenStore,this.corpusTokens=new t.SortedSet,this.eventEmitter=new t.EventEmitter,this._idfCache={},this.on("add","remove","update",function(){this._idfCache={}}.bind(this))},t.Index.prototype.on=function(){var t=Array.prototype.slice.call(arguments);return this.eventEmitter.addListener.apply(this.eventEmitter,t)},t.Index.prototype.off=function(t,e){return this.eventEmitter.removeListener(t,e)},t.Index.load=function(e){e.version!==t.version&&t.utils.warn("version mismatch: current "+t.version+" importing "+e.version);var n=new this;return n._fields=e.fields,n._ref=e.ref,n.documentStore=t.Store.load(e.documentStore),n.tokenStore=t.TokenStore.load(e.tokenStore),n.corpusTokens=t.SortedSet.load(e.corpusTokens),n.pipeline=t.Pipeline.load(e.pipeline),n},t.Index.prototype.field=function(t,e){var e=e||{},n={name:t,boost:e.boost||1};return this._fields.push(n),this},t.Index.prototype.ref=function(t){return this._ref=t,this},t.Index.prototype.add=function(e,n){var i={},o=new t.SortedSet,r=e[this._ref],n=void 0===n?!0:n;this._fields.forEach(function(n){var r=this.pipeline.run(t.tokenizer(e[n.name]));i[n.name]=r,t.SortedSet.prototype.add.apply(o,r)},this),this.documentStore.set(r,o),t.SortedSet.prototype.add.apply(this.corpusTokens,o.toArray());for(var s=0;s0&&(i=1+Math.log(this.documentStore.length/n)),this._idfCache[e]=i},t.Index.prototype.search=function(e){var n=this.pipeline.run(t.tokenizer(e)),i=new t.Vector,o=[],r=this._fields.reduce(function(t,e){return t+e.boost},0),s=n.some(function(t){return this.tokenStore.has(t)},this);if(!s)return[];n.forEach(function(e,n,s){var a=1/s.length*this._fields.length*r,h=this,l=this.tokenStore.expand(e).reduce(function(n,o){var r=h.corpusTokens.indexOf(o),s=h.idf(o),l=1,u=new t.SortedSet;if(o!==e){var c=Math.max(3,o.length-e.length);l=1/Math.log(c)}return r>-1&&i.insert(r,a*s*l),Object.keys(h.tokenStore.get(o)).forEach(function(t){u.add(t)}),n.union(u)},new t.SortedSet);o.push(l)},this);var a=o.reduce(function(t,e){return t.intersect(e)});return a.map(function(t){return{ref:t,score:i.similarity(this.documentVector(t))}},this).sort(function(t,e){return e.score-t.score})},t.Index.prototype.documentVector=function(e){for(var n=this.documentStore.get(e),i=n.length,o=new t.Vector,r=0;i>r;r++){var s=n.elements[r],a=this.tokenStore.get(s)[e].tf,h=this.idf(s);o.insert(this.corpusTokens.indexOf(s),a*h)}return o},t.Index.prototype.toJSON=function(){return{version:t.version,fields:this._fields,ref:this._ref,documentStore:this.documentStore.toJSON(),tokenStore:this.tokenStore.toJSON(),corpusTokens:this.corpusTokens.toJSON(),pipeline:this.pipeline.toJSON()}},t.Index.prototype.use=function(t){var e=Array.prototype.slice.call(arguments,1);e.unshift(this),t.apply(this,e)},t.Store=function(){this.store={},this.length=0},t.Store.load=function(e){var n=new this;return n.length=e.length,n.store=Object.keys(e.store).reduce(function(n,i){return n[i]=t.SortedSet.load(e.store[i]),n},{}),n},t.Store.prototype.set=function(t,e){this.has(t)||this.length++,this.store[t]=e},t.Store.prototype.get=function(t){return this.store[t]},t.Store.prototype.has=function(t){return t in this.store},t.Store.prototype.remove=function(t){this.has(t)&&(delete this.store[t],this.length--)},t.Store.prototype.toJSON=function(){return{store:this.store,length:this.length}},t.stemmer=function(){var t={ational:"ate",tional:"tion",enci:"ence",anci:"ance",izer:"ize",bli:"ble",alli:"al",entli:"ent",eli:"e",ousli:"ous",ization:"ize",ation:"ate",ator:"ate",alism:"al",iveness:"ive",fulness:"ful",ousness:"ous",aliti:"al",iviti:"ive",biliti:"ble",logi:"log"},e={icate:"ic",ative:"",alize:"al",iciti:"ic",ical:"ic",ful:"",ness:""},n="[^aeiou]",i="[aeiouy]",o=n+"[^aeiouy]*",r=i+"[aeiou]*",s="^("+o+")?"+r+o,a="^("+o+")?"+r+o+"("+r+")?$",h="^("+o+")?"+r+o+r+o,l="^("+o+")?"+i,u=new RegExp(s),c=new RegExp(h),f=new RegExp(a),d=new RegExp(l),p=/^(.+?)(ss|i)es$/,m=/^(.+?)([^s])s$/,v=/^(.+?)eed$/,y=/^(.+?)(ed|ing)$/,g=/.$/,S=/(at|bl|iz)$/,w=new RegExp("([^aeiouylsz])\\1$"),x=new RegExp("^"+o+i+"[^aeiouwxy]$"),k=/^(.+?[^aeiou])y$/,b=/^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/,E=/^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/,_=/^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/,F=/^(.+?)(s|t)(ion)$/,O=/^(.+?)e$/,P=/ll$/,N=new RegExp("^"+o+i+"[^aeiouwxy]$"),T=function(n){var i,o,r,s,a,h,l;if(n.length<3)return n;if(r=n.substr(0,1),"y"==r&&(n=r.toUpperCase()+n.substr(1)),s=p,a=m,s.test(n)?n=n.replace(s,"$1$2"):a.test(n)&&(n=n.replace(a,"$1$2")),s=v,a=y,s.test(n)){var T=s.exec(n);s=u,s.test(T[1])&&(s=g,n=n.replace(s,""))}else if(a.test(n)){var T=a.exec(n);i=T[1],a=d,a.test(i)&&(n=i,a=S,h=w,l=x,a.test(n)?n+="e":h.test(n)?(s=g,n=n.replace(s,"")):l.test(n)&&(n+="e"))}if(s=k,s.test(n)){var T=s.exec(n);i=T[1],n=i+"i"}if(s=b,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+t[o])}if(s=E,s.test(n)){var T=s.exec(n);i=T[1],o=T[2],s=u,s.test(i)&&(n=i+e[o])}if(s=_,a=F,s.test(n)){var T=s.exec(n);i=T[1],s=c,s.test(i)&&(n=i)}else if(a.test(n)){var T=a.exec(n);i=T[1]+T[2],a=c,a.test(i)&&(n=i)}if(s=O,s.test(n)){var T=s.exec(n);i=T[1],s=c,a=f,h=N,(s.test(i)||a.test(i)&&!h.test(i))&&(n=i)}return s=P,a=c,s.test(n)&&a.test(n)&&(s=g,n=n.replace(s,"")),"y"==r&&(n=r.toLowerCase()+n.substr(1)),n};return T}(),t.Pipeline.registerFunction(t.stemmer,"stemmer"),t.stopWordFilter=function(e){return e&&t.stopWordFilter.stopWords[e]!==e?e:void 0},t.stopWordFilter.stopWords={a:"a",able:"able",about:"about",across:"across",after:"after",all:"all",almost:"almost",also:"also",am:"am",among:"among",an:"an",and:"and",any:"any",are:"are",as:"as",at:"at",be:"be",because:"because",been:"been",but:"but",by:"by",can:"can",cannot:"cannot",could:"could",dear:"dear",did:"did","do":"do",does:"does",either:"either","else":"else",ever:"ever",every:"every","for":"for",from:"from",get:"get",got:"got",had:"had",has:"has",have:"have",he:"he",her:"her",hers:"hers",him:"him",his:"his",how:"how",however:"however",i:"i","if":"if","in":"in",into:"into",is:"is",it:"it",its:"its",just:"just",least:"least",let:"let",like:"like",likely:"likely",may:"may",me:"me",might:"might",most:"most",must:"must",my:"my",neither:"neither",no:"no",nor:"nor",not:"not",of:"of",off:"off",often:"often",on:"on",only:"only",or:"or",other:"other",our:"our",own:"own",rather:"rather",said:"said",say:"say",says:"says",she:"she",should:"should",since:"since",so:"so",some:"some",than:"than",that:"that",the:"the",their:"their",them:"them",then:"then",there:"there",these:"these",they:"they","this":"this",tis:"tis",to:"to",too:"too",twas:"twas",us:"us",wants:"wants",was:"was",we:"we",were:"were",what:"what",when:"when",where:"where",which:"which","while":"while",who:"who",whom:"whom",why:"why",will:"will","with":"with",would:"would",yet:"yet",you:"you",your:"your"},t.Pipeline.registerFunction(t.stopWordFilter,"stopWordFilter"),t.trimmer=function(t){var e=t.replace(/^\W+/,"").replace(/\W+$/,"");return""===e?void 0:e},t.Pipeline.registerFunction(t.trimmer,"trimmer"),t.TokenStore=function(){this.root={docs:{}},this.length=0},t.TokenStore.load=function(t){var e=new this;return e.root=t.root,e.length=t.length,e},t.TokenStore.prototype.add=function(t,e,n){var n=n||this.root,i=t[0],o=t.slice(1);return i in n||(n[i]={docs:{}}),0===o.length?(n[i].docs[e.ref]=e,void(this.length+=1)):this.add(o,e,n[i])},t.TokenStore.prototype.has=function(t){if(!t)return!1;for(var e=this.root,n=0;n element for each result
48 | res.results.forEach(function(res) {
49 | var $li = $('', {
50 | 'class': 'search-results-item'
51 | });
52 |
53 | var $title = $('