TCP/UDP协议

引言:TCP/UDP协议(未完成)

TCP/UDP知识点总结

此处仅供复习,省去废话,只有干货!(注意,没有标单位的数,单位将均为字节)

  1. 运输层的主要任务:多路复用、多路分解
  2. 多路复用:从不同的数据块接收数据,加首部传递到网络层;多路分解:将报文段数据交付到正确的套接字工作。
  3. UDP协议的特点:无连接、尽最大努力交付(不保证发送成功)、面向报文(加首部直接传递给下一层)、支持一对一、一对多、多对一、多对多首部开销小(8字节)
  4. UDP首部格式:源端口(2)、目的端口(2)、长度(2,包括头部)、检验和(2)
  5. 伪首部:TCP/UDP检验时都会用到,源IP地址(4)、目的IP地址(4)、0(1)、17(1,UDP为17,TCP为6)、UDP长度(2,这里的长度与首部中一样,都不会包含伪首部的长度)
  6. 检验过程:使用伪首部、首部、数据部分、(不为偶数还要填充0)进行求和取反码放入检验和字段,接收端接收时同样对所有数据进行求和取反码,全为1则无错,否则有错,丢弃数据包。
  7. 检验了什么?不仅检查了源端口、目的端口、数据部分、还检查了源IP地址、目的IP地址
  8. TCP协议的特点:面向连接只支持一对一提供可靠交付(无差错、无重复、无丢失、按序到达)、全双工通信面向字节流(数据报转化为无结构的字节流进行分组发送)
  9. 有关可以保证传输可靠的所有要点:停止等待协议、连续ARQ协议、滑动窗口协议
  10. 停止等待协议:发送一个分组后停止,等待对方确认后再发送下一个分组。
  11. 停止等待协议要点:超时重传、设置等待时间(略大于往返时间RTT)、设置编号(可以明确哪一个丢失)、要保存一个发送的副本(保证可以重发)、自动重传请求ARQ
  12. 自动重传请求ARQ:指重传的请求是自动进行的,接收端无需请求发送端重传某个出错的分组(通过超时重传、确认迟到、确认丢失机制实现)
  13. 确认丢失:指确认信息在返回时丢失了,此时发送方会超时重传,接收端会丢弃重复的包,并再一次返回确认信息
  14. 确认迟到:指确认信息在返回时迟到了,此时发送方会超时重传,接收端会丢弃重复的包,并再一次返回确认信息
  15. 信道利用率:公式为Td / (Td + RTT + Ta),其中Td为发送分组的时间,RTT是一次往返的事件、Ta是接收分组的时间
  16. 停止等待协议缺点:信道利用率低,效率低,信道绝大部分时间空闲
  17. TCP报文段格式:源端口(2)、目的端口(2)、序号(4)、确认号(4)、数据偏移(4 bit)、保留(6 bit)、URG(1bit,其后五个也均为1 bit)、ACK、PSH、RST、SYN、FIN、窗口(2)、检验和(2)、紧急指针(2)、选项(可变)、填充(补齐为4的倍数)
  18. TCP首部长度:固定长度20,可变长度最长为40,即整个首部最短为20,最长为60
  19. 序号(4):本报文段要发送的第一个字节的序号(TCP按字节编号,此字段标志本次发送的第一个字节的编号)
  20. 确认号(4):期望收到的下一个字节的编号(确认号为N:代表N-1个数据已经全部收到)
  21. 数据偏移(4 bit):记录数据起始处 - 首部起始处(其实就是首部长度,单位为 4字节,代表TCP首部最长为 (2^4 -1) * 4 = 60字节,即选项部分最长为40字节)
  22. 保留(6 bit):今后使用,全为0
  23. URG:紧急,当此字段为1,TCP会将此报文段优先传送(例如 Control + c 命令),与紧急指针协同使用
  24. 紧急指针(2):标明紧急的数据在本报文段的末尾位置(如果URG为1,就会把紧急的数据放在报文段最前面;注意:紧急数据后面的数据,属于普通数据,还需要排队)
  25. ACK:确认收到,连接建立后的所有报文段都必须把ACK置为1
  26. PSH:推送,置为1后,发送方立即创建报文段发送,接收方收到后立即交付进程(不必等到缓存存满再交付)
  27. RST:复位,表明要断开连接,重新建立
  28. SYN:同步,建立连接时使用,SYN=1 ACK=0表明这是一个连接请求,SYN=1 ACK=1表明同意连接请求
  29. FIN:终止,表明发送方数据发送完毕,要求终止连接
  30. 窗口(2):接收窗口的大小(作为接收方让发送方调节发送窗口的依据)
  31. 检验和(2):同UDP,要加上伪首部计算
  32. 选项(可变长度):可以设置MSS、窗口扩大选项、时间戳选项、选择确认选项
  33. MSS最大报文段长度:仅包括数据部分,默认为536字节(所以默认能接受的报文段长度是536 + 20 = 556字节)
  34. 窗口扩大选项(3):可以设置更大的接受窗口(首部的窗口只有2字节)
  35. 时间戳(10):两个作用:用此计算RTT时间;用此区分序号是否重复使用(防止序号绕回
  36. 防止序号绕回PAWS:序号字段只有4字节,当传输量大时,容易让旧的数据序号与新的数据序号重复,分不清新旧数据,可以在选项中加时间戳选项进行区分
  37. 改用流水线发送分组后,保证可靠性的协议:连续ARQ协议、滑动窗口协议(两个协议是一同使用的)
  38. 发送窗口:位于窗口内的包均可以发送,无需等待确认接收
  39. 连续ARQ协议:规定发送方每收到一个确认请求,就将窗口向前推进一个位置;接收方进行累积确认
  40. 累积确认回退N:累积确认:对按序到达的最后一个分组发送确认;意味着如果中间丢失了一个分组,需要重新发送其后的所有包,这就是回退N(Go-Back-N)
  41. 滑动窗口协议:TCP通信双方均维护一个发送缓存与接收缓存(实现全双工)
  42. 发送缓存:发送窗口是发送缓存的一部分,存放两部分数据:1 准备发送的数据;2 已经发出但是尚未接收到确认的数据
  43. 接收缓存:接收窗口是接收缓存的一部分,存放两部分数据:1 按序到达,但尚未读取的数据;2 未按序到达的数据
  44. 选择确认SACK:(替代累积确认的方案)当收到不连续的数据时,收下数据,并通知发送方,发送方就无需传递重复的数据
  45. TCP流量控制:TCP通过滑动窗口来进行流量控制,通过不断的变更窗口的大小,来使信道利用率变高。(目的是为了让接收端来得及接收)
  46. TCP拥塞控制:通过慢开始、拥塞避免、快重传、快恢复四种算法实现(目的是为了防止网络发生阻塞)
  47. 流量控制与拥塞控制的区别:1 流量控制是为了让接收端来得及接收;拥塞控制是为了防止网络发生阻塞;2 流量控制是一个端到端的控制(发送接收双方的控制);拥塞控制是一个全局的控制;
  48. 如何判断网络出现了拥塞:只要出现了超时,就发生了拥塞
  49. 拥塞窗口cwnd:发送方维持,只要无拥塞就持续增大,反之亦然。
  50. 慢开始:初始设cwnd为1(但其实是设为几倍的SMSS的大小),每经过一个RTT就翻倍的增加cwnd
  51. 拥塞避免:每经过一个RTT就给cwnd线性增长(比如加1);注意:拥塞避免只是难以出现拥塞,并不是避免了拥塞
  52. 慢开始门限ssthresh:当cwnd < ssthresh,就用慢开始;当cwnd>ssthresh就用拥塞避免;当cwnd=ssthresh ,二者选其一;如果网络出现了阻塞,会将cwnd置为初始值,重新进行慢开始。
  53. 快重传:发送方只要介绍到三个重复确认,就启动快重传算法。要求接收方不要等到自己发送数据时才捎带进行确认,而是要立即发送确认请求。
  54. 快恢复:快重传启动时执行。调整ssthresh为当前拥塞窗口的一半ssthresh = cwnd / 2
  55. AIMD算法:拥塞避免时,窗口线性增大(AI,加法增大);快恢复时,设置慢开始门限为当前拥塞窗口值的一半(MD,乘法减小);合称AIMD算法
  56. 发送窗口上限值 = Min(rwnd, cwnd)(即接收窗口与拥塞窗口中小的那一个)
  57. 拥塞控制流程总结:设置拥塞窗口初始值;首先慢开始;达到ssthresh,进入拥塞避免;出现拥塞,再次慢开始;如果连续收到3个重复的确认收到请求,开启快重传与快恢复
  58. TCP三次握手:(A代指客户、B代指服务器)
    1. 初始阶段:均处于CLOSED关闭状态
    2. B的TCP进程创建传输控制块TCB准备接收请求,处于LISTEN收听状态
    3. A的TCP进行也创建TCB,并发送SYN=1, seq=x(当SYN=1,ACK=0表示此请求是为建立连接,seq代表序号,初始时序号随机选择);发送完成后,A进入SYN-SENT同步发送状态
    4. B接收到请求后,如果同意连接,发送SYN=1, ACK=1, seq=y, ack=x+1(当SYN=1,ACK=1表示同意连接请求,服务器端也随机选一个初始值,并返回确认收到ack,注意ack是x+1),B进入SYN-RCVD同步收到状态
    5. A收到B的确认后,还要给出确认,发送ACK=1, seq=x+1, ack=y+1,A进入ESTAB-LISHED已建立状态
    6. B接收到A的确认后,也进入ESTAB-LISHED已建立状态
  59. SYN报文段(SYN为1的报文段)不能携带数据,但会消耗掉一个序号
  60. 第三次握手阶段,可以携带数据(第三次握手不是SYN报文段)
  61. 第三次握手的作用:防止已失效的连接请求突然又传到了服务器端
    1. 情景:假如当前为两次握手即建立连接,第一次连接请求在网络中延迟,导致客户端又重传了一次连接请求,第二次连接请求成功连接,然后客户端服务器进行通信且完成后断开,断开后,第一次连接请求又发送到了服务器端,但其实客户端已经没有要发送的数据了,导致服务器端资源白白浪费
  62. SYN泛洪攻击:攻击端利用了三次握手协议,伪造IP地址发送连接请求给服务器,服务器确认请求永远发送不到目的地(因为IP是伪造的),以此耗尽服务器资源。(预防措施:设SYN cookie,实现了接收到一个SYN时完全不需要分配空间)
  63. TCP四次挥手:
    1. 初始阶段:均处于ESTAB-LISHED已建立状态
    2. A主动关闭连接,发送FIN=1, seq=u的请求,进入FIN-WAIT-1终止等待1阶段
    3. B收到请求,发送ACK=1, seq=v, ack=u+1,进入CLOSE-WAIT关闭等待状态(此时处于半关闭状态,即A已经没有发送的数据了,但B可能还有);
    4. A收到确认请求后,进入FIN-WAIT-2终止等待2阶段
    5. B传输完数据后,也想关闭连接,发送FIN=1, ACK=1, seq=w, ack=u+1,进入LAST-ACK最后确认状态
    6. A收到请求后,发送ACK=1, seq=u+1, ack=w+1,进入TIME-WAIT时间等待状态
    7. B收到请求后,进入CLOSED关闭状态
    8. A在等待2MSL(MSL最长报文段寿命)后,也进入CLOSED关闭状态
  64. 为什么要设置TIME-WAIT状态?两个原因
    1. 保证客户端发送的最后一个ACK能到达B
    2. 也是为了防止迟到的连接请求出现
  65. 如果双方之一出现故障,无法进行四次挥手怎么办?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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 发送端:
# 有三个字段:分别表示源端口号、目的端口号、长度
0110 0110 0110 0000
0101 0101 0101 0101
1000 1111 0000 1100
--------------------求和,高位溢出不管
0100 1010 1100 0010
--------------------取反码
1011 0101 0011 1101 # 此值作为检验和字段
--------------------
--------------------
# 接收端
# 将四个字段进行求和
0110 0110 0110 0000
0101 0101 0101 0101
1000 1111 0000 1100
1011 0101 0011 1101 # 检验和
------------------- # 求和
0000 0000 0000 0000

最后的结果全为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]

