前言

这个文章耗时巨久,查阅和学习资料,还鸽🐦了几天,不过在线代考试的前晚,也就是27日今晚搞出来了!!!

本来还想谈谈UDP协议,HTTPS协议,加密原理和证书,SSL/TLS握手过程哪些…..

不过想了想,没时间弄了,所以再一次🐦了,等啥时候有空或者想去弄的时候再(水)博客吧~

OSI

根据 开放式系统互联模型(英语:Open System Interconnection Model,缩写:OSI;简称为OSI模型
可知 传输控制协定(TCP)属于传输层(Transport Layer)網路中的OSI模型- IT閱讀

我们再来看看

TCP 的特性

  • TCP 提供一种面向连接的、可靠的字节流服务
  • 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP
  • TCP 使用校验和,确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。

传输层(Transport Layer)有什么作用?

传输层的协议为应用进程提供端到端的通信服务。它提供面向连接的数据流支持、可靠性流量控制多路复用等服务。

什么是端到端(end-to-end)呢?

形象来说,要将数据从A传送到E,中间可能经过A→B→C→D→E,对于传输层来说它并不知道B,C,D的存在,它只认为我的报文数据是从a直接到e的,这就叫做端到端。

端到端是网络连接也是逻辑链路,这条路可能经过了很复杂的物理路线,但两端主机不管,只认为是有两端的连接,而且一旦通信完成,这个连接就释放了,物理线路可能又被别的应用用来建立连接了。TCP就是用来建立这种端到端连接的一个具体协议,

举个例子,平时我们的 客户端 到 服务端 就是端到端.

端与端之前虽然有IP地址可以进行连接,但是2端都有各自的应用进程(即传输层对应的传输用户),而且都可能进行TCP连接.

端口号

我们知道服务端可能可以同时提供很多个服务,如数据库服务、FTP服务、Web服务等,那么除了IP地址外还需要什么参数才能保证应用进程能访问到该服务器需要的服务并且内容不会错发给应用进程呢?

那就是端口号了,TCP端口号是为了标志特定的应用实体(应用进程)
通常,传输层会对应多个应用进程,即一个主机上有多个进程并行运行,如在多任务或多用户多任务系统中。而TCP提供的“端口号”就是来区分这些进程的。如TCP中HTTPS协议那么端口号就是443,HTTP的端口号为80,FTP的端口号为21.

套接字有4个纬度 ip+port : ip+port (拓展:port可分配的有65535个)

举个例子, 比如我电脑上有A,B两个进程,都需要向一个服务器进行连接,假设进行的是HTTPS协议传输.那么我的电脑会为 A,B这2个进程分配不同的端口号,通过 套接字socket(IP地址:端口号) 与服务器进行通信.此时服务器的套接字socket为服务器的IP:443(因为是HTTPS协议)

套接字socket是握手前的核心条件之一


接下来我们开始聊聊

三次握手(Three-way Handshake)

三次握手是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。在 socket 编程中,客户端执行 connect() 时。将触发三次握手。

  • 第一次握手(SYN=1, seq=x):

    客户端发送一个 TCP 的 SYN 标志位置1(设置1为开启标识)的包(意思是想要与服务端进行数据同步),指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。

    发送完毕后,客户端进入 SYN_SEND 状态。

为什么需要添加这个序列号(Sequence Number)呢?

为什么要添加这个序号呢?因为应用程序可能需要连续发送多个序号给服务器,这样服务器就有依据可以判断哪些是累赘信息,而且序列号(Sequence Number)是随机生成的,作为初始值来进行后续判断依据,这样更加保证了通道的唯一性

  • 第二次握手(SYN=1, ACK=1, seq=y, ACKnum=x+1):

    服务器发回确认包(ACK)应答。即 SYN 标志位和 ACK 标志位均为1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加1,即X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。

  • 第三次握手(ACK=1,ACKnum=y+1)

    客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1

    发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

三次握手的过程的示意图如下:

three-way-handshake

三次握手.gif

有人会问为什么需要握手?为了在不可靠的网络信道建立起一种可靠连接,必须要有握手确认机制.

为什么是三次?

为了防止失效的连接请求报文段突然又传送到主机B,因而产生错误。所以在此基础上的双向确认最少需要3次
在谢希仁版《计算机网络》中有这样的例子:
“已失效的连接请求报文段”的产生在这样一种情况下:
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。
本来这是一个早已失效的报文段,但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。
于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。
由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据,但server却以为新的运输连接已经建立,并一直等待client发来数据。
这样,server的很多资源就白白浪费掉了。
采用“三次握手”的办法可以防止上述现象发生。
例如刚才那种情况,client不会向server的确认发出确认,server由于收不到确认,就知道client并没有要求建立连接。”


数据传输状态

经过三次握手后,客户端和服务端都进入了数据传输状态

在数据传输中,一包数据有可能会被拆成多包发送,如何处理丢包问题?

由于数据包到达的先后顺序可能不同,如何处理乱序问题?

针对这些问题,TCP协议在内核管理的内存中,为每个连接建立缓冲区.

注:在Linux网络收发过程中包含多个队列和缓冲区,这里不展开讲.只是列举一下.

  • 网卡收发网络包时,通过 DMA 方式交互的环形缓冲区;
  • 网卡中断处理程序为网络帧分配的,内核数据结构 sk_buff 缓冲区;
  • 应用程序通过套接字接口,与网络协议栈交互时的套接字缓冲区。

从建立链接后的第一个字节的序列号为0,后面每个字节的序列号就会增加1

发送数据时,从发送数据缓冲区取一部分数据组成发送报文,在其TCP协议头中会附带序列号和长度

接收端在收到数据后,需要回复确认报文,确认报文中的ACK等于接收序列号加长度

也就是下一包数据需要发送起始序列号

这样一问一答的发送方式,能够使发送端确认发送的数据,已经被对方收到,发送端也可以一次发送连续多包的数据.

接收端只需回复一次ACK就可以了,这样发送端可以把待发送的数据分割成一系列的碎片发送到对端

对端根据序列号和长度,在接收后重构出来完整的数据

假设其中丢失了某些数据包,在接收端可以要求发送端重传,比如丢失了100-199,这100个字节

接收端向发送端发送ACK=100的报文,发送端收到后重传这一包数据,接受端进行补齐

由于TCP是全双工协议,通信双方可以同时通信.所以以上过程不区分客户端和服务端,均可采用上述机制.

这里来张图,帮助理解:image-20211227192132394


四次挥手(Four-way handshake)

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。处于连接状态的客户端和服务端在TCP全双工的加持下,客户端或服务器均可主动发起挥手动作,在 socket 编程中,任何一方执行 close() 操作即可产生挥手操作。

  • 第一次挥手(FIN=1,seq=x)

    假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为1的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。

    发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(ACK=1,ACKnum=x+1)

    服务端此时还可以发送未发送的数据 ,而客户端还可以接受数据,待服务端发送完数据之后,服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。

    发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手(FIN=1,seq=y)

    服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。

    发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个ACK。

  • 第四次挥手(ACK=1,ACKnum=y+1)

    客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT状态,等待可能出现的要求重传的 ACK 包。

    服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

    客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

四次挥手的示意图如下:

four-way-handshake

过程大致如图所示image-20211227194125061

四次挥手.gif

为什么客户端需要等待超时时间呢?

这是为了保证对方已收到ACK包,因为假设客户端发送完最后一包ACK包后就释放了连接

一旦ACK包在网络中丢失,服务端将一直停留在最后确认状态

如果客户端在发送完最后一包ACK包后等待一段时间,这时服务端因为没有收到ACK包,会重发FIN包

客户端会响应这个FIN包,重发ACK包并刷新超时时间

这个机制和三次握手一样,也是为了保证在不可靠的网络链路中进行可靠的连接断开确认

拓展:

TCP数据段中有6个控制位,他们的作用如下:

控制位 作用
ACK 置1时表示确认号合法,为0的时候表示数据段不包含确认信息,确认号被忽略。
PSH 置1时请求的数据段在接收方得到后就可直接送到应用程序,而不必等到缓冲区满时才传送
RST 置1时重建连接。如果接收到RST位时候,通常发生了某些错误
SYN 置1时用来发起一个连接
FIN 置1时表示发端完成发送任务。用来释放连接,表明发送方已经没有数据发送了
URG 紧急指针,告诉接收TCP模块紧要指针域指着紧要数据

其中:
ACK为确认标志位。如果ACK为1,表示数据包中的确认号有效。
RST标志位用来复位一条连接。当RST=1时,表示出现严重错误,必须释放连接,然后再重新建立。
SYN标志位用来建立连接,如果SYN=1而ACK=0,表明它是一个连接请求;如果SYN=1且ACK=1,则表示同意建立一个连接。

注:

TCP报文里有SYN,ACK和FIN等标识 SYN (全称:Synchronization),中文译为 同步,ACK (全称Acknowledgment),中文译为 确认

TCP有6个控制位,各个步骤的控制位是唯一性的,可以根据控制位来区分进行到那个步骤.

无论是SYN包还是其他包,发送都是通过内核的传输控制层完成的,都是双方的内核在通信.


SYN攻击

  • 什么是 SYN 攻击(SYN Flood)?

    在三次握手过程中,服务器发送 SYN-ACK 之后,收到客户端的 ACK 之前的 TCP 连接称为半连接(half-open connect)。此时服务器处于 SYN_RCVD 状态。当收到 ACK 后,服务器才能转入 ESTABLISHED 状态.

    SYN 攻击指的是,攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

    SYN 攻击是一种典型的 DoS/DDoS 攻击。

  • 如何检测 SYN 攻击?

    检测 SYN 攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

  • 如何防御 SYN 攻击?

    SYN攻击不能完全被阻止,除非将TCP协议重新设计。我们所做的是尽可能的减轻SYN攻击的危害,常见的防御 SYN 攻击的方法有如下几种:

    • 缩短超时(SYN Timeout)时间
    • 增加最大半连接数
    • 过滤网关防护
    • SYN cookies技术

TCP KeepAlive

TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。

TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。

TCP-Keepalive-HOWTO 有对 TCP KeepAlive 特性的详细介绍,有兴趣的同学可以参考。这里主要说一下,TCP KeepAlive 的局限。首先 TCP KeepAlive 监测的方式是发送一个 probe 包,会给网络带来额外的流量,另外 TCP KeepAlive 只能在内核层级监测连接的存活与否,而连接的存活不一定代表服务的可用。例如当一个服务器 CPU 进程服务器占用达到 100%,已经卡死不能响应请求了,此时 TCP KeepAlive 依然会认为连接是存活的。因此 TCP KeepAlive 对于应用层程序的价值是相对较小的。需要做连接保活的应用层程序,例如 QQ,往往会在应用层实现自己的心跳功能。

参考资料

简单演示一下用内核程序(非应用层)创建socket通信:

image-20211227172417959