Loading

TCP连接 & tcp or udp

😉 本文共6175字,阅读时间约20min

TCP连接

1 TCP报文

image.png
  • 源端口:源计算机上的应用程序的端口号
  • 目标端口:目标计算机的应用程序端口号
  • 序列号字段(seq):表示本报文段所发送数据的第一个字节的编号
  • 确认号(ack):表示接收方期望收到发送方下一个报文段的第一个字节数据的编号
  • 标志位字段
    • URG:标记是否有紧急数据
    • ACK:表示前面的ack是否有效。ACK=1 时表示有效。
    • PSH:告知接收方是否马上将数据提交给上层,PSH=1,马上提交不是缓存起来
    • RST:是否重建连接, RST=1说明 TCP 连接出现了严重错误(如主机崩溃),必须释放连接,然后再重新建立连接。
    • FIN:标记数据是否发送完毕。 FIN=1,表示数据已经发送完成,可以释放连接。
    • SYN:建立连接时使用,用来同步序号
      • 当 SYN=1,ACK=0 时,表示这是一个请求建立连接的报文段;
      • 当 SYN=1,ACK=1 时,表示对方同意建立连接。
      • SYN=1 时,说明这是一个请求建立连接或同意建立连接的报文。只有在前两次握手中 SYN 才为 1。
  • TCP校验和字段:校验传输过程中数据是否损坏,丢包等

2 TCP建立连接(三次握手)

2.1 三次握手过程

三次握手的主要作用就是为了确认双方的收发能力是否正常、指定自己的初始化序列号(ISN)为后面的可靠性传送做准备。在socket编程中,客户端执行connect()时,将触发三次握手。

三次握手.png
  • 一次握手:客户端发送带有 SYN(SEQ=x) 标志的数据包 -> 服务端,然后客户端进入 SYN_SENT 状态,等待服务器的确认;
  • 二次握手:服务端发送带有 SYN+ACK(SEQ=y,ACK=x+1) 标志的数据包 –> 客户端,然后服务端进入 SYN_RECV 状态
  • 三次握手:客户端发送带有带有 ACK(ACK=y+1) 标志的数据包 –> 服务端,然后客户端和服务器端都进入ESTABLISHED 状态,完成TCP三次握手。

2.2 为什么不能是两次握手?

角度一:为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。

client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。

假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。

角度二:TCP 的可靠连接是靠 seq( sequence numbers 序列号)来达成的。所以说,建立可靠连接,需要对客户端和服务器的起始序列号达成共识。第一次,客户端发送一个起始序列号seq = x的报文段给服务器。第二次,服务器端返回向客户端发送确认号 ack = x+1,表示对客户端的起始序列号x 表示确认,并告诉客户端,他的起始序列号是 seq = y。

假设不采用“三次握手”,这种情况下,只有服务器对客户端的起始序列号做了确认,但客户端却没有对服务器的起始序列号做确认,不能保证传输的可靠性。

角度三:需要三次握手才能确认双方的接收与发送能力是否正常。

2.3 为什么不是四次握手?

角度一:三次是保证双方互相明确对方能收能发的最低值。三次握手其实是双方各一次握手,各一次确认,其中一次握手和确认合并在一起,这样A,B能确认彼此能发送和接收自己的消息。四次握手可以提高可信度,但是浪费。

因为TCP 是双向通讯协议,作为响应一方(Responder) 要想初始化发送通道,必须也进行一轮 SYN + ACK。由于 SYN ACK 在 TCP 分组头部是两个标识位,因此处于优化目的被合并了。所以达到双方都能进行收发的状态只需要 3 个分组。

角度二:可以分成四次,来确认彼此的起始序列号,但第二次和第三次就可以合并,依据报文结构。这样可以提高连接的速度与效率。

2.4 第2次握手传回了ACK,为什么还要传回SYN?

服务端传回发送端所发送的 ACK 是为了告诉客户端:“我接收到的信息确实就是你所发送的信号了”,这表明从客户端到服务端的通信是正常的。回传 SYN 则是为了建立并确认从服务端到客户端的通信

2.5 什么是半连接队列?

服务器第一次收到客户端的 SYN 之后,就会处于 SYN_RCVD 状态,此时双方还没有完全建立其连接,服务器会把此种状态下请求连接放在一个队列里,我们把这种队列称之为半连接队列

