icode icode
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

iqqcode

保持对技术的探索实践与热爱
首页
  • Android学习

    • 📁基础内容
    • 📺AndroidCore
    • 🎨Android-UI
    • 🏖️Components
    • 📊Fragment
    • 🔗网络操作
    • 🔏异步机制
    • 📦数据存储
    • 🗃️Gradle
  • 学习笔记

    • 『框架』笔记
    • 『Kotlin』笔记
    • 《Vue》笔记
    • 《Git》学习笔记
    • 『Bug踩坑记录』
  • ListView
  • RecyclerView
  • ViewPager
  • Java笔记

    • 🟠JavaSE
    • 🟢JavaWeb
    • 🔴JavaEE
    • ⚪JavaTopic
    • 🍳设计模式
  • 计算机基础

    • 📌计算机网络
    • 🔍数据结构
    • 📦数据库
    • 💻OS
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • 学习
  • 面试
  • 心情杂货
  • 实用技巧
  • 友情链接
  • 关于

    • 📫关于我
  • 收藏

    • 网站
    • 资源
    • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础内容

  • TCP-IP

    • 【OSI-TCP网络模型】
    • HTTP-超文本传输协议
    • HTTPS
    • 全双工通讯的WebSocket协议
    • DNS
    • 传输层
    • 网络层
    • UDP协议
    • TCP握手挥手
      • 0. TCP协议的认识
      • 1. TCP协议特点
      • 2. TCP报文首部格式
        • 序号&确认号
        • URG&PSH
      • 3. 三次握手
      • 4. 四次挥手
      • 【三次握手即原因】
        • 为什么是三次
        • 为什么不是两次
        • 为什么不是四次
      • 【四次挥手及原因】
        • 为什么是四次
        • 为什么不是三次
      • 【TIME_WAIT 等待 2MSL】
      • 初始序列号ISN的初始化
      • SYN洪泛攻击
      • Client-Server状态变化
        • Client
        • Server
    • TCP可靠传输
    • 输入URL到跳转
    • Ping命令
  • 计算机网络
  • TCP-IP
iqqcode
2021-06-17
目录

TCP握手挥手

TCP-传输控制协议 (opens new window)

# 0. TCP协议的认识

什么是 TCP 连接?

用于保证 可靠性 和 流量控制 维护的某些-状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。

aa

所以我们可以知道,建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。

  • Socket:由 IP 地址和端口号组成
  • 序列号:用来解决乱序问题等
  • 窗口大小:用来做流量控制

# 1. TCP协议特点

  1. 面向连接(虚连接)
  2. 点对点传输。每一条TCP连接只能有两个端点,无法进行广播或多播
  3. 可靠有序,不丢不重。TCP提供可靠的交付服务。无差错,不丢失,不重复,按序到达
  4. 全双工通信
  5. 面向字节流

【面向连接】:一定是「一对一」才能连接,不能像 UDP 协议 可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;

【可靠】:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;

【字节流】:TCP将数据看成仅仅是一 连串的无结构的字节流。消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃

【全双工通信】:发送方和接收方可以同时发送数据,接收数据。协议两端都设有发送缓存,接收缓存


# 2. TCP报文首部格式

20B的固定首部 + 选项字段,4B对齐方式

【源端口】:发送方端口,16位

【目的端口】:接收方端口,16位

【序号】:本报文段所发送的数据-的第一个字节的序号

【确认号】:期望收到下一个报文段数据的第一个字节的序号

【数据偏移】:TCP首部长度,最大长度为(2^4^ - 1) * 4B = 60B,固定首部20B + 可变头部40B。由于首部长度不固定,所以数据起始位置不固定

【六个控制位】

  • 紧急位URG:发送方紧急处理位。URG=1表示此段报文有紧急数据,要立即发送出去,不用在缓存队列中排队,配合紧急指针插队优先处理

  • 确认位ACK:ACK=1,确认号有效

  • 推送位PSH:接收方紧急处理位。PSH=1时,接收方尽快向应用进程交付此段报文,不必等缓存队列填满

  • 复位RST:RST=1时,表示TCP与主机的连接出现严重差错,必须释放连接再重新建立

  • 同步位SYN:SYN=1,表明一个连接 请求/连接接收报文

  • 终止位FIN:FIN=1,表明发送方数据已发完,要求释放连接

