目录
[1. 应用层的作用](#1. 应用层的作用)
[2. 自定义应用层协议](#2. 自定义应用层协议)
[3. 应用层的 "通用协议格式"](#3. 应用层的 "通用协议格式")
[3.1 xml](#3.1 xml)
[3.2 json](#3.2 json)
[3.3 protobuffer (pd)](#3.3 protobuffer (pd))
[1. UDP](#1. UDP)
[1.1 无连接](#1.1 无连接)
[1.2 不可靠传输](#1.2 不可靠传输)
[1.3 面向数据报](#1.3 面向数据报)
[1.4 全双工](#1.4 全双工)
[1.5 缓冲区](#1.5 缓冲区)
[1.6 UDP 数据报](#1.6 UDP 数据报)
[2. TCP](#2. TCP)
[2.1 有连接](#2.1 有连接)
[2.2 可靠传输](#2.2 可靠传输)
[2.2.1 确认应答](#2.2.1 确认应答)
[2.2.2 超时重传](#2.2.2 超时重传)
[2.2.3 连接管理](#2.2.3 连接管理)
[2.2.4 滑动窗口](#2.2.4 滑动窗口)
[2.2.5 流量控制](#2.2.5 流量控制)
[2.2.6 拥塞控制](#2.2.6 拥塞控制)
[2.2.7 延迟应答](#2.2.7 延迟应答)
[2.2.8 捎带应答](#2.2.8 捎带应答)
[2.2.9 面向字节流](#2.2.9 面向字节流)
[2.2.10 TCP 异常情况的处理](#2.2.10 TCP 异常情况的处理)
[2.3 TCP 十个核心特性简要总结](#2.3 TCP 十个核心特性简要总结)
[2.4 TCP / UDP 对比](#2.4 TCP / UDP 对比)
[1. IP 协议](#1. IP 协议)
[1.1 基本概念](#1.1 基本概念)
[1.2 协议头格式](#1.2 协议头格式)
[1.3 IPv4 (IP 地址不够用了怎么办 )](#1.3 IPv4 (IP 地址不够用了怎么办 ))
[1.4 IPv6 (从根本上解决了 IP 地址不够用的问题)](#1.4 IPv6 (从根本上解决了 IP 地址不够用的问题))
[2. 地址管理](#2. 地址管理)
[2.1 网段划分](#2.1 网段划分)
[2.2 特殊的 IP 地址](#2.2 特殊的 IP 地址)
[3. 路由选择](#3. 路由选择)
[1. 认识以太网](#1. 认识以太网)
[1.1 以太网帧格式](#1.1 以太网帧格式)
[1.2 认识 MAC 地址](#1.2 认识 MAC 地址)
[1.3 认识 MTU](#1.3 认识 MTU)
[1.4 DNS (域名解析系统)](#1.4 DNS (域名解析系统))
应用层
1. 应用层的作用
// 满⾜我们⽇常需求的⽹络程序,都是在应⽤层
2. 自定义应用层协议
// 我们可以根据自己具体的需求, 来设定应用层协议
// 自定义的协议格式, 是可以任意的
3. 应用层的 "通用协议格式"
// 为了避免一些过于 "个性化" 的设计, 圈内大佬就搞出来了 "通用的协议格式"
// 参考一些 "通用的协议格式", 可以对我们的协议设计产生重要的指导作用
// "通用协议格式" 有很多种体现形式
3.1 xml
// 以成对的标签, 来表示 "键值对" 信息, 同时标签支持嵌套, 就可以构成一些更复杂的树形结构数据
// <request> (开始标签)
// <useId>...</useId> (内容: 键值对 结构)
// </request> (结束标签)
// 优点: 可以非常清晰的把结构化数据表示出来
// 缺点: 表示数据需要引入大量的标签, 看起来繁琐, 还会占用不少的网络带宽
3.2 json
// 当前最流行的一种数据组织形式
// 本质上也是键值对, 但看起来比 xml 要干净不少
// {
// useId: ... ,
// }
// json 中, 使用 { } 表示 键值对, 使用 [ ] 表示数组, 数组里的每个元素, 可以是字符串, 还可以是其他的 { } 或 [ ]
// json 对于换行并不敏感, 所有内容放在同一行也是合法的
// 一般网络传输的时候, 会对 json 进行压缩 (去掉不必要的换行和空格), 同时将所以数据放到一行中去, 整体占用的带宽就会降低 (影响到可读性)
// 我们格式化 json 时也有很多 json 格式化工具, 可以直接使用
// 优势: 相比于 xml , 表示的数据简洁很多, 可读性非常好, 方便我们观察中间结果, 方便调试问题
// 劣势: 终究需要花费一定的带宽来传输 key 的名字的
3.3 protobuffer (pd)
// 谷歌提出的一套, 二进制的数据序列化方式
// 使用二进制的方式, 约定某几个字节, 表示哪个属性
// 优点: 节省带宽, 最大化效率
// 最大程度的节省空间 (不必传输 key, 是根据位置和长度, 区分每个属性的)
// 缺点: 二进制数据, 无法肉眼直接观察, 不方便调试
// 使用起来比较复杂, 需要专门编写一个 proto 文件, 描述数据的格式 (需要先学习它的语法规则), 再进一步通过人家提供的工具, 把 proto 文件转换成一些代码, 再嵌入到程序中使用
// 相当于是 牺牲了开发效率, 换来了运行效率
传输层
1. UDP
// 基本特点: 无连接 不可靠传输 面向数据报 全双工
// UDP 传输过程类似于寄信
1.1 无连接
// 指导对象端的 IP 和端口号就可以直接进行传输, 不需要建立连接
1.2 不可靠传输
// 没有任何安全机制, 发送端发送数据报之后, 如果因为网络故障该段无法发给对方, UDP 协议层也不会给应用层返回任何的错误信息
1.3 面向数据报
// 应用层交给 UDP 多长的报文, UDP 照原样发送, 既不会拆分,也不会合并
1.4 全双工
// 既可以发送数据, 也可以接收数据, 这两个操作也可以同时进行 (既能读也能写)
1.5 缓冲区
// UDP 只有接收缓冲区, 没有发送缓冲区
1.6 UDP 数据报
// UDP 数据报由: UDP 报头 + 载荷
// UDP 报头由: 源端口 (2 字节) + 目的端口 (2 字节) + UDP 报文长度 (2 字节) + 校验和 (2 字节)
2. TCP
// 基本特点: 有连接 可靠传输 面向字节流 全双工
// TCP 的报头是 "变长" 的, 最大长度是 60 字节
2.1 有连接
// 在进行数据传输之前, 需要先进行连接
2.2 可靠传输
// 靠内核实现的可靠传输 (校验和)
// 可靠传输实现的最重要 (核心) 的机制: 确认应答
// 我们可以通过编号的方式来确保数据传输的可靠性 (对每个字节进行编号)
// TCP 报头 中的 ACK 若为 0, 表示 这是一个普通报文, 此时只有 32 位序号是有效的, ACK 若为 1, 表示这是一个应答报文, 这个报文的 序号 和 确认序号, 都是有效的 (确认报文的序号和正常报文的序号之间没有关联关系, 各自论各自的)
2.2.1 确认应答

// 每一个 ACK 都带有对应的确认序列号, 意思是告诉发送者, 我已经收到了哪些数据, 下一次你从哪里发
2.2.2 超时重传


// 主机 A 发送数据给 B 之后, 可能因为网络拥堵等一些原因导致数据无法到达 B
// 如果主机 A 在一个特定时间间隔内没有收到 B 发来的确认应答, 就会进行重发; 但是, 主机 A 未收到 B 发来的确认应答, 可能是因为 ACK 丢失了, 因此主机 B 会收到很多重复数据, 那么 TCP 协议 需要能够识别出哪些包是重复的包, 并且把重复的丢弃掉
// 这时我们可以利用前面提到的序列号, 达到去重的效果
// 接收方, 也会在接收缓冲区中, 对收到的数据进行排序, 也能处理后发先至的问题
2.2.3 连接管理
// 正常情况下, TCP 要建立连接: 需要三次握手, 断开连接: 需要四次挥手
2.2.3.1 建立连接: 三次握手
// 三次握手 (其实是四次交互,但中间两次进行了合并), 这样可以减少封装和分用的次数, 得到更高的传输效率

// 三次握手, 第一次 SYN 一定是客户端发起的 (客户端是主动的一方)
// 三次握手的意义: 1) 是一种保证可靠性的机制 (投石问路); 2) 是协商必要的参数, 使客户端和服务器使用相同的参数进行消息传输
// TCP 三次握手, 就是要验证网络通信是否通畅, 以及验证每个主机的发送能力和接收能力是否正常
// 三次握手, 还能起到 "消息协商" 的效果,使通信双⽅共同确认⼀些通信中的必备参数数值
2.2.3.2 断开连接: 四次挥手
// 连接: 通信双方, 各自在内存中保存了对端的相关信息

// 经过上述四个步骤之后, 连接就彻底不再使用了, 双方就可以把各自保存对端消息的空间释放了
// 四次挥手中间这两步不一定能合并, 有时候可以, 有时候不行
// FIN 的触发, 是应用程序代码来控制的, 调用 socket.close() 或者进程结束, 就会触发 FIN, 相比之下, ACK 则是由内核控制的, 收到 FIN 就会立马返回 ACK , 所以关键还是要看代码是怎么编写的
// 为了确保最后一次客户端向服务器发送的 ACK 服务器确认收到了, 而没有出现中途丢包导致超时重传, 客户端需要在发送完成 ACK 之后, 等待一定时间后再释放连接, 但是也不用一直等下去, 只要超过应该等待的时间 [MSL] (网络上任意两点之间传输数据所需最大时间的两倍), 服务器还没有重传 FIN, 大概率就是服务器成功接收到了 ACK, 这时候就可以自行释放连接了, 若发生了网络传输问题导致服务器没有成接收到 ACK, 则客户端重传一次即可
// TCP 是如何实现可靠传输的: 1) 确认应答 (起决定性作用) 2) 超时重传 3) 连接管理 (三次握手, 四次挥手)
2.2.4 滑动窗口
// 用来提高传输效率 (更准确的说, 是让 TCP 在可靠传输的前提下, 效率不要太拉胯)
// 使用滑动窗口, 不能使 TCP 变得比 UDP 快, 但是可以缩小差距
// 使用滑动窗口, 一次性发出一组数据, 发这一组数据的过程中, 不需要等待 ACK 就直接往前发, 此时就相当于使用 "一份等待时间" 等四个 ACK
// 把一次发多少数据, 不用等 ACK 这样的大小, 称为窗口
2.2.5 流量控制
// TCP⽀持根据接收端的处理能⼒,来决定发送端的发送速度.这个机制就叫做流量控制 (Flow Control)
// 接收端将自己可以接受的缓冲区大小放入 TCP 首部中的 "窗口大小" 字段, 通过 ACK 端通知发送端
// 窗口大小字段越大, 说明网络的吞吐量越高
// 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端
// 发送端接受到这个窗口之后, 就会减慢自己的发送速度
// 如果接收端缓冲区满了, 就会将窗口置为 0, 这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
2.2.6 拥塞控制
// 总的传输效率, 是一个 木桶效应, 取决于最短板
// TCP 中, 拥塞控制是这样展开的: 1) 慢启动: 刚开始进行通信的时候, 会使用一个非常小的窗口, 先试试水; 2) 指数增长: 在传输通常的过程中, 拥塞窗口就会指数增长 (*2) (指数增长的速度是极快的, 必须加以限制,否则会出现非常大的值) 3) 线性增长: 指数增长到拥塞窗口达到一个阈值之后, 就会从指数增长转换成 线性增长(+n) (线性增长也会使得发送速度越来越快, 当快到一定程度, 接近网络传输的极限, 就可能会出现丢包了) 4) 拥塞窗口回归一个小窗口: 当窗口大小增长过程中, 如果传输出现丢包, 就认为事当前网络出现拥堵了, 此时就会把窗口大小调整成最初的小窗口, 继续回到之前 指数增长 + 线性增长 的过程, 另外此处也会根据当前出现丢包的窗口大小, 调整阈值 (指数增长 -> 线性增长), 新的阈值是: 出现丢包的窗口大小/2
// 拥塞窗口, 就是在这个过程中不断发生变化, 不断重新调整的过程
// 这样的调整就可以非常好的适应多变的网络环境
// 当然, 也是有不少的性能损失
// 实际发送方的窗口 = min (拥塞窗口, 流量控制窗口)
// 拥塞控制和流量控制, 共同限制了滑动窗口机制, 可以使滑动窗口能够在可靠性的前提下, 提高传输速率
2.2.7 延迟应答
// 是一种提高传输效率的机制, 还是围绕滑动窗口琢磨的, 在条件允许的基础山, 尽可能的提高窗口大小
// 在返回 ACK 的时候, 拖延一点时间, 利用这个拖延时间, 就可以给应用程序藤出来更多的消费数据的时间, 这样接收缓冲区的剩余空间就更大了
2.2.8 捎带应答
// 在延迟应答基础上, 引入的一个进一步提高效率的方式
// 延迟应答: 让 ACK 传输的时机更慢
// 捎带应答: 基于延迟应答, 让数据进行合并
// 四次挥手有时也可以是三次的主要原因就是延迟应答和捎带应答的功劳, 可以将两个数据包合成一个, 效率会有明显的提升
2.2.9 面向字节流
// 粘包问题: 这里 "粘" 的是 "应用层数据包"
// 在 TCP 协议头中, 没有如同 UDP 一样的 "报文长度" 这样的字段, 但是有一个序号字段
// 站在传输层的角度, TCP 是一个一个报文过来的, 按照序号排好序放在缓冲区中
// 站在应用层的角度, 看到的只是一串连续的字节数据
// 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分是一个完整的应用层数据包
// 解决粘包问题的核心就是明确来那个包之间的边界
// 粘包问题不仅仅是 TCP 才有的, 只要是面向字节流的机制 (文件) 也有同样的问题, 解决方案也都是一样的, 要么使用分隔符, 要么在数据前固定几个字节来表示数据长度
2.2.10 TCP 异常情况的处理
// 网络本身存在一些变数, 导致 TCP 连续不能继续正常工作了
// 进程崩溃: 进程没了, 相当于调用了 socket.close(), 崩溃的这一方就会发出 FIN, 进一步的触发 四次挥手, 此时连接就正常释放了, 这种情况下 TCP 的处理和进程正常退出没啥区别
// 主机关机 (正常步骤关机): 关机前会先去强制终止所有的进程, 和之前的崩溃处理是一样的
// 主机掉电 (拔电源, 不留反应空间): 此时就没有任何可以操作的空间了, 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset . 即使没有写入操作, TCP 自己也内置了一个保活定时器 (心跳包), 会定期询问对方是否还在, 如果对方不在, 也会把连接释放
// 网线断开: 相当于主机掉电的升级版本
2.3 TCP 十个核心特性简要总结
// 确认应答 -> 可靠性
// 超时重传 -> 可靠性
// 连接管理 -> 可靠性
// 滑动窗口 -> 效率
// 流量控制 -> 可靠性
// 拥塞控制 -> 可靠性
// 延时应答 -> 效率
// 捎带应答 -> 效率
// 面向字节流 => 粘包问题 -> 编程注意事项
// 异常情况处理 => 心跳包 -> 异常情况
2.4 TCP / UDP 对比
// TCP 优势在于可靠性: 适用于绝大部分场景, 应用于文件传输, 重要状态更新等场景
// UDP 优势在于效率: 适合于机房内部的主机之间通信, 用于对高速传输和实时性要求较高的通信领域
2.4.1 用 UDP 实现可靠传输
// 本质还是要到 TCP 上去, 使用 TCP 中的一些保证可靠性的方法实现可靠性传输
网络层
1. IP 协议
1.1 基本概念
// 主机: 配有 IP 地址, 但是不进行路由控制的设备
// 路由器: 既配有 IP 地址, 又能进行路由控制
// 节点: 主机和路由器的统称
1.2 协议头格式
// 4 位版本号, 用来表示 IP 协议的版本, 现有的 IP 协议只有 IPv4 和 IPv6
// 4位首部长度, 设定和 TCP 一样, IP 报头可变长的, IP 报头又是带有选项的, 此处单位也是 4 字节
// 8 位服务类型 (其中真正只有 4 位才有效果), 类似于 模式/形态 切换
// 16 位总长度: IP 报头 + 载荷 的长度
// 16 位标识, 3 位标志位, 13位片偏移, 描述了整个 IP 数据报拆包组包的过程
// 8 位生存时间 (TTL), 单位是 次, 初始情况下, TTL 会有个数值, 每次经过一个路由器转发, TTL 就会 -1, 减到 0 了就会被丢弃; 正常来说 TTL 足以支持数据报到达网络的任一位置, 如果确实出现 0 了, 基本可以认为目标 IP 不可达
// 16 位首部校验和: 校验数据是否正确的机制, 只需要校验首部即可
// 32 位源地址, 32 位目的地址: IP 协议中最为重要的部分, 说明了 数据报 从哪来, 到哪去
1.3 IPv4 (IP 地址不够用了怎么办 )
// IPv4, 是 4 个字节, 32 位表示 IP 地址
// 动态分配 IP (DHCP) : 这杯需要上网了才分配 IP, 不需要就先不分配, 这种方法只能缓解, 但不能根治
// NAT 机制 (网络地址转换): 把 IP 地址分为内网 IP (不同的局域网内的设备, 内网 IP 可以重复, 同一个局域网内的设备, 内网 IP 不能重复) 和外网 IP (不能重复), 它也只是提高了 IP 地址的 "利用率", 并没有从根本上解决 IP 不够用的问题, 缺点也很明显: 1) 效率不高; 2) 非常繁琐; 3) 不方便直接访问局域网内的设备; 但是它有一个很大的优点: NAT 是一个 "纯软件实现" 的方案
1.4 IPv6 (从根本上解决了 IP 地址不够用的问题)
// IPv6, 是 16 个字节, 128 位, 表示 IP 地址
2. 地址管理
2.1 网段划分
// IP 地址分为两个部分, 网络号和主机号 (192.168.22.25)
// 网络号: 标识网段, 保证相互连接的两个网段具有不同的标识
// 主机号: 标识主机, 同一网段内, 主机之间具有相同的网络号, 但是必须有不同的主机号
// 子网掩码是现代用来划分网络号的方案 (255.255.255.0)
// 过去是用 A B C D E 将 IP地址划分为五类
2.2 特殊的 IP 地址
// 将 IP 地址中的主机地址全部设为 0, 就成为了网络号, 代表这个局域网
// 将 IP 地址中的主机地址全部设为 1 , 就成为了广播地址, 用于给同一个链路中相互连接的所有主机发送数据包; 此处广播, 在传输层只能使用 UDP, 而不能使用 TCP (TCP 无法针对广播地址进行三次握手, 建立连接的操作)
// 127.* 的 IP 地址用于本机环回 (loop back) 测试, 通常是 127.0.0.1
// 本机环回主要用于本机到本机的网络通信 (系统内部为了性能, 不会走网络的方式传输), 对于开发网络通信的程序 (即网络编程) 而言, 常见的开发方式都是本机到本机的网络通信
3. 路由选择
// 路由选择的过程, 是一跳一跳 (Hop by Hop) "问路" 的过程, 这里给出的路径, 不一定是最优解, 只能说是 "较优解"
数据链路层
// 代表协议: 以太网
1. 认识以太网
1.1 以太网帧格式
// 以太网帧格式: 目的地址 (6个字节) + 源地址 (6个字节) + 类型 (2个字节) + 数据 + CRC (4个字节)
// 源地址和目的地址是指: 物理地址 (MAC 地址), 长度是 48 位, 是在网卡出厂时固化的
// 帧协议类型字段有 3 种值, 分别对应 IP, ARP, RARP
// 一个以太网数据帧, body 部分, 最大长度 1500, 受限于硬件
// 帧末尾是 CRC 校验码
1.2 认识 MAC 地址
// MAC 地址用来失败数据链路层中相连的节点
// 长度为 48 位, 即 6个字节, 一般用 16 进制数字加上冒号的形式来表示 (例如:08:00:27:03:fb:19)
// 在网卡出厂时就确定了, 不能修改. MAC 地址通常是唯一的
1.3 认识 MTU
// MTU 相当于发快递时对包裹尺寸的限制, 这个限制是不同的数据链路对应的物理层, 产生的限制
// 以太网帧中的数据长度规定最小 46 字节, 最大 1500 字节, ARP 数据包的长度不够 46 字节, 要在后面补填充位
// 最大值 1500 称为以太网的最大传输单元 (MTU), 不同的网络类型有不同的 MTU;
// 如果一个数据包从以太网路由到拨号链路上, 数据包长度大于拨号链路的 MTU 了, 则需要对数据包进行分片 (fragmentation)
// 不同的数据链路层标准的 MTU 是不同的
1.4 DNS (域名解析系统)
// 上网要访问服务器, 知道服务器的 IP 地址, IP 地址是一串数字, 虽然这个数字使用点分十进制已经清晰不少了, 但是任然不方便人们记忆和传播, 我们可以通过单词 (域名) 来代替 IP 地址
// 域名往往是分级的, 例如: www.sougou.com 中 com (一级域名); sougou (二级域名); www (三级域名)
// DNS 服务器如何能够承载高并发量 (开源, 节流): 1) 在每个电脑上, 在进行域名解析的时候, 都会有缓存, 访问很多次, 只有第一次真的访问 DNS, 后面几次不一定访问; 2) 全世界会搭建出很多的 "DNS 镜像服务器", 从最初的 DNS 服务器这里同步数据