当然还有一个全连接队列,就是已经完成三次握手,建立起连接的就会放在全连接队列中。如果队列满了就有可能会出现丢包现象。

这里在补充一点关于SYN-ACK 重传次数的问题:
服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段时间仍未收到客户确认包,进行第二次重传。如果重传次数超过系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。
注意,每次重传等待的时间不一定相同,一般会是指数增长,例如间隔时间为 1s,2s,4s,8s…

2.6 ISN(Initial Sequence Number)是固定的吗?

当一端为建立连接而发送它的SYN时,它为连接选择一个初始序号。ISN随时间而变化,因此每个连接都将具有不同的ISN。ISN可以看作是一个32比特的计数器,每4ms加1 。这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。

三次握手的其中一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number),以便让对方知道接下来接收数据的时候如何按序列号组装数据。如果在后续的报文中检查序列号不匹配,这个报文将被认为是非法报文,做丢弃处理。

如果 ISN 是固定的,攻击者很容易猜出后续的确认号,因此 ISN 是动态生成的。

假设C和S正在进行TCP通信,X是破坏者,可以预测TCP ISN。可能的攻击如下:

1)X首先对C进行攻击(比如Syn Flood),导致C不可用。

2)然后X仿冒C的地址对S发起连接请求。

3)S对C进行回应,附带ISN。注意:这个报文X是收不到的。

4)X可以预测ISN,可以按预测的ISN直接给S回应确认,这时S误认为已经和C建立了连接。

5)X这时就可以仿冒C的地址,发送恶意指令给S,S会认为这是C下发的指令,被欺骗执行,攻击生效。

2.7 SYN攻击是什么?

服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击。SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪。SYN 攻击是一种典型的 DDoS 攻击。

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

netstat -n -p TCP | grep SYN_RECV

2.8 三次握手过程中可以携带数据吗?

其实第三次握手的时候,是可以携带数据的。但是,第一次、第二次握手不可以携带数据

为什么这样呢?大家可以想一个问题,假如第一次握手可以携带数据的话,如果有人要恶意攻击服务器,那他每次都在第一次握手中的 SYN 报文中放入大量的数据。因为攻击者根本就不理服务器的接收、发送能力是否正常,然后疯狂着重复发 SYN 报文的话,这会让服务器花费很多时间、内存空间来接收这些报文。

也就是说,第一次握手不可以放数据,其中一个简单的原因就是会让服务器更加容易受到攻击了。而对于第三次的话,此时客户端已经处于 ESTABLISHED 状态。对于客户端来说,他已经建立起连接了,并且也已经知道服务器的接收、发送能力是正常的了,所以能携带数据也没啥毛病。

3 TCP断开连接(四次挥手)

3.1 四次挥手过程

TCP是全双工通信,可以双向传输数据。任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了 TCP 连接。

举个例子:A 和 B 打电话,通话即将结束后。

  1. 第一次挥手 : A 说“我没啥要说的了”
  2. 第二次挥手 :B 回答“我知道了”,但是 B 可能还会有要说的话,A 不能要求 B 跟着自己的节奏结束通话
  3. 第三次挥手 :于是 B 可能又巴拉巴拉说了一通,最后 B 说“我说完了”
  4. 第四次挥手 :A 回答“知道了”,这样通话才算结束。

3.2 为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?

因为服务器收到客户端断开连接的请求时,可能还有一些数据没有发完,这时先回复 ACK,表示接收到了断开连接的请求。等到数据发完之后再发 FIN,断开服务器到客户端的数据传送。

3.3 如果第二次挥手时服务器的 ACK 没有送达客户端,会怎样?

客户端没有收到 ACK 确认,会重新发送 FIN 请求。

CLOSE_WAIT & TIME_WAIT

TCP四次挥手中会有这两个状态

close_wait和time_wait的区别

下图展示了TCP四次挥手的各个阶段,以下将主动关闭方统称为client,将被动关闭方统称为server

img

可以看到,server在接收到client发来的FIN信号后,会进入close_wait状态,client在接收到server发来的FIN信号后,会进入time_wait状态

它们的区别在于:

  1. close_wait是server持有的状态,由于TCP连接是全双工的,close_wait状态可以保证server将剩余数据全部传递完成后,再关闭连接,因此close_wait持续时间不确定
  2. time_wait是client持有的状态,固定等待2MS