【窗口】:接收方接收窗口的大小,即现在允许发送方发送的数据量,根据接收方的窗口大小,设置发送方的发送缓存

【检验和】:检验首部 + 数据,检验时要加上12B伪首部「伪IP数据报首部」;第四个字段为协议字段,TCP为6,UDP为17

【紧急指针】:URG=1时才有意义,指出本报文段中紧急数据的位置

【选项】:最大报文段长度MSS、窗口扩大、时间戳、选择确认

【填充】:填充0保证4字节对齐

# 序号&确认号

序号:本报文段所发送的数据-的第一个字节的序号

确认号:确认上一次发送的成功收到。期望收到下一个报文段数据的第一个字节的序号

序号的初始值是在建立连接后,随机生成的

# URG&PSH

  • 紧急位URG:发送方紧急处理位。URG=1表示此段报文有紧急数据,要立即发送出去,不用在缓存队列中排队,配合紧急指针插队优先处理

  • 推送位PSH:接收方紧急处理位。PSH=1时,接收方尽快向应用进程交付此段报文,不必等缓存队列填满



# 3. 三次握手

三次握手

【握手阶段】

  1. 建立链接前需要 Server 端先监听端口,因此 Server 端建立链接前的初始状态就是 LISTEN 状态,这时 Client 端准备建立链接。在第一次消息发送中,Client随机选取一个序列号作为自己的初始序号发送给Server,Client 端的链接状态变成了 SYN_SENT 状态;
  2. Server收到了来自Client的连接请求,如果在资源条件合理的情况下,服务器为该TCP连接分配缓存和变量。Server使用ACK对Client的数据包进行确认,因为已经收到了序列号为x的数据包,准备接收序列号为 x+1 的包,所以 ack=x+1,同时Server告诉Client自己的初始序列号,就是seq=y;发送完 ACK 和 SYN 后,Server 端的链接状态就变成了 SYN_RCVD
  3. Client 收到 Server 的 ACK 后,Client 端的链接状态就变成了 ESTABLISHED 状态。同时,Client 向 Server 端发送 ACK,回复 Server 端的 SYN 请求。Client回复 seq=x+1、ack=y+1 ,Client为该TCP连接分配缓存和变量

# 4. 四次挥手

四次挥手

【挥手阶段】

  1. Client准备关闭连接,首先主动向Server发送一个 TCP 首部 FIN 标志位被置为 1 的报文,Client随机选取一个序列号seq=u作为自己的初始序号发送给Server,之后客户端进入 FIN_WAIT_1 状态。
  2. Server回应ACK对Client的数据包进行确认,回复确认号ack=u+1,同时Server告诉Client自己的初始序列号seq=v;客户到服务器这个方向的连接就成了一一半关闭 CLOSED_WAIT 状态。由于Server可能还与他客户端同时在通信,或者可能此时对A的数据没有发送完,此次不会回复FIN标志位。
  3. 第三条消息Server告诉Client可以断开连接,数据已传输完。回复FIN=1,ACK=1确认。Server依然回复第二次的确认号ack=u+1,因为这段时间Clinet处于半关闭状态,并没有在发送数据 。Server回复seq=w,进入 LAST_ACK状态
  4. Clint收到Server的反馈后,回送ACK=1,由于要断开连接不再发送数据了,seq=u+1(序号为上次的ack),ack=w+1。发送完后Clinet进入 TIME_WAIT状态。Client在等待 2MSL 后,确保Server收到第四条消息后,自动进入 CLOSE状态。Server收到第四条消息后进入了 CLOSE 状态。至此连接接关闭!


# 【三次握手即原因】

# 为什么是三次

所以,重要的是为什么三次握手才可以初始化Socket、序列号和窗口大小,并建立 TCP 连接。

以下原因:

  1. 三次握手才可以: 阻止历史重复连接-的-初始化(主要原因)
  2. 三次握手才可以: 同步双方的初始序列号和序号
  3. 三次握手才可以: 避免资源浪费

不使用「两次握手」和「四次握手」的原因:

  • 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
  • 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。