浏览器访问一个IP地址

  • 然后B开始发送GET请求,S收到后回应[ACK]
  • 然后S开始发送响应,B收到后回应[ACK]

服务端的三次握手

一个TCP服务器,需要创建ServerSocket:

  • 先给bind一个端口

  • 然后对这个端口进行监听

图源来自CSDN

如图所示:

  • 服务器在收到SYN请求后,将客户端的文件描述符FD存放到半连接队列
  • 服务器再收到ACK请求后,将半连接队列中的文件描述符FD放到全连接队列

之后服务器accept进入阻塞,然后等待客户端的数据。

TCP“粘包”问题

很多人觉得这并不是一个问题,而是一个TCP的特性,关键在于你怎么看待

什么是粘包问题?

  1. 以接收端来看:因为TCP是面向流的协议,所以不会保持输入数据的边界,导致接收测很可能一下子收到多个应用层报文,需要应用开发者自己分开,有些人觉得这样不合理,就起名为粘包
  2. 以发送端来看:用户数据被TCP发送时,会根据Nagle算法,将小的数据封装在一个报文段发送出去,导致不同的报文段黏在了一起

如何解决?

  1. 发送方可以关闭Nagle算法(设置TCP_NODELAY就能关闭Nagle算法,但我不太认同这种做法)
  2. 接收方对粘包无法处理,只能交给应用层
  3. 应用层:
    • 格式化数据:每条数据有固定的格式(开始符,结束符),这种方法简单易行(但要确保内容没有开始符与结束符)
    • 发送长度:发送数据规定一个长度,以便于应用层判断

