认识 TCP 协议(上)

创作不易,喜欢的话请点赞收藏转载,您的支持是我更新的最大动力!!!

TCP 协议基础

TCP 协议定义

TCP 是互联网中最常用的传输协议之一,是一种在计算机网络中常用的可靠、面向连接的传输协议。它提供了基于字节流的、全双工的数据传输,并通过确认应答、重传机制来保证可靠传输。它被广泛用于各种应用,包括网页浏览、文件传输、电子邮件、远程登录等。 TCP 提供了可靠性和顺序传输的特性,适用于需要确保数据完整性和精确顺序的应用场景。

通过定义可知,TCP 协议最主要的三个特性,分别是:面向连接、可靠传输和流式传输。其中,面向连接决定了使用 TCP 协议通信之前需要建立连接,建立的连接是一对一而不是一对多的。可靠传输是 TCP 协议通过确认应答来保证数据按序传输,并且会自动重传丢失的数据包。流式传输是 TCP 协议将数据转换为字节流传输,没有固定的边界关系。发送方将应用层数据分割成合适长度的数据段发送,接收方按序接收并重新组装。

TCP 协议首部

和 HTTP 协议一样,TCP 协议也有自己的首部的格式,首部默认长度是 20 个字节,主要格式如下图所示:

  • 源端口和目的端口:用于标识不同的进程和服务,源端口表示数据发送方,目的端口表示数据接收方。端口号占 16 位,从 065535,其中 01023 用于系统服务。
  • 序列号:发送方传输的数据的顺序号,接收方根据这个顺序号确认数据在序列中的位置,占 32 位,用于确保数据的可靠传输和重组。
  • 确认号:接收方对接收到的数据进行确认,发送方根据这个号来确定数据是否被接收,占 32 位,用于保证数据的可靠传输和流量控制。
  • 状态位:用于指定数据的不同含义和执行特定的动作,占 6 位,不同的位置代表不同的含义,如下所示
    • URG:表示紧急指针字段的值是否有效,当为 1 时,数据是紧急数据,需要被优先处理;
    • ACK:表示确认号的字段是否有效,当为 1 时,表示确认号字段有效;
    • PSH:表示接收方应该立即将数据推送给应用层,而不是等待缓冲区填满再推送;
    • RST:表示重置连接,当 RST 位被设置为 1 时,表示连接出错或被强制中断,需要终止连接
    • SYN:表示建立连接请求,当为 1 时,表示发送方请求建立连接,并制定初始化序号;
    • FIN:表示发送方已完成数据发送,请求关闭连接。
  • 窗口大小:用于指定接收方能接收数据的缓冲区大小,占 16 位,可以用于控制发送速率,实现流量控制。

TCP 协议通过源端口和目的端口,可以确认发送方和接收方的进程。而 TCP 数据包下传到网络层后,会加上发送方和接收方的 IP 地址,可以确认发送方和接收方的主机。通过源 IP,源端口,目的 IP,目的端口,可以在网络中的任意两个服务和进程之间建立唯一的连接。

建立 TCP 连接

TCP 协议为了保证连接的可靠性,设计了一套连接标准,即三次握手。

三次握手

一开始,客户端和服务端都会处于 CLOSE 状态。随后服务端会进入 LISTEN 状态,如下图所示:

当客户端发起请求时,会先随机始化序列号 client_isn,并填入 TCP 首部的序列号字段中,同时把状态位设置为 SYN 态。随后客户端会将该 SYN 报文发送给服务端,表示建立连接请求,此后客户端进入了 SYN_SENT 状态。

服务端收到客户端发送的 SYN 报文后,也随机初始化自己的序列号 server_isn,将其填写到 TCP 首部的序列号字段中,同时把状态位的 SYN 位和 ACK 位都置为 1。服务端还会将收到的 client_isn 加 1,并填写到 TCP 首部的确认序号字段中。随后服务端会将该报文发送给客户端,表示建立连接,且收到了客户端前面发送过来的 SYN 请求报文,此后服务端进入了 SYN_RCVD。

客户端收到了服务端的 SYN+ACK 报文后,会向服务器发送一个应答报文,并且将收到的服务端序列号 server_isn 加 1 后填写到 TCP 首部的确认号上,将状态位设置为 ACK 态,然后将该报文发送给服务端,随后客户端进入了 ESTABLISHED 状态。服务端收到客户端的 ACK 确认报文后,也进入了 ESTABLISHED 状态。双方到此完成了连接的建立,此后可以开始进行数据的传输。