# 原因一:避免历史连接

我们来看看 RFC 793 指出的 TCP 连接使用三次握手的首要原因:

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?

三次握手避免历史连接

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:

  • 一个「旧 SYN 报文」比「最新的 SYN 」 报文早到达了服务端;
  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。

如果是两次握手连接,就不能判断当前连接是否是历史连接。


三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:

  • 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
  • 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;

所以, TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。


# 原因二:同步双方初始序列号

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:

  • 接收方可以去除重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中, 哪些是已经被对方收到的;

可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

87

四次握手与三次握手

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。


# 原因三:避免资源浪费(冗余连接)

如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?

如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

39551

两次握手会造成资源浪费

image.gif




# 为什么不是两次

1. 两次握手会造成消息滞留

  • 服务器重复接受无用的连接请求 SYN 报文,而造成重复分配资源

2. 只能得知客户端具有发送的能力,不知道其是否有接收能力

  • 两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收

# 为什么不是四次

三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。



# 【四次挥手及原因】

# 为什么是四次

  1. 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了,但是还能接收数据

  2. 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文;而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

服务端通常需要等待完成数据的发送和处理,所以服务端的 ACK 和 FIN 一般都会分开发送,从而比三次握手导致多了一次。


# 为什么不是三次

四次才能确保数据能够完成传输。

关闭连接时,当服务器收到客户端的FIN报文通知时,它仅仅表示客户端没有数据发送给服务器了;但未必服务器所有的数据都全部发送给对方了

服务器不会关闭SOCKET,可能还需要发送一些数据给客户端之后,所以第二次只发送ACK确认报文,第三次再发送FIN报文给对方来表示同意现在可以关闭连接了

所以它ACK报文和FIN报文多数情况下都是分开发送的。


挥手的时候为什么是分开的时候发送呢?

  • 一起发送可能会导致一方被迫强制关闭

因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。

但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭 SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。

如果服务器段将ACK(第二次挥手)和FIN(第三次挥手)合并成一块发过去的话,这就意味着一方关闭,另一方也要被迫关闭,若此时有服务器继续单向的发送片段给客户端的需求则无法实现。

故需要四步挥手。




# 【TIME_WAIT 等待 2MSL】

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。

Client发送完第四次消息后,并不知道Server是否接收到了这次的消息,因为是可靠传输,但是挥手只是4次,它需要默默等待Server接收到消息后才下线。(🤔PS:真实负责的好男人呀!)

如果报文丢失了或者Server接收到错误的报文:

  • Server重复第三次挥手过程,让Client再重新发送一次

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是:

网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间。

比如,如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时。

等待2MSL是为了确认服务器能够收到第四次挥手消息




# 初始序列号ISN的初始化

为什么客户端和服–务端的初始序列号 ISN 是不相同的?

因为网络中的报文会延迟、会复制重发、也有可能丢失,这样会造成的不同连接之间产生互相影响,所以为了避免互相影响,客户端和服务端的初始序列号是随机且不同的。

ISN是不能硬编码的,不然会出问题的——比如:如果连接建好后始终用1来做ISN,如果client发了30个segment过去,但是网络断了,于是 client重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client的Sequence Number 可能是3,而Server端认为client端的这个号是30了。全乱了。

RFC793 (opens new window)中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一操作,直到超过2^32^,又从0开始。这样,一个ISN的周期大约是4.55个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过MSL(Maximum Segment Lifetime ),所以,只要MSL的值小于4.55小时,那么,我们就不会重用到ISN。

这样做可以防止ISN重用,另外出于安全考虑,也不应该对ISN硬编码。


# SYN洪泛攻击

https://zh.wikipedia.org/wiki/SYN_flood

TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),消耗服务器资源,使得服务器不能为正常用户服务。

SYN 攻击.png

SYN 攻击

image.gif

SYN Flood是当前最流行的DoS (opens new window)(拒绝服务攻击)与DDoS (opens new window)(Distributed Denial Of Service分布式拒绝服务攻击 (opens new window))的方式之一。

【解决方式】

  1. 缩短SYN Timeout时间
  2. 设置SYN Cookie
  3. SYN flood可以用DCN防火墙来拦截
  4. image-20201102143958970