拥塞控制

首先要知道几个结构:

  • 发送窗口:发送窗口的大小是拥塞窗口接收窗口的较小值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状态

TCP的11种状态

关于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状态,会占用服务器的内存、文件描述符、端口号。

可能的原因有几点:

  1. HTTP没有使用长连接:
    • 比如使用的版本是1.0
    • 请求头Connection:close:这种情况下,无论是谁先主动断开的连接,都会算作服务端主动关闭连接
  2. HTTP长连接超时:长连接的超时时间是60s,到时间就会关闭连接
  3. HTTP长连接的请求数量达到了上限:超过最大限制时,就会主动关闭连接

如何排查呢?

  1. 看看是不是都开启了keep-alive,有一个没开启都算服务器主动关闭
  2. 网络是否有问题?
  3. 长连接的请求上限设置的是否合理

问题4:如果服务器出现了大量的CLOSE_WAIT状态,可能的原因是什么?

当服务端出现大量 CLOSE_WAIT 状态的连接的时候,说明服务端的程序没有调用 close 函数关闭连接

(一般都是代码的问题:比如这个link

需要对着步骤进行分析:

  1. 服务端创建socket,bind、listen
  2. 将socket注册到epoll,epoll_ctl设置关心的操作,并切入内核,将红黑树和双向链表copy过去
  3. epoll_wait等待事件
  4. 事件发生,调用accept接收连接:
    • 连接到来时,netty 「忘了」调用 accept 把连接从内核的全连接队列里取走。
    • 这里的「忘」可能是因为逻辑 bug 或者 netty 忙于其他事情没有时间取走
  5. 注册新的连接EPOLLIN、EPOLLERR、EPOLLHUP的事件到epoll:
    • netty取走了连接,三次握手真正完成,但是没有注册新连接的后续事件,导致后面收到了 fin 包以后无动于衷
  6. 继续epoll_wait