通过三次握手,保证了双方都能知道对方已经准备好建立连接,并且确保双方都具备发送和接收数据的能力。三次握手的目的是确认双方之间的初始序列号、同步双方的初始化序列号、协商双方的TCP窗口大小等信息,以建立可靠的通信连接。

三次握手的必要性

可能,有人会说 TCP 建立连接需要三次握手,而不是两次或者四次?下面,我们就来聊一聊这个原因。

第一,避免历史连接。如下图所示:

当 TCP 建立连接时,客户端先发送一个建立连接的 SYN 请求报文 ,序列号为 90,称之为旧 SYN 报文,如果网络环境不好导致长时间没有收到服务端的 SYN+ACK 报文,客户端会再发送新的 SYN 请求报文,序列号为 100,称之为新 SYN 报文。此时客户端就向服务端发送了两个 SYN 报文。如果旧 SYN 报文比新 SYN 报文先到服务端,服务端会对旧 SYN 报文进行确认,确认号为 91,并发送 SYN+ACK 报文给客户端。客户端通过上下文比较,发现自己期望的是收到确认号为 101 的 SYN+ACK 报文,客户端于是会发送一个 RST 报文中止连接,断开对旧 SYN 报文的连接,避免资源浪费。当新的 SYN 报文到达服务端后,客户端收到了确认号为 101 的 SYN+ACK 报文,双方就开始建立连接。

如果客户端和服务端在两次握手后就直接建立连接,服务端会没有中间态来阻止客户端的其他历史连接,导致建立起了无用历史连接,对资源造成浪费。而四次握手的话就没什么必要,因为三次握手已经保证了可靠连接的建立。

第二,同步双方的序列号。如下图所示:

序列号是客户端和服务端双方进行可靠传输和流量控制的重要依赖。客户端建立连接会初始化序列号 client_isn 并发送 SYN 报文给服务端,服务端收到 SYN 报文后,也会初始化一个序列号 server_isn 并发送 SYN+ACK 报文给客户端。第三次握手客户端会对服务端的序列号 server_isn 进行确认,并发送 ACK 报文给服务端。

在第二次握手的时候,服务端其实是做了两个操作,第一个操作是对客户端的 client_isn 进行确认,第二个操作是初始化自己的序列号 server_isn,只不过最后将这两步操作合并成一条 SYN+ACK 报文,将四次握手变成了三次握手。而如果只有二次握手,假如服务端的 SYN+ACK 报文在网络传输中丢失了,没有第三次握手的确认,双方是无法建立可靠的连接,其序列号也无法同步。

第三,避免资源浪费。如下图所示:

若没有第三次握手,服务端无法确认自己的发送的 SYN+ACK 报文有没有被客户端收到,因此每收到一个 SYN 报文只能建立一个连接,从而造成多个冗余的无效连接,造成资源的不必要浪费。

序列号唯一性

TCP 建立连接时,客户端和服务端通信时都会初始化序列号,用于表示数据的顺序。序列号是要保证唯一性的,这不仅可以历有效阻止历史报文报文被其他新建连接接收,同时,也有效防止黑客伪造相同序列号的 TCP 报文被对方接收。

SYN 攻击

一般情况下,系统内部会维护一个 SYN 队列(半连接队列)和 Accept 队列(全连接队列)。当服务端收到客户端的 SYN 报文后,会创建一个半连接对象,并将该对象加入到内核的 SYN 队列中。随后服务端会向客户端发送 SYN+ACK 报文,等待客户端回应的 ACK 报文。当服务端收到客户端发送的 ACK 报文后,会从 SYN 队列取出一个半连接对象,然后创建一个新的连接对象放入 Accept 队列中。最后,应用进程通过调用 socket 接口,从 Accept 队列中取出连接对象,建立连接。如下图所示:

系统维护 SYN 队列和 Accept 队列是会消耗服务器资源,SYN 攻击正是利用了 TCP 协议中的三次握手过程中的漏洞,不断消耗目标服务器资源并使其无法正常工作。攻击者发送大量伪造的 SYN 报文段给目标服务器,但是攻击者并不完成后续的握手过程。由于服务器需要为每个未完成的连接保留一些资源,当攻击者发送大量的伪造 SYN 请求时,服务器的资源很快会耗尽,无法继续处理正常的连接请求,导致服务不可用。

