tcp连接加深

参考:

标识符含义

三次握手

流程图

dail

建立连接(客户端B和服务器A通信)

  1. TCP 连接建立之前, 双方都处于 CLOSED 状态
  2. 主动发起 TCP 连接的一方向另一方发送 SYN = 1, Sequence Number = k 的握手请求, 这是第一个握手, 此 Segment 发出以后, 主动发起 TCP 连接的一方由 CLOSED 状态转变为 SYN - SENT 状态
  3. 被动打开的一方在收到另一方发来的握手请求后, 若同意建立连接, 则发送 SYN = 1, ACK = 1, Acknowledgment Number = k + 1, Sequence Number = m 的 TCP Segment, 当该 Segment 发出以后, 被动打开的一方由 LISTEN 状态转变为 SYN - RCVD 状态, 这是第二个握手
  4. 主动打开的一方在收到另一方的握手响应之后, 发送 ACK = 1, Acknowledgment Number = m + 1, Sequence Number = k + 1 的 TCP Segment, 当该 Segment 发出以后, 主动发起 TCP 连接的一方由 SYN - SENT 状态转变为 ESTAB - LISTEN 状态, 这是第三个握手 (第三个握手消息可以包含有数据, 如果没有数据则不消耗序号, 即若该握手消息中没有携带数据, 则下一个 TCP Segment 仍可以使用值为 k + 1 的 Sequence Number)
  5. 主动发起 TCP 连接的一方在发送第三个握手消息之后便进入连接已建立的状态了, 它可以开始向接收方发送正式的数据
  6. 被动打开的一方在收到主动发起的一方发送的第三个握手消息之后也进入 ESTAB - LISTEN 状态, 此时也可以正式开始收发数据

建立连接服务侧处理图

dail_detail

三次握手原因

四次挥手

流程图

dail

关闭连接(客户端B和服务器A通信)

  1. A 向 B 发送 FIN = 1, Sequence Number = k 的 TCP Segment, 当该 Segment 发出以后, A 由 ESTAB - LISTEN 状态转变为 FIN - WAIT - 1 状态 (第一次挥手)
  2. B 收到 A 发来的第一次挥手 Segment 后, 向 A 发送 ACK = 1, Acknowledgment Number = k + 1, Sequence Number = m 的 TCP Segment, 当该 Segment 发出以后, B 由 ESTAB - LISTEN 状态转变为 CLOSE - WAIT 状态 (第二次挥手)
  3. A 收到 B 发来的第二次挥手 Segment 之后, 便由 FIN - WAIT - 1 状态转变为 FIN - WAIT - 2 状态, 此时由 A → B 上的连接可以认为已经释放, 但 B 仍然可以给 A 发送消息
  4. B 如果不想关闭连接, 可以持续正常地向 A 发送 TCP Segment, 假设在某个时间点上, B 也想关闭 TCP 连接, 则 B 向 A 发送 FIN = 1, ACK = 1, Acknowledgment Number = k + 1, Sequence Number = j 的 Segment, B 发出以后, 它由 CLOSE - WAIT 状态转变为 LAST - ACK 状态 (第三次挥手)
  5. A 收到 B 发来的 FIN = 1 的 Segment 后, 向 B 发送 ACK = 1, Acknowledgment Number = j + 1, Sequence Number = k + 1 的 Segment, 该 Segment 发出以后, A 由 FIN - WAIT - 2 状态转变为 TIME - WAIT 状态 (第四次挥手)
  6. B 收到 A 发送的第四次挥手消息之后, 便由 LAST - ACK 状态转变为 CLOSED 状态, 对 B 来说, TCP 连接已彻底释放
  7. 但 A 仍需要等待一段时间, RFC 793 建议的等待时长为 2min * 2, 其中 2 min 是 MSL (Maximum Segment Lifetime, 即估计一个 TCP Segment 从发出以后在被接收之前在网络中存活的最长时间), 这里 A 在最后一次发出 Segment 之后仍需要等待 2 * MSL 才可以彻底释放连接

TIME_WAIT作用

TIME_WAIT过多

重传输机制

超时重传

RTT: Round-Trip Time 往返时延
RTO: Retransmission Timeout 超时重传时间

  1. 估计往返时间,通常需要采样以下两个:
    • 需要 TCP 通过采样 RTT 的时间,然后进行加权平均,算出一个平滑 RTT 的值,而且这个值还是要不断变化的,因为网络状况不断地变化。
    • 除了采样 RTT,还要采样 RTT 的波动范围,这样就避免如果 RTT 有一个大的波动的话,很难被发现的情况。
  2. 如果超时重发的数据,再次超时的时候,又需要重传的时候,TCP 的策略是超时间隔加倍。
    RTO具体计算参考: rfc2988

快速重传

TCP 还有另外一种快速重传(Fast Retransmit)机制,它不以时间为驱动,而是以数据驱动重传。 quickresend 在上图,发送方发出了 1,2,3,4,5 份数据:

  1. 第一份 Seq1 先送到了,于是就 Ack 回 2;
  2. 结果 Seq2 因为某些原因没收到,Seq3 到达了,于是还是 Ack 回 2;
  3. 后面的 Seq4 和 Seq5 都到了,但还是 Ack 回 2,因为 Seq2 还是没有收到;
  4. 发送端收到了三个 Ack = 2 的确认,知道了 Seq2 还没有收到,就会在定时器过期之前,重传丢失的 Seq2。
  5. 最后,收到了 Seq2,此时因为 Seq3,Seq4,Seq5 都收到了,于是 Ack 回 6 。 快速重传机制只解决了一个问题,就是超时时间的问题,但是它依然面临着另外一个问题。就是重传的时候,是重传之前的一个,还是重传所有的问题。

