Transmission Control Protocol,传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP协议的目的是: 在不可靠传输的IP层之上建立一套可靠传输的机制。 TCP的可靠只是对于它自身来说的, 甚至是对于socket接口层, 两个系统就不是可靠的了, 因为发送出去的数据, 没有确保对方真正的读到(所以要在业务层做重传和确认机制)。
可靠传输的第一要素是 确认 , 第二要素是 重传 , 第三要素是 顺序 。 任何一个可靠传输的系统, 都必须包含这三个要素。 数据校验 也是必要的。
传输是一个广义的概念, 不局限于狭义的网络传输, 应该理解为通信和交互 任何涉及到通信和交互的东西, 都可以借鉴TCP的思想。无论是在UDP上实现可靠传输或者创建自己的通信系统,无论这个系统是以API方式还是服务方式,只要是一个通信系统,就要考虑这三个要素。
SeqNum的增加是和传输的字节数相关的。 上图中,三次握手后,来了两个Len:1440的包,而第二个包的SeqNum就成了1441。然后第一个ACK回的是1441(下一个待接收的字节号),表示第一个1440收到了。
网络上的传输是没有连接的,包括TCP也是一样的 。而TCP所谓的“连接”,其实只不过是在通讯的双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP的状态变换是非常重要的。
查看各种状态的数量
ss -ant | awk '{++s[$1]} END {for(k in s) print k,s[k]}'
通过三次握手完成连接的建立
三次握手的目的是交换通信双方的初始化序号,以保证应用层接收到的数据不会乱序,所以叫SYN(Synchronize Sequence Numbers)。
ISN是不能hard code的,不然会出问题的。比如:如果连接建好后始终用1来做ISN,如果client发了30个segment过去,但是网络断了,于是client重连,又用了1做ISN,但是之前连接的那些包到了,于是就被当成了新连接的包,此时,client的Sequence Number可能是3,而Server端认为client端的这个号是30了。全乱了。RFC793中说,ISN会和一个假的时钟绑在一起,这个时钟会在每4微秒对ISN做加一 *** 作,直到超过232,又从0开始。这样,一个ISN的周期大约是455个小时。因为,我们假设我们的TCP Segment在网络上的存活时间不会超过Maximum Segment Lifetime(MSL),所以,只要MSL的值小于455小时,那么,我们就不会重用到ISN。
如果Server端接到了Clien发的SYN后回了SYN-ACK,之后Client掉线了,Server端没有收到Client返回的ACK,那么,这个连接就处于一个中间状态,即没成功,也没失败。于是,Server端如果在一定时间内没有收到的ACK会重发SYN-ACK。在Linux下,默认重试次数为5次,重试的间隔时间从1s开始每次都翻番,5次的重试时间间隔为1s, 2s, 4s, 8s, 16s,总共31s,第5次发出后还要等32s都知道第5次也超时了,所以,总共需要 1s + 2s + 4s+ 8s+ 16s + 32s = 26 -1 = 63s,TCP才会断开这个连接。
客户端给服务器发了一个SYN后,就下线了,于是服务器需要默认等63s才会断开连接,这样,攻击者就可以把服务器的SYN连接的队列耗尽,让正常的连接请求不能处理。
于是,Linux下给了一个叫tcp_syncookies的参数来应对这个事:当SYN队列满了后,TCP会通过源地址端口、目标地址端口和时间戳打造出一个特别的Sequence Number发回去(又叫cookie),此时服务器并没有保留客户端的SYN包。如果是攻击者则不会有响应,如果是正常连接,则会把这个SYN Cookie发回来,然后服务端可以通过cookie建连接(即使你不在SYN队列中)。
千万别用tcp_syncookies来处理正常的大负载的连接的情况。因为sync cookies是妥协版的TCP协议,并不严谨。应该调整三个TCP参数:tcp_synack_retries减少重试次数,tcp_max_syn_backlog增大SYN连接数,tcp_abort_on_overflow处理不过来干脆就直接拒绝连接
因为TCP是全双工的,因此断开连接需要4次挥手,发送方和接收方都需要发送Fin和Ack。如果两边同时断连接,那就会就进入到CLOSING状态,然后到达TIME_WAIT状态。
指的是报文段的最大生存时间,如果报文段在网络中活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的 *** 作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒
主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长。由于TIME_WAIT的存在,大量短连接会占有大量的端口,造成无法新建连接。
主动关闭的一方发出 FIN包,被动关闭的一方响应ACK包,此时,被动关闭的一方就进入了CLOSE_WAIT状态。如果一切正常,稍后被动关闭的一方也会发出FIN包,然后迁移到LAST_ACK状态。
CLOSE_WAIT状态在服务器停留时间很短,如果你发现大量的 CLOSE_WAIT状态,那么就意味着被动关闭的一方没有及时发出FIN包。
TCP要保证所有的数据包都可以到达,所以,必需要有重传机制。
接收端给发送端的Ack确认只会确认最后一个连续的包 ,比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack 3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,因为正如前面所说的,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了
但总体来说都不好。因为都在等timeout,timeout可能会很长
不以时间驱动,而以数据驱动重传
如果包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传
Selective Acknowledgment, 需要在TCP头里加一个SACK的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版,在发送端就可以根据回传的SACK来知道哪些数据到了,哪些没有收到
重复收到数据的问题,使用了SACK来告诉发送方有哪些数据被重复接收了
经典算法:Karn/Partridge算法,Jacobson/Karels算法
TCP必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包
Advertised-Window :接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来
接收端LastByteRead指向了TCP缓冲区中读到的位置,NextByteExpected指向的地方是收到的连续包的最后一个位置,LastByteRcved指向的是收到的包的最后一个位置,我们可以看到中间有些数据还没有到达,所以有数据空白区。
发送端的LastByteAcked指向了被接收端Ack过的位置(表示成功发送确认),LastByteSent表示发出去了,但还没有收到成功确认的Ack,LastByteWritten指向的是上层应用正在写的地方。
接收端在给发送端回ACK中会汇报自己的AdvertisedWindow = MaxRcvBuffer – LastByteRcvd – 1;
收到36的ack,并发出了46-51的字节
如果Window变成0了,发送端就不发数据了
如果发送端不发数据了,接收方一会儿Window size 可用了,怎么通知发送端呢:TCP使用了Zero Window Probe技术,缩写为ZWP,也就是说,发送端在窗口变成0后,会发ZWP的包给接收方,让接收方来ack他的Window尺寸,一般这个值会设置成3次,每次大约30-60秒。如果3次过后还是0的话,有的TCP实现就会发RST把链接断了。
如果你的网络包可以塞满MTU,那么你可以用满整个带宽,如果不能,那么你就会浪费带宽。避免对小的window size做出响应,直到有足够大的window size再响应。
如果这个问题是由Receiver端引起的,那么就会使用David D Clark’s 方案。在receiver端,如果收到的数据导致window size小于某个值,可以直接ack(0)回sender,这样就把window给关闭了,也阻止了sender再发数据过来,等到receiver端处理了一些数据后windows size大于等于了MSS,或者receiver buffer有一半为空,就可以把window打开让send 发送数据过来。
如果这个问题是由Sender端引起的,那么就会使用著名的 Nagle’s algorithm。这个算法的思路也是延时处理,他有两个主要的条件:1)要等到 Window Size >= MSS 或是 Data Size >= MSS,2)等待时间或是超时200ms,这两个条件有一个满足,他才会发数据,否则就是在攒数据。
TCP_CORK是禁止小包发送,而Nagle算法没有禁止小包发送,只是禁止了大量的小包发送
TCP不是一个自私的协议,当拥塞发生的时候,要做自我牺牲
拥塞控制的论文请参看 《Congestion Avoidance and Control》
主要算法有:慢启动,拥塞避免,拥塞发生,快速恢复,TCP New Reno,FACK算法,TCP Vegas拥塞控制算法
TCP网络协议及其思想的应用
TCP 的那些事儿(上)
TCP 的那些事儿(下)
tcp为什么是三次握手,为什么不是两次或四次?
记一次TIME_WAIT网络故障
再叙TIME_WAIT
tcp_tw_recycle和tcp_timestamps导致connect失败问题
tcp短连接TIME_WAIT问题解决方法大全(1)- 高屋建瓴
tcp短连接TIME_WAIT问题解决方法大全(2)- SO_LINGER
tcp短连接TIME_WAIT问题解决方法大全(3)- tcp_tw_recycle
tcp短连接TIME_WAIT问题解决方法大全(4)- tcp_tw_reuse
tcp短连接TIME_WAIT问题解决方法大全(5)- tcp_max_tw_buckets
TCP的TIME_WAIT快速回收与重用
浅谈CLOSE_WAIT
又见CLOSE_WAIT
PHP升级导致系统负载过高问题分析
Coping with the TCP TIME-WAIT state on busy Linux servers
在CentOS中,可以通过配置内核参数来调整网速。以下是一些常用的内核参数:
1 netcoresomaxconn:此参数指定了待处理请求的最大数目。如果您正在处理大量网络请求,可以适当增加该参数以提高性能。
2 netcorenetdev_max_backlog:此参数指定了网络设备队列的最大长度。如果队列长度太短,则可能出现数据包丢失的情况。可以适当增加该参数以提高网络吞吐量。
3 netipv4tcp_max_syn_backlog:此参数指了SYN队列的最大长度。如果队列长度太短,则可能无法处理所有的TCP连接请求。可以适当增加该参数以应对高负载情况。
4 netipv4tcp_fin_timeout:此参数指定了TCP连接终止时等待FIN确认的超时时间。如果该值设置太小,则可能导致服务中断;如果设置得太大,则可能导致资源浪费。可以根据实际情况适当调整该参数。
5 netipv4tcp_keepalive_time:此参数指定了TCP keepalive机制的启动时间。通过启用keepalive机制,可以在长时间空时检测到连接状态,并且避免连接空闲过久而被关闭。可以根据业务需要适当调整该参数。
6 netipv4tcp_tw_reuse:此参数指定是否开启TCP连接重用。启用该选项可以将处于TIME_WAIT状态的套接字重新分配给新连接,从而节省网络资源。但同时也可能导致一些安全问题。建议在生产环境下谨慎使用。
7 netipv4tcp_window_scaling:此参数指定了是否启用TCP窗口缩放。启用该选项可以在延迟的网络中提高吞吐量。议在高延迟的情况下启用该选项。
以上是一些常用的CentOS内核参数,可以通过修改/sys/文件系统中的相应文件来修改这些参数。为避免对系统造成不可逆的损害,请务必备份原有配置并在修改前详细了解参数的含义。
PHP 的 time() 函数返回的结果是 Unix 时间戳,值的单位是秒;
Java 中 SystemcurrentTimeMillis() 返回的结果,值的单位是毫秒。
那么很容易就知道,除以 1000 就行了嘛:
int seconds = SystemcurrentTimeMillis() / 1000;查看原文
图中黑色那条就是因为定时器超时仍没有收到 ACK,所以引起了发送方超时重传。实际上 TCP 有两个阈值来决定如何重传同一个报文段:一是愿意重传的次数 R1、二是应该放弃当前连接的时机 R2。R1 和 R2 的值应分别至少为 3 次和 100 秒,如果超过任何一个但还没能重传成功,会放弃该连接。当然这两个值是可以设置的,在不同系统里默认值也不同。
那么如何设定一个合适的超时的值呢?假设 TCP 工作在静态环境中,这很容易,但真实网络环境不断变化,需要根据当前状态来设定合适的值。
RTO(retransmission timeout)一般是根据RTT(round trip time)也就是往返时间来设置的。若 RTO 小于 RTT,则会造成很多不必要的重传;若 RTO 远大于 RTT,则会降低整体网络利用率,RTO 是保证 TCP 性能的关键。并且不同连接的RTT不相同,同一个连接不同时间的 RTT 也不相同,所以 RTO 的设置一直都是研究热点。
所以凭我们的直觉,RTO 应该比 RTT 稍大:
RTO=RTT+Δt
那么,RTT 怎么算呢:
SRTT=(1−α)×SRTT+α×RTTnew
SRTT(smooth RTT),RTTnew 是新测量的值。如上,为了防止 RTT 抖动太大,给了一个权值 a ,也叫平滑因子。a 的值建议在 10%~20%。举个例子,当前 RTTs=200ms,RTTs=200ms,最新一次测量的 RTT=800ms,RTT=800ms,那么更新后的 RTTs=200×0875+800×0125=275ms,RTTs=200×0875+800×0125=275ms
Δt 如何得到呢?RFC 2988 规定:
RTO=SRTT+4×RTTD
因此,按照上面的定义, Δt=4×RTTD 而 RTTD 计算公式如下:
RTTD=(1−β)×RTTD+β×|SRTT−RTTnew|
实际上,RTTD 就是一个均值偏差,它就相当于某个样本到其总体平均值的距离。这就好比你的成绩与你班级平均成绩差了多少。RFC 推荐 β=025。
根据前面的公式,我们可以得到 RTO。一旦超过 RTO 还没收到 ACK,就会引起发送方重传。但如果重传后还是没有在 RTO 时间内收到 ACK,这时候会认为是网络拥堵,会引发 TCP 拥塞控制行为,使 RTO 翻倍。则第 n 次重传的 RTOn 值为:
RTOn=2^(n−1)×RTO1
下图是一个例子:
如上,在时间为022531时开始第一次重传,此后重传时间开始指数增大,(在尝试了8次后停下,说明其 R2 的值可能为8)。
前面说了 RTO 的公式,它和 RTT 有关,那么每一次的 RTT 是如何得到的呢?在之前 TCP 连接管理的时候讲过,TCP有一个 TSOPT (timestamp option) 选项,它包含两个时间戳值。它允许发送者在报文中带上一个32比特的时间戳值(TSV),然后接收方 将收到的值原封不动的填入 ACK 报文段中 TSOPT 选项的第二部分,时间戳回显字段(TSER)。发送方收到 ACK 以后,将当前时间戳减去 TSOPT 选项的 TSER 就可得到精确的RTT值。
还有另一个重要的细节,如果测量 RTT 的样本出现了超时重传,但是我们收到了 ACK 时无法分辨是对哪一次的确认,这时候 RTT 的值可能是不正确的。
因此,Karn 算法规定: 此时不更新 RTTnew 的值 。并且如果发生再次重传,则采用退避后的 RTO 的值,直到发送成功,退避指数重新设定为 1 。
假设有三个数据包依次发送,1号和3号先到达,2号数据包由于网络因素最后到达。接收方收到3号时,会发送一个1号的冗余 ACK,然后2号到达,此时会发送一个3号的累积 ACK 表明这三个到达。在这个例子里,3号 ACK 并没有立即返回,发送方收到3号的 ACK 后,根据其 TSER 计算此时的 RTT,就会导致发送方过高的估计 RTT,降低重传积极性,使得 RTO 相应增大,当然这在失序时是有好处的,因为过分积极会导致大量的伪重传。
如下图,在发送第四个 ACK 后出现延迟高峰,导致发送方在 RTO 时间内没有收到 5 ~ 8 的 ACK,于是发生重传,然后之前的 ACK 到达,于是又依次发送 6 ~ 8,就导致了不必要的重传。可以用 Eifel 算法来解决(略)。
<img src=">
首先,我们需要明确, 只有主动断开的那一方才会进入 TIME_WAIT 状态 ,且会在那个状态持续 2 个 MSL(Max Segment Lifetime)。
为了讲清楚 TIME_WAIT,需要先介绍一下 MSL 的概念。
MSL(报文最大生存时间)是 TCP 报文在网络中的最大生存时间。这个值与 IP 报文头的 TTL 字段有密切的关系。
IP 报文头中有一个 8 位的存活时间字段(Time to live, TTL)如下图。 这个存活时间存储的不是具体的时间,而是一个 IP 报文最大可经过的路由数,每经过一个路由器,TTL 减 1,当 TTL 减到 0 时这个 IP 报文会被丢弃。
TTL 经过路由器不断减小的过程如下图所示,假设初始的 TTL 为 12,经过下一个路由器 R1 以后 TTL 变为 11,后面每经过一个路由器以后 TTL 减 1
从上面可以看到 TTL 说的是「跳数」限制而不是「时间」限制,尽管如此我们依然假设 最大跳数的报文在网络中存活的时间不可能超过 MSL 秒 。
Linux 的套接字实现假设 MSL 为 30 秒,因此在 Linux 机器上 TIME_WAIT 状态将持续 60秒。
要构造一个 TIME_WAIT 非常简单,只需要建立一个 TCP 连接,然后断开某一方连接,主动断开的那一方就会进入 TIME_WAIT 状态,我们用 Linux 上开箱即用的 nc 命令来构造一个。
过程如下图:
在机器 c2 上用nc -l 8888启动一个 TCP 服务器
在机器 c1 上用 nc c2 8888 创建一条 TCP 连接
在机器 c1 上用 Ctrl+C 停止 nc 命令,随后在用netstat -atnp | grep 8888查看连接状态。
第一个原因是:数据报文可能在发送途中延迟但最终会到达,因此要等老的“迷路”的重复报文段在网络中过期失效,这样可以避免用相同源端口和目标端口创建新连接时收到旧连接姗姗来迟的数据包,造成数据错乱。
比如下面的例子
假设客户端 10211552 的 61594 端口与服务端 102115510 的 8080 端口一开始建立了一个 TCP 连接。
假如客户端发送完 FIN 包以后不等待直接进入 CLOSED 状态,老连接 SEQ=3 的包因为网络的延迟。过了一段时间 相同 的 IP 和端口号又新建了另一条连接,这样 TCP 连接的四元组就完全一样了。
恰好 SEQ 因为回绕等原因 也正好相同,那么 SEQ=3 的包就无法知道到底是旧连接的包还是新连接的包了,造成新连接数据的混乱。
TIME_WAIT 等待时间是 2 个 MSL,已经足够让一个方向上的包最多存活 MSL 秒就被丢弃,保证了在创建新的 TCP 连接以后,老连接姗姗来迟的包已经在网络中被丢弃消逝,不会干扰新的连接。
第二个原因是确保可靠实现 TCP 全双工终止连接。
关闭连接的四次挥手中,最终的 ACK 由主动关闭方发出,如果这个 ACK 丢失,对端(被动关闭方)将重发 FIN,如果主动关闭方不维持 TIME_WAIT 直接进入 CLOSED 状态,则无法重传 ACK,被动关闭方因此不能及时可靠释放。
如果四次挥手的第 4 步中客户端发送了给服务端的确认 ACK 报文以后不进入 TIME_WAIT 状态,直接进入 CLOSED状态,然后重用端口建立新连接会发生什么呢?
如下图所示
主动关闭方如果马上进入 CLOSED 状态,被动关闭方这个时候还处于LAST-ACK状态,主动关闭方认为连接已经释放,端口可以重用了, 如果使用相同的端口三次握手发送 SYN 包,会被处于 LAST-ACK状态状态的被动关闭方返回一个 RST,三次握手失败。
为什么时间是两个 MSL?
1 个 MSL 确保四次挥手中主动关闭方最后的 ACK 报文最终能达到对端
1 个 MSL 确保对端没有收到 ACK 重传的 FIN 报文可以到达
2MS = 去向 ACK 消息最大存活时间(MSL) + 来向 FIN 消息的最大存活时间(MSL)
在一个非常繁忙的服务器上,如果有大量 TIME_WAIT 状态的连接会怎么样呢?
连接表无法复用
socket 结构体内存占用
连接表无法复用 因为处于 TIME_WAIT 的连接会存活 2MSL(60s),意味着相同的TCP 连接四元组(源端口、源 ip、目标端口、目标 ip)在一分钟之内都没有办法复用,通俗一点来讲就是“占着茅坑不拉屎”。
假设主动断开的一方是客户端,对于 web 服务器而言,目标地址、目标端口都是固定值(比如本机 ip + 80 端口),客户端的 IP 也是固定的,那么能变化的就只有端口了,在一台 Linux 机器上,端口最多是 65535 个( 2 个字节)。
如果客户端与服务器通信全部使用短连接,不停的创建连接,接着关闭连接,客户端机器会造成大量的 TCP 连接进入 TIME_WAIT 状态。
可以来写一个简单的 shell 脚本来测试一下,使用 nc 命令连接 redis 发送 ping 命令以后断开连接。
如果在 60s 内有超过 65535 次 redis 短连接 *** 作,就会出现端口不够用的情况,这也是使用 连接池 的一个重要原因。
针对 TIME_WAIT 持续时间过长的问题,Linux 新增了几个相关的选项,netipv4tcp_tw_reuse 和 netipv4tcp_tw_recycle。
下面我们来说明一下这两个参数的用意。 这两个参数都依赖于 TCP 头部的扩展选项:timestamp
TCP 头部时间戳选项(TCP Timestamps Option,TSopt)
除了我们之前介绍的 MSS、Window Scale 还有以一个非常重要的选项:时间戳(TCP Timestamps Option,TSopt)
它由四部分构成:类别(kind)、长度(Length)、发送方时间戳(TS value)、回显时间戳(TS Echo Reply)。
时间戳选项类别(kind)的值等于 8,用来与其它类型的选项区分。长度(length)等于 10。两个时间戳相关的选项都是 4 字节。
如下图所示:
是否使用时间戳选项是在三次握手里面的 SYN 报文里面确定的。
下面的包是 curl githubcom 抓包得到的结果:
发送方发送数据时,将一个发送时间戳 1734581141 放在发送方时间戳 TSval 中
接收方收到数据包以后,将收到的时间戳 1734581141 原封不动的返回给发送方,放在 TSecr 字段中,同时把自己的时间戳 3303928779 放在 TSval 中
后面的包以此类推
有几个需要说明的点:
1 时间戳是一个单调递增的值,与我们所知的 epoch 时间戳不是一回事。 这个选项不要求两台主机进行时钟同步
2 timestamps 是一个双向的选项,如果只要有一方不开启,双方都将停用 timestamps。
比如下面是curl >>
1、要将netipv4tcp_timestamps的值设置为0,请运行sysctl-wnetipv4tcp_timestamps=0命令。
2、在默认sysctlconf文件中添加netipv4tcp_timestamps=0值。
以上就是关于TCP协议总结全部的内容,包括:TCP协议总结、centos内核参数网速、请问Java怎么获得当前时间戳,要int型的不要long的!等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)