前言
整理下tcp
相关笔记。
TCP三次握手
tcp
三次握手过程大致过为:
clinet -> server : SYN=1, seq=0
server -> client : SYN=1 ACK=1, seq=0
client -> server : seq=1, ACK=1
握手过程抓包如下。其中Len=0,有效数据负荷为0.
握手三次的原因
先看下两次握手缺陷情况:
先假设 tcp
连接只有两次握手,过程如下。假设,A发第一次的 SYN(1)
在网络中阻塞了,然后A再次发送超时 SYN(2)
,此时B马上接收到了 SYN(2)
,然后完成剩下的连接并交换完数据。这个时候第一次发送的 SYN(1)
又送到了B,B认为请求合法,返回 ack
之后就认为自方连接完成,就会一直等待A发数据。这就是两次握手的弊端,B无法确认SYN
是否有效。
对于三次握手而言,B会返回SYN
的ACK
给A,由A会检查该SYN
请求是否合法。合法,发送给ack=1,seq=1
给B,B接收到之后,连接完成。否则,A直接扔掉该 ACK
,B等待超时闭关该连接。
所以采用三次握手的原因是,防止失效的SYN请求传给服务器,以发生错误。
A -> B : SYN=1 阻塞
A -> B : SYN=1 该条SYN比上SYN先到达
B -> A : SYN=1 ACK=1
SYN FLOOD
通过伪造源IP
,向服务器发送大量的SYN
请求,但是服务器的listen
队列有限,过多的恶意请求过来的时候,listen
队列无法装入更多的连接而拒绝正常的请求连接。
TCP四次挥手
- (1)A向B发送一个 FIN , A本身进入FIN_WAIT_1状态。
- (2)B接收到FIN, 返回一个ACK,并进入CLOSE_WAIT状态。
- (3)A接收到ACK,进入FIN_WAIT_2状态。
- (4)B应用层调用close(),发送FIN.
- (5)A接收FIN,并返回ACK。A进入time_wait,等待 2* MSL时长。
- (6)B接收到ACK之后关闭连接
- (7) A等待time_wait过去,关闭连接。
过程大概为:
A -> B : FIN , A enter FIN_WAIT_1
B -> A : ACK of FIN , B enter ClOSE_WAIT, A receive ack and enter FIN_WAIT_2
B -> A : FIN, A receive FIN, enter time_wait
A -> B : ACK of FIN
CLOSE_WAIT
等应用层接收发送完成数据之后调用close(),才结束CLOSE_WAIT,发送FIN给对方。如果CLOSE_WAIT存在较久的话,考虑应用进程没有close socket。
TIME_WAIT
time_wait
是这个tcp
连接在2MSL
等待期间不能被使用。一个tcp
连接指的是(客户的IP地址和端口号,服务器的 IP地址和端口号)。
time_wait作用在:
(1)让tcp
再次发送最后的ack
(另一端超时并重发最后的FIN
)
(2)等待时长2MSL
让前所所有重复数据包失效在网络中,避免时间太短出现新连接收到之前的数据包出现错误。
但是仍然可以使用SO_REUSERADDR
socket选项,让一个新连接请求到处于time_wait
阶段的连接。
TCP KEEPALIVE
` TCP keepalive 并不是TCP协议的一部分,但是提供
tcp_keepalive API接口设置。
保活更多时候是为服务器提供的,便于服务器应用程序需要知道客户端是否崩溃或者发生重启。
如果一个
tcp连接建立完成之后,客户端直接断电,便会在服务端留下一个半开放的连接,而服务器会一直等待客户端数据。保活功能就是能尽早在服务端检测这样的半开放连接。
tcp默认保活策略:
tcp`连接如果两个小时内没有任何动作,则服务器向客户端发送一个探测报文:
- 客户端给
tcp
连接正常,则该tcp
连接回应正常。 - 客户端没有回复响应。服务器每隔75秒,发送共10个这样的探测报文,如果客户端一直没有回应则服务器关闭套接字。
LINUX内核支持对KEEPALIVE
提供支持:
/proc/sys/net/ipv4/tcp_keepalive_time
(tcp
闲置时长)/proc/sys/net/ipv4/tcp_keepalive_intvl
(探测间隔)/proc/sys/net/ipv4/tcp_keepalive_probes
(最多探测数量) 一般的应用层程序为了知道tcp
连接情况以采取相应策略,在上层间隔性发送心跳包。
Nagle算法和Ack延时
ack
延时:tcp
在接收到数据时并不立即发送ack
;相反,推迟发送,将ack
和要发送给对方的数据一起发送出去。绝大多数采用200ms
延时,tcp
以最大200ms
延时等待是否有数据一起发送。
nagle
算法定义:一个tcp
连接上任何时间最多只能有一个未被确认的小分组。通过合并小包,以减少微小(报文大小)分组数据传输,则会发送更少的分组。
对于ack
延时,是接收方并不发立即发送确认;对于,nagel算法,一次只有一个未被确认的分组,意味着,它等待确认才发送下一个分组。如此,他们可能出现互相等待,直到超时。
socket API
选项上,使用TCP_NODELAY
来禁止tcp
使用nagle
算法发送,使用TCP_QUICKACK
来禁止ack
延时。对于延时高,丢包少的网络环境采用,nagle
算法禁用ack
延时较好。对于交互性要求高的场景,可以选择禁用ack
延时,不过会增加网络数据包数量。
TCP滑动窗口
滑动窗口维护了四个部分的窗口:
1、已发送并确认的部分。
2、发送,但未被发送确认的部分。
3、能够发送但并未发送的部分。
4、不能发送的窗口。
1)当收到了数据确认,窗口左边界向右边界靠近。
2)当窗口右边界向右移动,允许发送更多数据。
滑动窗口优点:在窗口2、3内可以发送多个分组、同时确认多个分组,而非一个个发送并一个个等待确认。
滑动窗口的主要作用在:1)为
tcp
提供流控机制。2)为tcp
提供可靠性。确认传输成功数据包,超时丢失数据段。
滑动窗口大小
滑动窗口的大小是接收方的缓存的大小,是动态变化的。
PUSH标志
TCP接收到待PUSH标志的报文段时,它需要立即将这些数据递交给服务器。
慢启动算法
通过观察新分组进入网络中的速率与另一端确认的返回确认速率而工作。
TCP为慢启动维护了一个慢启动窗口,记为 cwnd
。
最初发送一个报文段,然后在发送下一个报文段之前必须接收它的确认。当报文段被确认后,就可以发送两个报文段。窗口发小呈指数增长。
拥塞避免
慢启动算法是一个在连接上发起数据流的方法,但是有时候会达到路由器的极限。拥塞控制就避免一次在网络中注入太多的数据,达到网络极限。拥塞发生时,我们希望降低分组进入网络的传输速率。
拥塞避免算法和慢启动算法对每一个连接维护两个变量:拥塞控制窗口cwnd
和慢启动门限ssthres
。
慢启动时,窗口变化为:1,2,4……;拥塞避免,窗口变化:接收一个确认时,cwnd
增加1/cwnd
。
算法过程:
- 1)给定一个连接,初始化
cwn
为1个报文段,ssthres
为65535. - 2)
tcp
的输出不能超过cwnd
和接收方通知的窗口大小,拥塞避免是发送方使用的流量控制,而通知窗口是接收使用的流量控制。 - 3)当拥塞发生时,
ssthresh
被设置当前窗口大小的一半。如果是超时引起了拥塞,则cwnd
被设置为一个报文段(慢启动)。 - 4)当新的数据被对方确认时,增加
cwdn
。如果,cwnd
小于或等于ssthresh
,则进行慢启动,否则进行拥塞避免。
快重传
发送方收到重复ack
,由于不知道一个重复的ack
是否一个丢失的报文段引起的,还是有于仅仅出现了几个报文重新排序,因此我们等待少量的重复的ack
到了。如果一连串的收到三个后三个以上的重复ack
,就非常可能是一个报文段丢失了。于是我们采用拥塞避免算法重传丢失的数据报文段,无需等待定时器的溢出。
参考
《tcp/ip详解第一卷》