为什么第四次挥手客户端time_wait需要等待 2*MSL(报文段最大生存时间)时间后才进入 CLOSED 状态?

MSL是任何报文段被丢弃前在网络内的最长时间。这个时间是有限的,因为TCP报文段以IP数据报在网络内传输,而IP数据报则有限制其生存时间的TTL字段。

两个理由:

  1. 保证客户端发送的最后一个ACK报文段能够到达服务端

    这个ACK报文段有可能丢失,使得处于LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认,服务端超时重传FIN+ACK报文段,而客户端能在2MSL时间内收到这个重传的FIN+ACK报文段,接着客户端重传一次确认,重新启动2MSL计时器,最后客户端和服务端都进入到CLOSED状态,若客户端在TIME-WAIT状态不等待一段时间,而是发送完ACK报文段后立即释放连接,则无法收到服务端重传的FIN+ACK报文段,所以不会再发送一次确认报文段,则服务端无法正常进入到CLOSED状态

  2. 防止“已失效的连接请求报文段”出现在本连接中

    防止延迟的数据段, 被使用相同源地址, 源端口, 目的地址的tcp连接收到.

    客户端在发送完最后一个ACK报文段后,再经过2MSL,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,使下一个新的连接中不会出现这种旧的连接请求报文段。

MSL(Maximum Segment Lifetime) : 一个片段在网络中最大的存活时间,2MSL 就是一个发送和一个回复所需的最大时间。如果直到 2MSL,Client 都没有再次收到 FIN,那么 Client 推断 ACK 已经被成功接收,则结束 TCP 连接。

close_wait过多问题解析

原因

server接收到了client的FIN信号后进入close_wait状态,但后续并未发送FIN信号给client而是长期滞留在close_wait状态当中,而client一般会设置超时时间,所以即便最终server发出了FIN信号,此时很大概率client已经判断超时导致TCP连接异常。

危害

CLOSE_WAIT 过多导致服务器资源占用,客户端在 FIN_WAIT_2 状态超时大约 60s,自动进入 CLOSED状态, 影响不大。但是服务端在 CLOSE_WAIT 的超时时间默认为 43200秒,所以大量的 CLOSE_WAIT 积压可能造成服务器无法分配出资源给新的连接。一般这种情况都是由于服务器没有调用 close 造成的。程序员应该主动检查代码。

解决

就是去研究server在什么情况下FIN包会发送失败?

一般是程序问题:如果代码层面忘记了 close 相应的 socket 连接,那么自然不会发出 FIN 包,从而导致 CLOSE_WAIT 累积;代码不严谨,出现死循环之类的问题,导致即便后面写了 close 也永远执行不到。

time_wait过多问题解析

原因

比如用Nginx去做反向代理,在高并发短连接的场景下,可能导致TIME_WAIT 连接大量存在,属于正常现象。

危害

  1. 大量的socket不能及时被释放,从而导致性能下降
  2. 如果客户端的并发量持续很高,此时部分客户端就会显示连接不上
    1. 每一个 time_wait 状态,都会占用一个「本地端口」,上限为 65535;
    2. 当大量的连接处于 time_wait 时,新建立 TCP 连接会出错,address already in use : connect 异常
// 统计TCP各种连接状态的数量
$ netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
ESTABLISHED 1154
TIME_WAIT 1645

解决

  • 客户端,HTTP 请求的头部,connection 设置为 keep-alive,保持存活一段时间:现在的浏览器,一般都这么进行了

  • 服务器端(通过linux内核进行一些网络调整)

    • 允许 time_wait 状态的 socket 被重用(多个socket重用同一个端口)
    • 允许快速回收(可以强制快速回收TCP连接)

TCP/UDP

UDP报文

  • UDP协议分为首部字段和数据字段,其中首部字段只占用8个字节
    • 源端口、目的端口、长度和检验和。

image-20230305220633195

TCP与UDP的区别

  1. 是否面向连接:UDP传数据前不需要先建立连接,TCP传数据前必须先建立连接,传送完必须先释放连接
  2. 是否可靠传输::
    1. 远地主机在收到 UDP 报文后,不需要给出任何确认,并且不保证数据不丢失,不保证是否顺序到达。
    2. TCP 提供可靠的传输服务,TCP 在传递数据之前,会有三次握手来建立连接,而且在数据传递时,有确认、窗口、重传、拥塞控制机制。通过 TCP 连接传输的数据,无差错、不丢失、不重复、并且按序到达。
  3. 传输效率:TCP传输时多了连接、确认、重传等机制,所以TCP传输效率比UDP低很多
  4. 传输形式:TCP是面向字节流的,UDP是面向报文的
  5. 首部开销:TCP 首部开销(20 ~ 60 字节)比 UDP 首部开销(8 字节)要大。