SACK 方法

SACK( Selective Acknowledgment 选择性确认)
这种方式需要在 TCP 头部「选项」字段里加一个 SACK 的东西,它可以将缓存的地图发送给发送方,这样发送方就可以知道哪些数据收到了,哪些数据没收到,知道了这些信息,就可以只重传丢失的数据。
如果要支持 SACK,必须双方都要支持。在 Linux 下,可以通过 net.ipv4.tcp_sack 参数打开这个功能(Linux 2.4 后默认打开)。

Duplicate SACK

Duplicate SACK 又称 D-SACK,其主要使用了 SACK 来告诉「发送方」有哪些数据被重复接收了。
D-SACK 有这么几个好处:

  1. 可以让「发送方」知道,是发出去的包丢了,还是接收方回应的 ACK 包丢了;
  2. 可以知道是不是「发送方」的数据包被网络延迟了;
  3. 可以知道网络中是不是把「发送方」的数据包给复制了; 在 Linux 下可以通过 net.ipv4.tcp_dsack 参数开启/关闭这个功能(Linux 2.4 后默认打开)

滑动窗口

引入原因

为每个包确认应答,效率较低,引入了窗口这个概念。即使在往返时间较长的情况下,它也不会降低网络通信的效率。
窗口大小就是指无需等待确认应答,而可以继续发送数据的最大值。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。

累计确认

发送299和399到服务端,假设没有收到ACK300,只收到ACK400,也认为400前的数据都收到了

包文字段

TCP 头里有一个字段叫 Window,也就是窗口大小。通常窗口的大小是由接收方的窗口大小来决定的。发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

TCP发送方滑动窗口

使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)

slidingwindowsend

TCP接收方滑动窗口

其中三个接收部分,使用两个指针进行划分:

slidingwindowrcv

接收窗口和发送窗口大小并不是完全相等,接收窗口的大小是约等于发送窗口的大小的

流量控制

原因

防止接收方处理不过来,TCP 提供一种机制可以让「发送方」根据「接收方」的实际接收能力控制发送的数据量,这就是所谓的流量控制。

流量控制原理

  1. TCP 通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。
  2. 如果窗口大小为 0 时,就会阻止发送方给接收方传递数据,直到窗口变为非 0 为止,这就是窗口关闭

窗口关闭潜在的危险和解决

拥塞控制

原因

前面的流量控制是避免「发送方」的数据填满「接收方」的缓存,但是并不知道网络的中发生了什么。
一般来说,计算机网络都处在一个共享的环境。因此也有可能会因为其他主机之间的通信使得网络拥堵。

在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大….

于是,就有了拥塞控制,控制的目的就是避免「发送方」的数据填满整个网络

实现

拥塞控制主要是四个算法

慢启动

拥塞避免算法

拥塞发生

快速恢复

拥塞算法示意图

congestionalgorithm

socket编程

socket

socket编程流程

listen(linux)源码分析

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的socket,后续用来传输数据。 所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket。 Linux内核中会维护两个队列:

状态机

state

int listen (int socketfd, int backlog)
在早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。

在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。

但是上限值是内核参数 somaxconn 的大小,也就说 accpet 队列长度 = min(backlog, somaxconn)。

状态统计

netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'  

主要配置(/etc/sysctl.conf)

#对于一个新建连接,内核要发送多少个 SYN 连接请求才决定放弃,不应该大于255,默认值是5,对应于180秒左右时间   
net.ipv4.tcp_syn_retries=2  
#net.ipv4.tcp_synack_retries=2  
#表示当keepalive起用的时候,TCP发送keepalive消息的频度。缺省是2小时,改为300秒  
net.ipv4.tcp_keepalive_time=1200  
net.ipv4.tcp_orphan_retries=3  
#表示如果套接字由本端要求关闭,这个参数决定了它保持在FIN-WAIT-2状态的时间  
net.ipv4.tcp_fin_timeout=30    
#表示SYN队列的长度,默认为1024,加大队列长度为8192,可以容纳更多等待连接的网络连接数。  
net.ipv4.tcp_max_syn_backlog = 4096  
#表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭  
net.ipv4.tcp_syncookies = 1  
  
#表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭  
net.ipv4.tcp_tw_reuse = 1  
#表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭  
net.ipv4.tcp_tw_recycle = 1  
  
##减少超时前的探测次数   
net.ipv4.tcp_keepalive_probes=5   
##优化网络设备接收队列   
net.core.netdev_max_backlog=3000   

## 关闭延迟ACK
## 这样的问题就是每个TCP数据包都会有一个ACK包,增加了网络的包量
root权限下把/proc/sys/net/ipv4/tcp_no_delay_ack文件的值修改成1即可。

支付宝打赏 微信打赏

来杯咖啡~

文章导航