TCP/UDP协议
TCP/UDP知识点总结
此处仅供复习,省去废话,只有干货!(注意,没有标单位的数,单位将均为字节)
- 运输层的主要任务:多路复用、多路分解
- 多路复用:从不同的数据块接收数据,加首部传递到网络层;多路分解:将报文段数据交付到正确的套接字工作。
- UDP协议的特点:无连接、尽最大努力交付(不保证发送成功)、面向报文(加首部直接传递给下一层)、支持一对一、一对多、多对一、多对多、首部开销小(8字节)
- UDP首部格式:源端口(2)、目的端口(2)、长度(2,包括头部)、检验和(2)
- 伪首部:TCP/UDP检验时都会用到,源IP地址(4)、目的IP地址(4)、0(1)、17(1,UDP为17,TCP为6)、UDP长度(2,这里的长度与首部中一样,都不会包含伪首部的长度)
- 检验过程:使用伪首部、首部、数据部分、(不为偶数还要填充0)进行求和取反码放入检验和字段,接收端接收时同样对所有数据进行求和取反码,全为1则无错,否则有错,丢弃数据包。
- 检验了什么?不仅检查了源端口、目的端口、数据部分、还检查了源IP地址、目的IP地址
- TCP协议的特点:面向连接、只支持一对一、提供可靠交付(无差错、无重复、无丢失、按序到达)、全双工通信、面向字节流(数据报转化为无结构的字节流进行分组发送)
- 有关可以保证传输可靠的所有要点:停止等待协议、连续ARQ协议、滑动窗口协议
- 停止等待协议:发送一个分组后停止,等待对方确认后再发送下一个分组。
- 停止等待协议要点:超时重传、设置等待时间(略大于往返时间RTT)、设置编号(可以明确哪一个丢失)、要保存一个发送的副本(保证可以重发)、自动重传请求ARQ
- 自动重传请求ARQ:指重传的请求是自动进行的,接收端无需请求发送端重传某个出错的分组(通过超时重传、确认迟到、确认丢失机制实现)
- 确认丢失:指确认信息在返回时丢失了,此时发送方会超时重传,接收端会丢弃重复的包,并再一次返回确认信息
- 确认迟到:指确认信息在返回时迟到了,此时发送方会超时重传,接收端会丢弃重复的包,并再一次返回确认信息
- 信道利用率:公式为
Td / (Td + RTT + Ta)
,其中Td为发送分组的时间,RTT是一次往返的事件、Ta是接收分组的时间 - 停止等待协议缺点:信道利用率低,效率低,信道绝大部分时间空闲
- TCP报文段格式:源端口(2)、目的端口(2)、序号(4)、确认号(4)、数据偏移(4 bit)、保留(6 bit)、URG(1bit,其后五个也均为1 bit)、ACK、PSH、RST、SYN、FIN、窗口(2)、检验和(2)、紧急指针(2)、选项(可变)、填充(补齐为4的倍数)
- TCP首部长度:固定长度20,可变长度最长为40,即整个首部最短为20,最长为60
- 序号(4):本报文段要发送的第一个字节的序号(TCP按字节编号,此字段标志本次发送的第一个字节的编号)
- 确认号(4):期望收到的下一个字节的编号(确认号为N:代表N-1个数据已经全部收到)
- 数据偏移(4 bit):记录数据起始处 - 首部起始处(其实就是首部长度,单位为 4字节,代表TCP首部最长为
(2^4 -1) * 4 = 60
字节,即选项部分最长为40字节) - 保留(6 bit):今后使用,全为0
- URG:紧急,当此字段为1,TCP会将此报文段优先传送(例如
Control + c
命令),与紧急指针协同使用 - 紧急指针(2):标明紧急的数据在本报文段的末尾位置(如果URG为1,就会把紧急的数据放在报文段最前面;注意:紧急数据后面的数据,属于普通数据,还需要排队)
- ACK:确认收到,连接建立后的所有报文段都必须把ACK置为1
- PSH:推送,置为1后,发送方立即创建报文段发送,接收方收到后立即交付进程(不必等到缓存存满再交付)
- RST:复位,表明要断开连接,重新建立
- SYN:同步,建立连接时使用,
SYN=1 ACK=0
表明这是一个连接请求,SYN=1 ACK=1
表明同意连接请求 - FIN:终止,表明发送方数据发送完毕,要求终止连接
- 窗口(2):接收窗口的大小(作为接收方让发送方调节发送窗口的依据)
- 检验和(2):同UDP,要加上伪首部计算
- 选项(可变长度):可以设置MSS、窗口扩大选项、时间戳选项、选择确认选项
- MSS最大报文段长度:仅包括数据部分,默认为536字节(所以默认能接受的报文段长度是536 + 20 = 556字节)
- 窗口扩大选项(3):可以设置更大的接受窗口(首部的窗口只有2字节)
- 时间戳(10):两个作用:用此计算RTT时间;用此区分序号是否重复使用(防止序号绕回)
- 防止序号绕回PAWS:序号字段只有4字节,当传输量大时,容易让旧的数据序号与新的数据序号重复,分不清新旧数据,可以在选项中加时间戳选项进行区分
- 改用流水线发送分组后,保证可靠性的协议:连续ARQ协议、滑动窗口协议(两个协议是一同使用的)
- 发送窗口:位于窗口内的包均可以发送,无需等待确认接收
- 连续ARQ协议:规定发送方每收到一个确认请求,就将窗口向前推进一个位置;接收方进行累积确认
- 累积确认与回退N:累积确认:对按序到达的最后一个分组发送确认;意味着如果中间丢失了一个分组,需要重新发送其后的所有包,这就是回退N(Go-Back-N)
- 滑动窗口协议:TCP通信双方均维护一个发送缓存与接收缓存(实现全双工)
- 发送缓存:发送窗口是发送缓存的一部分,存放两部分数据:1 准备发送的数据;2 已经发出但是尚未接收到确认的数据
- 接收缓存:接收窗口是接收缓存的一部分,存放两部分数据:1 按序到达,但尚未读取的数据;2 未按序到达的数据
- 选择确认SACK:(替代累积确认的方案)当收到不连续的数据时,收下数据,并通知发送方,发送方就无需传递重复的数据
- TCP流量控制:TCP通过滑动窗口来进行流量控制,通过不断的变更窗口的大小,来使信道利用率变高。(目的是为了让接收端来得及接收)
- TCP拥塞控制:通过慢开始、拥塞避免、快重传、快恢复四种算法实现(目的是为了防止网络发生阻塞)
- 流量控制与拥塞控制的区别:1 流量控制是为了让接收端来得及接收;拥塞控制是为了防止网络发生阻塞;2 流量控制是一个端到端的控制(发送接收双方的控制);拥塞控制是一个全局的控制;
- 如何判断网络出现了拥塞:只要出现了超时,就发生了拥塞
- 拥塞窗口cwnd:发送方维持,只要无拥塞就持续增大,反之亦然。
- 慢开始:初始设cwnd为1(但其实是设为几倍的SMSS的大小),每经过一个RTT就翻倍的增加cwnd
- 拥塞避免:每经过一个RTT就给cwnd线性增长(比如加1);注意:拥塞避免只是难以出现拥塞,并不是避免了拥塞
- 慢开始门限ssthresh:当cwnd < ssthresh,就用慢开始;当cwnd>ssthresh就用拥塞避免;当cwnd=ssthresh ,二者选其一;如果网络出现了阻塞,会将cwnd置为初始值,重新进行慢开始。
- 快重传:发送方只要介绍到三个重复确认,就启动快重传算法。要求接收方不要等到自己发送数据时才捎带进行确认,而是要立即发送确认请求。
- 快恢复:快重传启动时执行。调整ssthresh为当前拥塞窗口的一半
ssthresh = cwnd / 2
- AIMD算法:拥塞避免时,窗口线性增大(AI,加法增大);快恢复时,设置慢开始门限为当前拥塞窗口值的一半(MD,乘法减小);合称AIMD算法
发送窗口上限值 = Min(rwnd, cwnd)
(即接收窗口与拥塞窗口中小的那一个)- 拥塞控制流程总结:设置拥塞窗口初始值;首先慢开始;达到ssthresh,进入拥塞避免;出现拥塞,再次慢开始;如果连续收到3个重复的确认收到请求,开启快重传与快恢复
- TCP三次握手:(A代指客户、B代指服务器)
- 初始阶段:均处于CLOSED关闭状态
- B的TCP进程创建传输控制块TCB准备接收请求,处于LISTEN收听状态
- A的TCP进行也创建TCB,并发送
SYN=1, seq=x
(当SYN=1,ACK=0
表示此请求是为建立连接,seq代表序号,初始时序号随机选择);发送完成后,A进入SYN-SENT同步发送状态 - B接收到请求后,如果同意连接,发送
SYN=1, ACK=1, seq=y, ack=x+1
(当SYN=1,ACK=1
表示同意连接请求,服务器端也随机选一个初始值,并返回确认收到ack,注意ack是x+1
),B进入SYN-RCVD同步收到状态 - A收到B的确认后,还要给出确认,发送
ACK=1, seq=x+1, ack=y+1
,A进入ESTAB-LISHED已建立状态 - B接收到A的确认后,也进入ESTAB-LISHED已建立状态
- SYN报文段(SYN为1的报文段)不能携带数据,但会消耗掉一个序号
- 第三次握手阶段,可以携带数据(第三次握手不是SYN报文段)
- 第三次握手的作用:防止已失效的连接请求突然又传到了服务器端
- 情景:假如当前为两次握手即建立连接,第一次连接请求在网络中延迟,导致客户端又重传了一次连接请求,第二次连接请求成功连接,然后客户端服务器进行通信且完成后断开,断开后,第一次连接请求又发送到了服务器端,但其实客户端已经没有要发送的数据了,导致服务器端资源白白浪费
- SYN泛洪攻击:攻击端利用了三次握手协议,伪造IP地址发送连接请求给服务器,服务器确认请求永远发送不到目的地(因为IP是伪造的),以此耗尽服务器资源。(预防措施:设SYN cookie,实现了接收到一个SYN时完全不需要分配空间)
- TCP四次挥手:
- 初始阶段:均处于ESTAB-LISHED已建立状态
- A主动关闭连接,发送
FIN=1, seq=u
的请求,进入FIN-WAIT-1终止等待1阶段 - B收到请求,发送
ACK=1, seq=v, ack=u+1
,进入CLOSE-WAIT关闭等待状态(此时处于半关闭状态,即A已经没有发送的数据了,但B可能还有); - A收到确认请求后,进入FIN-WAIT-2终止等待2阶段
- B传输完数据后,也想关闭连接,发送
FIN=1, ACK=1, seq=w, ack=u+1
,进入LAST-ACK最后确认状态 - A收到请求后,发送
ACK=1, seq=u+1, ack=w+1
,进入TIME-WAIT时间等待状态 - B收到请求后,进入CLOSED关闭状态
- A在等待2MSL(MSL最长报文段寿命)后,也进入CLOSED关闭状态
- 为什么要设置
TIME-WAIT
状态?两个原因- 保证客户端发送的最后一个ACK能到达B
- 也是为了防止迟到的连接请求出现
- 如果双方之一出现故障,无法进行四次挥手怎么办?TCP设有保活计时器,每收到一次客户端请求,就重置,在2小时内没收到客户端的数据后,会给客户端每75秒发送一条探测报文,如果连续10个探测报文都未收到响应,就会关闭连接。
运输层(未完成)
运输层处于应用层与网络层之间,运输层最起码要完成的任务,就是要提供一种复用/分解的服务
在这一层的数据我们使用报文段来进行说明(在RFC中报文段、数据报混合使用,我们这里只使用报文段来阐述)
多路分解与多路复用
多路分解:
将运输层报文段中的数据交付到正确的套接字工作作为多路分解
多路复用:
从不同的套接字中收集数据块,并为每一个数据块封装上首部信息,从而生成报文段,然后将报文段传递到网络层
套接字Socket
socket可以简单理解为 IP + 端口号
(这与JavaAPI内的Socket有些区别)
一台计算机上有很多进程,OS收到数据后,依据端口号来区分哪些信息是传给哪些进程的
端口号:占用2字节,16bit ,大小在0~65535之间
其中0-1023是周知端口号,自己的APP应避免周知端口号
- UDP套接字:一个二元组标识,分别是目的IP地址与目的端口号
- TCP套接字:一个四元组标识,除了目的的IP与端口号,还包括源端口号与源IP地址
UDP
UDP:一种简单的无连接的协议,IP层传来的数据,只进行了简单的封装(只有8个字节)
特点:
- 无连接:发送端与接收端不必事先连接
- 头部简单:只有8个字节
- 传输快
- 不确保可以发送到
报文段结构
UDP封装的头只有四个字段,每个字段占2个字节,一共占8个字节:
- 源端口号
- 目的端口号
- 长度
- 检验和
检验和
为什么UDP还要有差错检验功能?
原因是不能保证从源到目的所有链路都有差错检验功能,也就是说,这些链路中可能有一条链路没有使用差错检测的协议
除此外,报文存储在路由器内存中,也可能有比特差错
检验和的流程
检验和的流程简单来说就是:将所有的字段求二进制和,然后取其反码作为检验和这个字段,接收端接收后,将包括检验和的所有字段进行求和,如果每一位都是0,则代表传输过程中没有出现错误
举例:
1 | # 发送端: |
最后的结果全为0,代表没有出错
如果其中有一个不为0,说明出现了错误
出错的处理
UDP虽然可以检测出数据是否有异常,但是处理异常的过程,非常简单:
- 将数据丢弃
- 给出警告
其他
UDP只能进行不可靠传输吗?
基于UDP是可以实现可靠传输的,可以在应用程序端自身建立可靠性机制
对于不同源端口号或是源IP地址(不同源),但是具有相同目的端口与IP的报文段的处理
UDP会将这些报文段通过同一个socket传输给对应的进程
(而TCP会为每一个单独建一个socket)
TCP
TCP:运输层的另一个协议,面向连接的传输协议
特点:
- 面向连接
- 可靠
- 全双工:连接双方可以互相“说话”
- 点对点:一对一
抓包——三次握手
在浏览器输入一个IP地址,就会先进行TCP三次握手
- B表示浏览器(端口为52085)、S表示服务器(端口为80)
- B中输入
127.0.0.1
进行访问 - 先建立三次握手:
- B给S发建立连接的请求
[SYN]
- S给B回应,允许建立连接
[SYN, ACK]
- B给S回应,收到
[ACK]
- B给S发建立连接的请求
- 然后B开始发送GET请求,S收到后回应
[ACK]
- 然后S开始发送响应,B收到后回应
[ACK]
服务端的三次握手
一个TCP服务器,需要创建ServerSocket:
先给
bind
一个端口然后对这个端口进行监听
如图所示:
- 服务器在收到SYN请求后,将客户端的文件描述符FD存放到半连接队列
- 服务器再收到ACK请求后,将半连接队列中的文件描述符FD放到全连接队列
之后服务器accept
进入阻塞,然后等待客户端的数据。
TCP“粘包”问题
很多人觉得这并不是一个问题,而是一个TCP的特性,关键在于你怎么看待
什么是粘包问题?
- 以接收端来看:因为TCP是面向流的协议,所以不会保持输入数据的边界,导致接收测很可能一下子收到多个应用层报文,需要应用开发者自己分开,有些人觉得这样不合理,就起名为粘包
- 以发送端来看:用户数据被TCP发送时,会根据Nagle算法,将小的数据封装在一个报文段发送出去,导致不同的报文段黏在了一起
如何解决?
- 发送方可以关闭Nagle算法(设置
TCP_NODELAY
就能关闭Nagle算法,但我不太认同这种做法) - 接收方对粘包无法处理,只能交给应用层
- 应用层:
- 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行(但要确保内容没有开始符与结束符)
- 发送长度:发送数据规定一个长度,以便于应用层判断
拥塞控制
首先要知道几个结构:
- 发送窗口:发送窗口的大小是拥塞窗口和接收窗口的较小值
swnd=min(cwnd, rwnd)
- 拥塞窗口:拥塞控制的核心组件
cwnd
- 拥塞窗口门限:在越过这个门限后
ssthresh
会有不同的操作
其次我们要知道几个控制算法:
- 慢开始:指
cwnd
从1开始增长,并且每次翻倍(慢开始指的是起点低,而不是增长速度慢) - 拥塞避免:在超过
ssthresh
后,进入拥塞避免算法,会让cwnd
线性增长
如果出现了拥塞,那么就将ssthresh
的值调整为当前cwnd
的一半:ssthresh = cwnd / 2
,然后重新进入慢开始阶段。
如果发送端接收到了三个重复的ACK请求(证明当前的网络也不太好,但是也没拥塞那么严重),那么会进入快重传+快恢复阶段
快重传:要求接收方立即发送确,
- 即使收到了失序的报文段,也要立即发出对已收到的报文段的重复确认
- 发送确认也不要等缓存区满再发了,加急直接发
快恢复:cwnd的初始值不是1了,而是
ssthresh
注意:
问题1:什么时候会出现ssthresh = cwnd / 2
呢?
- 出现拥塞(即超时)
- 收到3个连续的ACK请求
问题2:如何判断出现了拥塞?
- 丢包
- 滑动窗口变小
- ACK延迟或是重复出现
CLOSING状态
TCP还有一个CLOSING状态:
在「同时关闭」的情况下出现。
这里的同时关闭中的「同时」其实并不是时间意义上的同时,而是指的是在发送 FIN 包还未收到确认之前,收到了对端的 FIN 的情况。
所以TCP总共含有11种状态,CLOSING状态之后会进入TIME_WAIT状态
关于CLOSE_WAIT与TIME_WAIT状态
问题1:CLOSE_WAIT与TIME_WAIT是谁的状态?
TCP是全双工协议,TIME_WAIT是主动关闭者的状态,CLOSE_WAIT是被关闭者的状态,不能简单的认为前者是客户端的,后者是服务器的。
问题2:TIME_WAIT状态的作用?
进入TIME_WAIT状态后,会等待2MSL自动关闭
1、保证确认关闭的ACK请求发送给了对方
2、避免历史数据被下一个相同的四元组连接接收到
问题3:如果服务器出现了大量的TIME_WAIT状态,有什么危害?可能是什么原因?
如果服务器出现了大量的TIME_WAIT状态,会占用服务器的内存、文件描述符、端口号。
可能的原因有几点:
- HTTP没有使用长连接:
- 比如使用的版本是1.0
- 请求头
Connection:close
:这种情况下,无论是谁先主动断开的连接,都会算作服务端主动关闭连接
- HTTP长连接超时:长连接的超时时间是60s,到时间就会关闭连接
- HTTP长连接的请求数量达到了上限:超过最大限制时,就会主动关闭连接
如何排查呢?
- 看看是不是都开启了
keep-alive
,有一个没开启都算服务器主动关闭 - 网络是否有问题?
- 长连接的请求上限设置的是否合理
问题4:如果服务器出现了大量的CLOSE_WAIT状态,可能的原因是什么?
当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接
(一般都是代码的问题:比如这个link)
需要对着步骤进行分析:
- 服务端创建socket,bind、listen
- 将socket注册到epoll,epoll_ctl设置关心的操作,并切入内核,将红黑树和双向链表copy过去
- epoll_wait等待事件
- 事件发生,调用accept接收连接:
- 连接到来时,netty 「忘了」调用 accept 把连接从内核的全连接队列里取走。
- 这里的「忘」可能是因为逻辑 bug 或者 netty 忙于其他事情没有时间取走
- 注册新的连接EPOLLIN、EPOLLERR、EPOLLHUP的事件到epoll:
- netty取走了连接,三次握手真正完成,但是没有注册新连接的后续事件,导致后面收到了 fin 包以后无动于衷
- 继续epoll_wait