什么时候选择 TCP,什么时候选 UDP?

UDP 一般用于即时通信,比如: 语音、 视频 、直播等等。这些场景对传输数据的准确性要求不是特别高,比如你看视频即使少个一两帧,实际给人的感觉区别也不大。

TCP 用于对传输准确性要求特别高的场景,比如文件传输、发送和接收邮件、远程登录等等

使用 TCP 的协议有哪些?使用 UDP 的协议有哪些?

运行于 TCP 协议之上的协议

HTTP 协议/HTTPS 协议 :主要为浏览器与服务器间通信设计的。HTTP 协议是基于 TCP 协议的,所以发送 HTTP 请求之前首先要建立 TCP 连接也就是要经历 3 次握手。

FTP 协议:FTP提供文件传输服务,可以屏蔽操作系统和文件存储方式。基于 TCP 实现可靠传输。

SMTP 协议:简单邮件传输协议,基于 TCP 协议,用来发送电子邮件。注意 ⚠️:接受邮件的协议不是 SMTP 而是 POP3 协议。

POP3/IMAP 协议: POP3 和 IMAP 两者都是负责邮件接收的协议。

SSH 协议 : SSH( Secure Shell)是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。SSH 建立在可靠的传输协议 TCP 之上。

运行于 UDP 协议之上的协议

RTP协议:实时传输协议,主要用于音视频传输协议,基于UDP协议

DNS : 域名系统(DNS,Domain Name System)将人类可读的域名转换为机器可读的 IP 地址。 我们可以将其理解为专为互联网设计的电话薄。实际上 DNS 同时支持 UDP 和 TCP 协议

TCP协议有哪些缺陷

  • 升级 TCP 的工作很困难;
  • TCP 建立连接的延迟;
  • TCP 存在队头阻塞问题;
  • 网络迁移需要重新建立 TCP 连接

That's why we have quic.

TCP/UDP 报最大长度

对于UDP协议来说,整个包的最大长度为65535,其中包头长度是65535-20=65515;

对于TCP协议来说,整个包的最大长度是由MSS决定,MSS就是TCP数据包每次能够传输的最大数据分段。为了达到最佳的传输效能,TCP协议在建立连接的时候通常要协商双方的MSS值。通讯双方会根据双方提供的MSS值得最小值确定为这次连接的最大MSS值。

据说,UDP传输最好不要超过1024 也就是1K,有这个事情吗?

因为使用UDP协议来传送数据,在数据发送后,在发送方并不确认对方是否接收到。这样就可能导致传送的数据在网络中丢失,尤其在网络条件并不很好的情况下,丢失数据包的现象就更多。udp容易丢包,所以不要太大。

Socket原理

socket套接字

套接字(socket)是支持TCP/IP协议的网络通信的基本操作单元。它包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

应用层通过传输层进行数据通信时,TCP会遇到同时为多个应用程序进程提供并发服务的问题。多个TCP连接或多个应用程序进程可能需要通过同一个 TCP协议端口传输数据。

为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了套接字(Socket)接口。应用层可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据传输的并发服务。

建立socket连接

建立Socket连接至少需要一对套接字,其中一个运行于客户端,称为ClientSocket ,另一个运行于服务器端,称为ServerSocket 。

套接字之间的连接过程分为三个步骤:服务器监听,客户端请求,连接确认。

连接确认:当服务器端套接字监听到或者说接收到客户端套接字的连接请求时,就响应客户端套接字的请求,建立一个新的线程,把服务器端套接字的描述发给客户端,一旦客户端确认了此描述,双方就正式建立连接。而服务器端套接字继续处于监听状态,继续接收其他客户端套接字的连接请求。

socket连接与TCP连接

创建Socket连接时,可以指定使用的传输层协议,Socket可以支持不同的传输层协议(TCP或UDP),当使用TCP协议进行连接时,该Socket连接就是一个TCP连接。通常情况下Socket连接就是TCP连接。

posted @ 2023-03-05 23:56  iterationjia  阅读(97)  评论(0编辑  收藏  举报