TCP扫盲1

TCP 包头格式

 源端口号和目标端口号是不可少的,这一点和 UDP 是一样的。如果没有这两个端口号。数据就不 知道应该发给哪个应用。 

为什么要给包编号呢?当然是为了解决乱序的问题。不编好号怎么确认哪个应该先 来,哪个应该后到呢。编号是为了解决乱序问题。

还应该有的就是确认序号。发出去的包应该有确认,要不然我怎么知道对方有没有收到呢?如果没有收 到就应该重新发送,直到送达。这个可以解决不丢包的问题。

对于传输层的TCP是靠谱的,但是网络层的丢包问题就管不到了,但是会尽量确保可靠。

接下来有一些状态位。例如 SYN 是发起一个连接,ACK 是回复,RST 是重新连接,FIN 是结束连接 等。TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变 更。 

还有一个重要的就是窗口大小。TCP 要做流量控制,通信双方各声明一个窗口,标识自己当前能够的处 理能力,别发送的太快,撑死我,也别发的太慢,饿死我。

除了做流 量控制以外,TCP 还会做拥塞控制。

顺序问题 ,稳重不乱;

丢包问题,承诺靠谱;

连接维护,有始有终;

流量控制,把握分寸;

拥塞控制,知进知退。

TCP 的三次握手 

a:你好,我是a

b:你好,我是b

a:你好b

大部分情况下,A 和 B 建立了连接之后,A 会马上发送数据的,一旦 A 发送数据,则很多问题都得 到了解决。例如 A 发给 B 的应答丢了,当 A 后续发送的数据到达的时候,B 可以认为这个连接已经建 立,或者 B 压根就挂了,A 发送的数据,会报错,说 B 不可达,A 就知道 B 出事情了。 

你作为服务端 B 的程序设计者,对于 A 这种长时间不发包的客户端,可以主动关闭,从而空出资 源来给其他客户端使用。 

三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是TCP 包的序号的问题。

A 要告诉 B,我这面发起的包的序号起始是从哪个号开始的,B 同样也要告诉 A,B 发起的包的序号起始 是从哪个号开始的。为什么序号不能都从 1 开始呢?因为这样往往会出现冲突。 例如,A 连上 B 之后,发送了 1、2、3 三个包,但是发送 3 的时候,中间丢了,或者绕路了,于是重新 发送,后来 A 掉线了,重新连上 B 后,序号又从 1 开始,然后发送 2,但是压根没想发送 3,但是上次 绕路的那个 3 又回来了,发给了 B,B 自然认为,这就是下一个包,于是发生了错误。 

因而,每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的,可以看成一个 32 位的计数 器,每 4ms 加一,如果计算一下,如果到重复,需要 4 个多小时,那个绕路的包早就死翘翘了,因为我 们都知道 IP 包头里面有个 TTL,也即生存时间。 

一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态。然 后客户端主动发起连接 SYN,之后处于 SYN-SENT 状态。服务端收到发起的连接,返回 SYN,并且 ACK 客户端的 SYN,之后处于 SYN-RCVD 状态。客户端收到服务端发送的 SYN 和 ACK 之后,发送
ACK 的 ACK,之后处于 ESTABLISHED 状态,因为它一发一收成功了。服务端收到 ACK 的 ACK 之 后,处于 ESTABLISHED 状态,因为它也一发一收了。

TCP 四次挥手 

A:B 啊,我不想玩了。 

B:哦,你不想玩了啊,我知道了。 

此时A处于半关闭状态

B:A 啊,好吧,我也不玩了,拜拜。 

A:好的,拜拜。

断开的时候,我们可以看到,当 A 说“不玩了”,就进入 FIN_WAIT_1 的状态,B 收到“A 不玩”的消 息后,发送知道了,就进入 CLOSE_WAIT 的状态。 

A 收到“B 说知道了”,就进入 FIN_WAIT_2 的状态,如果这个时候 B 直接跑路,则 A 将永远在这个状 态。TCP 协议里面并没有对这个状态的处理,但是 Linux 有,可以调整 tcp_fin_timeout 这个参数,设 置一个超时时间。

如果 B 没有跑路,发送了“B 也不玩了”的请求到达 A 时,A 发送“知道 B 也不玩了”的 ACK 后,从 FIN_WAIT_2 状态结束,按说 A 可以跑路了,但是最后的这个 ACK 万一 B 收不到呢?则 B 会重新发一 个“B 不玩了”,这个时候 A 已经跑路了的话,B 就再也收不到 ACK 了,因而 TCP 协议要求 A 最后等 待一段时间 TIME_WAIT,这个时间要足够长,长到如果 B 没收到 ACK 的话,“B 说不玩了”会重发 的,A 会重新发一个 ACK 并且足够时间到达 B。 

A 直接跑路还有一个问题是,A 的端口就直接空出来了,但是 B 不知道,B 原来发过的很多包很可能还 在路上,如果 A 的端口被一个新的应用占用了,这个新的应用会收到上个连接中 B 发过来的包,虽然序 列号是重新生成的,但是这里要上一个双保险,防止产生混乱,因而也需要等足够长的时间,等到原来 B 发送的所有的包都死翘翘,再空出端口来。

等待的时间设为 2MSL,MSL是Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网 络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 域,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数 据报将被丢弃,同时发送 ICMP 报文通知源主机。协议规定 MSL 为 2 分钟,实际应用中常用的是 30 秒,1 分钟和 2 分钟等。

还有一个异常情况就是,B 超过了 2MSL 的时间,依然没有收到它发的 FIN 的 ACK,怎么办呢?按照 TCP 的原理,B 当然还会重发 FIN,这个时候 A 再收到这个包之后,A 就表示,我已经在这里等了这么 长时间了,已经仁至义尽了,之后的我就都不认了,于是就直接发送 RST,B 就知道 A 早就跑了。

TCP状态机

end

参考刘超老师《趣谈网络协议》

 
一个没有高级趣味的人。 email:hushui502@gmail.com
原文地址:https://www.cnblogs.com/CherryTab/p/12198474.html