第一种:缩短SYN Timeout时间

由于SYN Flood攻击的效果取决于服务器上保持的SYN半连接数 (opens new window),这个值=SYN攻击的频度 x SYN Timeout,所以通过缩短从接收到SYN报文到确定这个报文无效并丢弃该连接的时间,例如设置为20秒以下(过低的SYN Timeout设置可能会影响客户的正常访问),可以成倍的降低服务器的负荷。

第二种方法:设置SYN Cookie

就是给每一个请求连接的IP地址分配一个Cookie,如果短时间内连续受到某个IP的重复SYN报文,就认定是受到了攻击,以后从这个IP地址来的包会被丢弃。

可是上述的两种方法只能对付比较原始的SYN Flood攻击

第三种方法:DCN防火墙来拦截

防火墙做一个中继,起到保护作用。

  • SYN网关
  • 被动SYN网关
  • SYN中继

防火墙如何阻止SYN Flood攻击 (opens new window)

I. SYN网关

防火墙收到 服务端的 SYN/ACK 包后 , 会立刻发送一个ACK包 给服务端,减少半连接数。(当客户端真正的ACK包到达时,有数据 则转发给服务端,没有数据则丢弃该包。)

在这里插入图片描述

II. 被动SYN网关

防火墙的超时期限远小于服务器的超时期限,当 超过 防火墙的期限后,客户端 还没有发送ACK包,防火墙就会向服务器发送RST包,节约了半连接时间。

在这里插入图片描述

III. SYN中继

得先跟防火墙三次握手成功,才能和 服务端 进行连接。

在这里插入图片描述




# Client-Server状态变化

6400

# Client

  • SYN_SENT - 客户端发起第 1 次握手后,连接状态为 SYN_SENT ,等待服务端内核进行应答,如果服务端来不及处理(例如服务端的 backlog 队列已满)就可以看到这种状态的连接。
  • ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。客户端收到服务器回复的 SYN+ACK 后,对服务端的 SYN 单独回复(第 3 次握手),连接建立完成,进入 ESTABLISHED 状态。服务端程序收到第 3 次握手包后,也进入 ESTABLISHED 状态。
  • FIN_WAIT_1 - 客户端发送了关闭连接的 FIN 报文后,等待服务端回复 ACK 确认。
  • FIN_WAIT_2 - 表示我方已关闭连接,正在等待服务端关闭。客户端发了关闭连接的 FIN 报文后,服务器发回 ACK 应答,但是没进行关闭,就会处于这种状态。
  • TIME_WAIT - 双方都正常关闭连接后,客户端会维持 TIME_WAIT 一段时间,以确保最后一个 ACK 能成功发送到服务器端。停留时长为 2 倍的 MSL (报文最大生存时间),Linux 下大约是 60 秒。所以在一个频繁建立短连接的服务器上通常可以看到成千上万的 TIME_WAIT 连接。

# Server

  • LISTEN - 表示当前程序正在监听某个端口时。
  • SYN_RCVD - 服务端收到第 1 次握手后,进入 SYN_RCVD 状态,并回复一个 SYN+ACK(第 2 次握手),再等待对方确认。
  • ESTABLISHED - 表示连接处于正常状态,可以进行数据传送。完成 TCP3 次握手后,连接建立完成,进入 ESTABLISHED 状态。
  • CLOSE_WAIT - 表示客户端已经关闭连接,但是本地还没关闭,正在等待本地关闭。有时客户端程序已经退出了,但服务端程序由于异常或 BUG 没有调用 close()函数对连接进行关闭,那在服务器这个连接就会一直处于 CLOSE_WAIT 状态,而在客户机已经不存在这个连接了。
  • LAST_ACK - 表示正在等待客户端对服务端的关闭请求进行最终确认。
编辑 (opens new window)
上次更新: 2021/06/27, 10:49:09
UDP协议
TCP可靠传输

← UDP协议 TCP可靠传输→

最近更新
01
匿名内部类
10-08
02
函数式接口
10-08
03
ARouter-Kotlin踩坑
10-05
更多文章>
Theme by Vdoing | Copyright © 2021-2023 iqqcode | MIT License | 备案号-京ICP备2021028793号
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×