为了应对 SYN 攻击,可以采取一些措施。第一,限制对服务器 SYN 请求的频率和次数。第二,使用 SYN Cookie 技术,服务端收到客户端的 SYN 请求时不立马分配资源,而是生成一些信息在 SYN+ACK 报文中返回给客户端,再收到客户端的 ACK 报文后才建立连接。第三,现在单个 IP 的 SYN 请求数。第四,增大 SYN 队列的容量。

关闭 TCP 连接

TCP 协议关闭连接也有一套标准流程,需要历经四次通信才能安全可靠地关闭,俗称四次挥手。

四次挥手

当客户端关闭连接的时候,会向服务端发送一个 TCP 首部 FIN 标记位置为 1 的报文,即 FIN 报文,之后客户端会进入 FIN_WAIT_1 状态。

服务端收到客户端的 FIN 报文后,会向客户端发送 ACK 报文,随后服务端进入了 CLOSE_WAIT 状态。客户端收到服务端的 ACK 报文后,会进入 FIN_WAIT_2 状态。

服务端处理完数据后,也会向客户端发送 FIN 报文,随后进入了 LAST_ACK 状态。客户端收到了服务端的 FIN 报文后,会向服务端发送一个 ACK 报文,就进入了 TIME_WAIT 状态。

服务端收到客户端的 ACK 报文后,就进入了 CLOSE 状态,关闭连接。客户端经过了 2MSL 后也进入了 CLOSE 状态,关闭连接。

TIME_WAIT 的必要性

在了解 TCP 协议的四次挥手过程中,客户端会进入 TIME_WAIT 后,过 2MSL 时间后才真正关闭连接。TIME_WAIT 的时间是 2MSL,MSL 是 TCP 协议中的一个重要概念,表示最大报文段生存时间。而 2MSL 表示报文往返的最大生存时间,这足以让报文在网络中消失。TIME_WAIT 在这里有两个作用。

第一,错误接收。防止历史连接中的数据,被后面相同地址的连接错误接收。因此这个状态会维持 2MSL,足以保证连接过程中的数据包都会被丢弃,后续出现的数据包一定是建立在新的连接上。

第二,正确关闭。保证被动关闭连接的一方,可以正确关闭。如果客户端发送完最后一次的 ACK 报文后就关闭,中途 ACK 报文丢失,服务端会重新发起 FIN 报文。而此时的客户端已经关闭,收到服务端重传的 FIN 报文后,会返回 RST 报文。虽然也能关闭,但并不是一个正常的方式。

Socket 编程

TCP 协议是系统级的协议栈,开发者是通过 Socket 编程进行计算机通信的,它提供了一组用于网络通信的编程接口,使开发人员能够在不同的计算机之间发送和接收数据。

Socket 编程基于套接字(Socket)的概念。套接字是一个抽象的概念,可以看作是网络通信的端点,通过套接字可以进行数据的发送和接收。在 Socket 编程中,通常存在两种类型的套接字:

  • 服务器套接字(Server Socket):服务器套接字用于监听客户端的连接请求。服务器套接字在指定的端口上监听,一旦有客户端请求连接,服务器套接字会接受连接并创建一个新的套接字用于与客户端进行通信。
  • 客户端套接字(Client Socket):客户端套接字用于与服务器建立连接并进行通信。客户端套接字会向服务器的 IP 地址和端口发送连接请求,一旦连接建立成功,客户端套接字就可以与服务器进行数据的发送和接收。
  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将 socket 绑定在指定的 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

总结

本文先是从 TCP 协议的基础知识入手,对 TCP 协议的定义和作用,TCP 首部进行学习。接着开始了解建立 TCP 连接的内容,理解了三次握手的重要性以及风险。最后,也对 TCP 连接关闭进行了解。

相关推荐
码喽哈哈哈13 分钟前
day01
前端·javascript·html
XT462525 分钟前
解决 npm install 卡住不动或执行失败
前端·npm·node.js
前端小魔女40 分钟前
Rust赋能前端: 纯血前端将 Table 导出 Excel
前端
mubeibeinv1 小时前
分页/列表分页
java·前端·javascript
林太白1 小时前
js属性-IntersectionObserver
前端·javascript
吾即是光1 小时前
[SWPUCTF 2021 新生赛]error
android
爱吃羊的老虎1 小时前
【WEB开发.js】getElementById :通过元素id属性获取HTML元素
前端·javascript·html
lucifer3111 小时前
未集成Jenkins、Kubernetes 等自动化部署工具的解决方案
前端
大耳猫1 小时前
Android 基于Camera2 API进行摄像机图像预览
android·kotlin·相机·camera
MYBOYER1 小时前
Kotlin DSL Gradle 指南
android·开发语言·kotlin