概览
1. UDP协议
1.1 特点
- 无连接: 知道对端的IP和端口号就直接进行传输, 不需要建立连接
- 不可靠: 没有确认机制, 没有重传机制; 如果因为网络故障该段无法发到对方, UDP协议层也不会给应用层返回任何错误信息
- 面向数据报: 不能够灵活的控制读写数据的次数和数量,只能一次接收(系统级别的操作:调用系统函数)
- 没有发送缓冲区(发了消息就不管),有接收缓冲区
- 数据最大64k,如果需要传输超过64k的数据,就需要在应用层手动分包,多次发送,并在接收端手动拼装。
1.2 UDP协议端格式
1.3 面向数据报
—— 一次性读取,没有发送缓冲区,应用层交给UDP多长的报文,UDP原样发送,既不会拆分,也不会合并
1.4 UDP的缓冲区
- UDP没有真正意义上的发送缓冲区. 调用sendto会直接交给内核, 由内核将数据传给网络层协议进行后续的传输动作
- UDP具有接收缓冲区. 但是这个接收缓冲区不能保证收到的UDP报的顺序和发送UDP报的顺序一致; 如果缓冲区满了, 再到达的UDP数据就会被丢弃
1.5 基于UDP的应用层协议
- NFS:网络文件系统
- TFTP:简单文件传输协议
- DHCP:动态主机配置协议
- BOOTP:启动协议(用于无盘设备启动)
- DNS:域名解析协议
2. TCP协议
—— 传输控制协议,非100%安全,保证可承受范围的安全,尽可能的提高网络传输数据的效率
2.1 TCP协议段格式
格式说明:
-
源/目的端口号:表示数据是从哪个进程来,到哪个进程去
-
32位序号:序号用来标识从TCP发端向TCP收端发送的数据字节流,它表示在这个报文段中的的第一个数据字节。如果将字节流看作在两个应用程序间的单向流动,则TCP用序号对每个字节进行计数。
-
32位确认号:确认序号包含发送确认的一端所期望收到的下一个序号。因此,确认序号应当是上次已成功收到数据字节序号加1。只有ACK标志为1时确认序号字段才有效。
-
4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节),所以TCP头部最大长度是15*4=60
-
6位标志位:
- URG:紧急指针是否有效
- ACK:确认号是否有效
- PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走
- RST:对方要求重新建立连接,我们把携带RST标识的称为复位报文段
- SYN:请求建立连接,我们把携带SYN标识的称为同步报文段
- FIN:通知对方,本端要关闭了,我们称携带FIN标识的为结束报文段
-
16位窗口大小:TCP的流量控制由连接的每一端通过声明的窗口大小来提供。窗口大小为字节数,起始于确认序号字段指明的值,这个值是接收端正期望接收的字节。
-
16位校验和:发送端填充,CRC校验,接收端校验不通过,则认为数据有问题。此处的检验和不光包含TCP首部,也包含TCP数据部分
-
16位紧急指针:标识哪部分数据是紧急数据
-
40字节头部选项:长度可变,最小0字节,最长达40字节。当没有使用选项部分时,TCP的首部为20字节。
2.2 确认应答机制(ACK):安全
-
保证安全:我发送的消息,对方必须确认并回复
-
保证多条数据确认消息的安全(序号+确认序号):
- 发送时:携带数据序号
- 确认回复时:携带确认序号
2.3 超时重传机制:安全
2.3.1 数据报丢失
- 主机A发送数据给B之后, 可能因为网络拥堵等原因, 数据无法到达主机B
- 如果主机A在一个特定时间间隔内没有收到B发来的确认应答, 就会进行重发
2.3.2 ACK确认应答数据报丢失
- 主机A未收到主机B发来的确认应答
因此主机B会收到很多重复数据。TCP协议需要能够识别出那些包是重复的包, 并且把重复的丢弃掉。这时候我们可以利用序列号, 就可以很容易做到去重的效果。
2.3.3 确定超时时间:
- 根据当前网络环境的情况(决定发送数据的速度),得到单次报文发送的最大生存时间(MSL),超时时间即为2MSL
- TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间
2.3.4 扩展了解:一直收不到ACK,超时时间的设置
- Linux中(BSD Unix和Windows也是如此), 超时以500ms为一个单位进行控制, 每次判定超时重发的超时时间都是500ms的整数倍。
- 如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传。
- 如果仍然得不到应答, 等待 4*500ms 进行重传,依次类推, 以指数形式递增。
- 累计到一定的重传次数, TCP认为网络或者对端主机出现异常, 强制关闭连接。
2.4 连接管理机制:安全、三次握手建立连接,四次挥手断开连接
连接管理机制图示:
由图可知:
-
三次握手的流程:
- 主机A发送SYN到主机B,要求建立主机A到主机B的连接,此时主机A的状态为SYN_SENT
- 主机B回复主机A ACK+SYN,要求建立主机B到主机A的连接,此时主机B的状态为SYN_RCVD
- 主机A回复第2步SYN的ACK,此时主机A的状态为 ESTABLISHED,建立主机A到主机B的连接。主机B接收到ACK数据报,建立主机B到主机A的连接,此时主机B的状态为 ESTABLISHED。
-
四次挥手的流程:
- 主机A发送FIN到主机B,请求关闭主机A到主机B的连接
- 主机B回复ACK,此时主机B的状态为CLOSE_WAIT
- 主机B发送FIN到主机A,请求关闭主机B到主机A的连接
- 主机A回复ACK(第三步主机B发送的FIN),此时主机A状态为TIME_WAIT,主机B接收到主机A发送的ACK数据报,此时主机B状态为CLOSED,主机A经过2MSL(超时等待时间即报文最大生存时间)之后,此时状态为CLOSED。
对于三次握手中的问题:
- Q:三次握手中,SYN为什么有两个?
- A: 双方会保存连接状态(有方向的)。
- Q:三次握手中,为什么主机B回复ACK+SYN?
- A:本质上是发送一个ACK应答,一个SYN请求,方向相同的两个数据报,可以合并。
- Q:三次握手中,第三步ACK确认应答哪个主机?
- A:应答的第2步中主机B发送的SYN
对于四次挥手中的问题:
- Q:四次挥手中,第2,3步为什么不能合并?
- A:第2步是TCP协议在系统内核实现时,自动响应的ACK,第3步是应用程序手动调用close来关闭连接。程序在关闭连接前,可能需要执行释放资源等前置操作,所以不能合并(TCP协议实现时,没有这样设计)
- Q:四次挥手中,第3步主机A为什么不能直接设置为CLOSED状态
- A:第4个数据报可能丢包:即主机B到达超时时间没有收到主机A的ACK数据报后(第4个数据报),会重发第3个数据报(FIN),会要求主机A再次回复ACK(第4个数据报),如果此时主机A已经处于CLOSED,会无法回复ACK数据报给主机B
- Q:服务器出现大量的CLOSE_WAIT状态,是什么原因,怎么解决?
- A:是因为服务端没有正确的关闭连接(程序没有调用close,或没有正确调用),导致四次挥手没有正确完成,解决办法是只需要加上对应的close即可解决问题。
2.5 滑动窗口:效率
2.5.1 前置知识
- 对于每一个发送的数据段,都需要给一个ACK确认应答,收到ACK后再发送下一个数据段,这样做性能较差,尤其是数据往返时间较长的时候
- 使用滑动窗口可以解决效率问题,一次发送多条数据,就可以大大提高性能(其实是将多个段的等待时间重叠在一起了)
2.5.2 滑动窗口图示
说明:
- 窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
- 发送前四个段的时候, 不需要等待任何ACK, 直接发送
- 收到第一个ACK后, 滑动窗口向后移动, 继续发送第五个段的数据,依次类推
- 操作系统内核为了维护这个滑动窗口, 需要开辟 发送缓冲区 来记录当前还有哪些数据没有应答; 只有确认应答过的数据, 才能从缓冲区删掉。
- 窗口越大, 则网络的吞吐率就越高
2.5.3 丢包重传
如果出现丢包,那么需要重传,如何重传,这里需要分情况讨论。
2.5.3.1 数据报抵达,ACK丢包
丢包图示:
窗口滑动图示:
这种情况下,部分ACK丢了不要紧,因为可以通过后续的ACK进行确认。
2.5.3.2 数据报丢包
由图可知:
- 当某一段报文丢失后,发送端会一直收到下一个是1001的ACK
- 如果发送端主机连续三次收到了同样一个’1001’ 的ACK,就会将对应数据1001-2000重新发送
- 此时接收端收到1001之后,再次返回的ACK就是7001了(因为2001-7000)接收端其实之前就已经收到了,被放到了接收端操作系统内核的接受缓冲区中。
这种情况也被称为:高速重发控制(也叫快重传)
2.5.4 滑动窗口小结
-
窗口大小:无需等待确认应答而可以继续发送数据的最大值
-
确定窗口大小:由拥塞窗口和流量控制窗口决定
滑动窗口大小 = min(拥塞窗口大小,流量控制窗口大小) -
如何滑动:依赖ACK的确认序号(ACK确认序号前的数据报都已经接收到了),在该ACK确认序号前,当次并行接收到多少个数据报,就可以滑动多少
-
为什么要有发送端、接收端缓冲区:
- 发送缓冲区:记录已发送的数据 —— 收到对应ACK,才可以清理该数据
- 接收缓冲区:记录已接收的数据 —— 如果发送数据丢包,才知道让对方重发
2.6 流量控制机制:安全
2.6.1 概念
接收端处理数据的速度是有限的,如果发送端发的太快, 导致接收端的缓冲区被打满,这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应。
因此TCP支持根据接收端的处理能力, 来决定发送端的发送速度,这个机制就叫做流量控制(Flow Control)。
2.6.2 控制流程
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过ACK端通知发送端
- 窗口大小字段越大, 说明网络的吞吐量越高
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度
- 如果接收端缓冲区满了, 就会将窗口置为0,这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端
接收端使用流量控制窗口,保证接收端数据安全。
如何保证===> 告诉发送端后,影响发送端窗口大小,在TCP首部中,16位窗口字段存放的就是窗口大小信息,实际上,TCP首部40字节选项中还包含了一个窗口扩大因子M,实际窗口大小是窗口字段的值左移M位。
2.7 拥塞控制机制:安全
如果在发送端网络状态不明的情况下,贸然发送大量数据报,会造成网络拥堵,TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。
拥塞窗口增长图示:
像上面这样的拥塞窗口的增长速度,是指数级别的。
- 为了不增长的那么快,因此不能使用拥塞窗口单纯的加倍。
- 此处引入一个叫做慢启动的阈值。
- 当拥塞窗口超过这个阈值的时候,不再按照指数方式增长,而是按照线性方式增长。
线性增长图示:
- 当TCP开始启动的时候, 慢启动阈值等于窗口最大值
- 在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1
少量的丢包,仅仅是触发超时重传,大量的丢包,我们就认为是网络拥塞,当TCP通信开始后,网络吞吐量会逐渐上升,随着网络发生拥堵,吞吐量会立刻下降。
拥塞控制,归根结底就是TCP协议想尽可能快的把数据传输给对方,但是又要避免给网络造成太大压力的折中方案。
2.8 延迟应答机制:效率
接收端收到数据,马上响应ACK,流量控制窗口大小(接收端接收缓冲区空余空间)就会比较小,即返回的窗口可能比较小
- 假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K
- 但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了
- 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来
- 如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M
窗口越大,网络吞吐量就越大,传输效率也就越高,也并不是所有的包都可以延迟应答
- 数量限制:每隔N个包就应答一次
- 时间限制:超过最大延迟时间就应答一次
具体的数量和超时时间,根据操作系统的不同也有差异,一般N取2,超时时间取200ms
延迟应答图示:
2.9 捎带应答机制:效率
接收端在响应ACK的时候,和主动发送的数据,可以合并返回
2.10 TCP小结
2.10.1 TCP特性
有连接的可靠协议:
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答机制
- 超时重发机制
- 连接管理机制
- 流量控制机制
- 拥塞控制机制
提高性能:
- 滑动窗口/快速重传机制
- 延迟应答机制
- 捎带应答机制
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT定时器等)
- 面向字节流:同时存在发送缓冲区、接收缓冲区,并且大小没有限制
2.10.2 基于TCP应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
2.10.3 粘包问题
问题:
- 基于传输层TCP来实现应用层协议时,因为TCP是基于字节流,读取时需要考虑格式,包括读取多长,如果格式读取不对,或长度没有设置好,会出现粘包问题
如何避免:明确两个包之间的边界
- 对于定长的包, 保证每次都按固定大小读取即可
- 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置
- 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)
2.10.4 TCP异常情况
- 进程终止:进程终止会释放文件描述符, 仍然可以发送FIN,和正常关闭没有什么区别
- 机器重启:和进程终止的情况相同
- 机器掉电/网线断开:接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在,如果对方不在, 也会把连接释放
3. IP协议
—— 在复杂的网络环境中确定一个合适的路径
3.1 基本概念&协议头格式
基本概念:
- 主机:配有IP地址,但是不进行路由控制的设备
- 路由器:既配有IP地址,又能进行路由控制
- 节点:主机和路由器的统称
协议头格式图示:
协议头说明:
- 4位版本号(version):指定IP协议的版本, 对于IPv4来说, 就是4
- 4位头部长度(header length):IP头部的长度是多少个32bit, 也就是 length * 4 的字节数。4bit表示最大的数字是15, 因此IP头部最大长度是60字节
- 8位服务类型(Type Of Service):3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0)。4位 TOS分别表示: 最小延时, 最大吞吐量, 最高可靠性, 最小成本. 这四者相互冲突, 只能选择一个。对于ssh/telnet这样的应用程序, 最小延时比较重要; 对于ftp这样的程序, 最大吞吐量比较重要
- 16位总长度(total length):IP数据报整体占多少个字节
- 16位标识(id):唯一的标识主机发送的报文. 如果IP报文在数据链路层被分片了, 那么每一个片里面的这个id都是相同的
- 3位标志字段:第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到)。第二位置为1表示禁止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文. 第三位表示"更多分片", 如果分片了的话, 最后一个分片置为1, 其他是0,类似于一个结束标记
- 13位分片偏移(framegament offset):是分片相对于原始IP报文开始处的偏移. 其实就是在表示当前分片在原报文中处在哪个位置。实际偏移的字节数是这个值 * 8 得到的。因此, 除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续了)
- 8位生存时间(Time To Live, TTL):数据报到达目的地的最大报文跳数。一般是64. 每次经过一个路由, TTL -= 1, 一直减到0还没到达, 那么就丢弃了。这个字段主要是用来防止出现路由循环
- 8位协议:表示上层协议的类型
- 16位头部校验和:使用CRC进行校验, 来鉴别头部是否损坏
- 32位源地址和32位目标地址:表示发送端和接收端
- 选项字段:不定长, 最多40字节
其中16位标识(id)、3位标志字段、13位分片偏移,这三个字段的目的,是基于数据链路层MTU拆分为多个数据报后,接收端分用到网络层前,需要先还原在分用,因为封装后的数据报超长,需要拆分为多个数据报发送
- 发送端:类似发送整个家具,先拆分为多个组件在发送
- 接收端:类似客户接收家具,需要把每个组件拆开还原再安装
3.2 网段划分
3.2.1 概念
IP地址分为两个部分,网络号和主机号
- 网络号:保证相互连接的两个网段具有不同的标识
- 主机号:同一网段内,主机之间具有相同的网络号,但是必须有不同的主机号
通过合理的设置主机号和网络号,就可以保证在相互连接的网络中,每台主机的IP地址都不相同,但是手动管理子网内的IP,是一个相当麻烦的事情
- 有一种技术叫做DHCP,能够自动的给子网内新增主机节点分配IP地址,避免了手动管理IP的不便
- 一般的路由器都带有DHCP功能,因此路由器也可以看做一个DHCP服务器
3.2.2 划分网络号和主机号
过去曾经提出一种划分网络号和主机号的方案,把所有IP地址分为5类,如下图所示:
说明:
- A类:0.0.0.0 到 127.255.255.255
- B类:128.0.0.0 到 191.255.255.255
- C类:192.0.0.0 到 223.255.255.255
- D类:224.0.0.0 到 239.255.255.255
- E类:240.0.0.0 到 247.255.255.255
但是这种划分方案有局限性,大多数组织都申请B类网络地址,导致B类地址很快就分配完了,而A类却浪费了大量地址,针对这种情况,提出了新的划分方案,称为CIDR:
- 引入一个额外的子网掩码(subnet mask)来区分网络号和主机号
- 子网掩码也是一个32位的正整数,通常用一串 “0” 来结尾
- 将IP地址和子网掩码进行 “按位与” 操作, 得到的结果就是网络号
- 网络号和主机号的划分与这个IP地址是A类、B类还是C类无关
例如:
- 140.252.20.68/24,表示IP地址为140.252.20.68, 子网掩码的高24位是1,也就是255.255.255.0,子网划分图示:
- 140.252.20.68/28,表示IIP地址为140.252.20.68,子网掩码的高28位为1,也就是255.255.255.240,子网划分图示:
通过下表我们能明确子网掩码和斜杠表示法之间的关系:
其中/8-/15只能用于A类网络,/16-/23可用于A类和B类网络,而/24-/30可用于A类、B类和C类网络。
3.2.3 特殊的IP地址
- 将IP地址中的主机地址全部设为0, 就成为了网络号, 代表这个局域网
- 将IP地址中的主机地址全部设为1, 就成为了广播地址, 用于给同一个链路中相互连接的所有主机发送数据包
- 127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1,即本机IP地址
3.2.4 IP地址的数量限制
CIDR在一定程度上缓解了IP地址不够用的问题(提高了利用率, 减少了浪费, 但是IP地址的绝对上限并没有增加), 仍然不是很够用,这时候有三种方式来解决:
- 动态分配IP地址:只给接入网络的设备分配IP地址. 因此同一个MAC地址的设备, 每次接入互联网中, 得到的IP地址不一定是相同的
- NAT技术
- IPv6:IPv6并不是IPv4的简单升级版. 这是互不相干的两个协议, 彼此并不兼容; IPv6用16字节128位来表示一个IP地址; 但是目前IPv6还没有普及
3.2.5 私有IP地址和公网IP地址
如果一个组织内部组建局域网,IP地址只用于局域网内的通信,而不直接连到Internet上,理论上 使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网的私有IP地址
- 10.*,前8位是网络号,共16,777,216个地址
- 172.16.到172.31.,前12位是网络号,共1,048,576个地址
- 192.168.*,前16位是网络号,共65,536个地址 包含在这个范围中的, 都成为私有IP, 其余的则称为全局IP(或公网IP)
对于子网和外网:
- 一个路由器可以配置两个IP地址, 一个是WAN口IP, 一个是LAN口IP(子网IP)。
- 路由器LAN口连接的主机, 都从属于当前这个路由器的子网中
- 不同的路由器, 子网IP其实都是一样的(通常都是192.168.1.1)。子网内的主机IP地址不能重复。但是子网之间的IP地址就可以重复了
- 每一个家用路由器, 其实又作为运营商路由器的子网中的一个节点. 这样的运营商路由器可能会有很多级,最外层的运营商路由器, WAN口IP就是一个公网IP了
- 子网内的主机需要和外网进行通信时, 路由器将IP首部中的IP地址进行替换(替换成WAN口IP), 这样逐级替换, 最终数据包中的IP地址成为一个公网IP。这种技术称为NAT(网络地址转换)
- 如果希望我们自己实现的服务器程序, 能够在公网上被访问到, 就需要把程序部署在一台具有外网IP的服务器上。
3.3 路由
—— 在复杂的网络结构中, 找出一条通往终点的路线
路由的过程, 就是这样一跳一跳(Hop by Hop) “问路” 的过程。
所谓 “一跳” 就是数据链路层中的一个区间。具体在以太网中指从源MAC地址到目的MAC地址之间的帧传输区间
IP数据包的传输过程也和问路一样:
- 当IP数据包, 到达路由器时, 路由器会先查看目的IP
- 路由器决定这个数据包是能直接发送给目标主机, 还是需要发送给下一个路由器
- 依次反复, 一直到达目标IP地址
那么如何判定当前这个数据包该发送到哪里呢? 这个就依靠每个节点内部维护一个路由表:
- 路由表可以使用route命令查看
例:windows上可以使用 route PRINT -4 - 如果目的IP命中了路由表, 就直接转发即可
- 路由表中的最后一行,主要由下一跳地址和发送接口两部分组成,当目的地址与路由表中其它行都不匹配时,就按缺省路由条目规定的接口发送到下一跳地址。