1、tcp的本性
tcp是一个悲观者,生下来就不信任网络,任务会发生丢包等,所以要从算法层面来保证可靠性。
2、TCP 包头格式
tcp的包头格式比UDP要复杂的多。
1.源端口号和目标端口号是不可少的,这一点和 UDP 是一样的。如果没有这两个端口号。数据就不知道应该发给哪个应用。
2.包的序号:是为了解决乱序的问题。不编好号怎么确认包到达的前后顺序。
3.确认序号:发出去的包需要ack,不然我也不知道有没有收到,如果没有收到,就应该重发,直到收到为止。//这个可以解决不丢包的问题
4.状态位:SYN:是发起一个连接ACK 是回复RST 是重新连接FIN 是结束连接//TCP 是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更
注意:tcp虽然是靠谱的协议,但是网络环境不好的时候,对于IP层丢不丢包,它也没有办法,tcp唯一能做的就是不断地重试。
3、tcp的握手和技能
- tcp如何保证对于连接的信任呢,为什么需要握手呢,这就好比人与人之间不会第一次见面就信任对方吧,怎么也得握手几次吧。
- tcp做流量控制:通信双方各声明一个窗口,标识自己当前能够的处理能力,别发送的太快,也别发的太慢。
- tcp拥塞控制:对于网络是否拥堵,tcp是无能为力的,唯一能做的就是控制自己,也即控制发送的速度。
- tcp需要关注的点如下:
1.顺序问题 ,稳重不乱
2.丢包问题,承诺靠谱
3.连接维护,有始有终
4.流量控制,把握分寸
5.拥塞控制,知进知退
4、TCP 的三次握手
tcp连接的建立,又称3次握手,这是一个连接维护的问题,这样比较便于理解。
就是一个:“请求 -> 应答 -> 应答之应答”
A: 您好,我是A
B: 您好,我是B
A: 您好,B
为啥不是2次,4次?
1.假设网络环境不好,A发出了一个请求,包有可能会丢失或者请求超时了,于是A一直补发,直到B收到了请求,但是B收到的请求的时候,A可能不直到,所以A还会再发。如果B收到了请求:一是B不愿意建立练级,A会在一定时间后放弃二是B愿意建立连接,像A发送一个ack,但是B发出去的包也会像石沉大海一样,也不知道A有没有收到。
另外还有一种可能,就是之前A发送包出去绕了一圈回来了,B接收到了,同样认为这是一个正常的请求,于是给出了响应,这事听起来也是挺离谱的。
so,2次握手是不可靠的!2. 为啥不是4次?
不是4次不行,400次都行,只是这样会没完没了,理由如下:
B发出的消息可能会发送多次,A只要收到一次,就认为连接建立了,自己的请求有了响应,A发送一个应答的应答。
而B也在等这个消息,才能确认连接是否建立,只有等到了这个消息,对于B来说,这个消息是有始有终的。
对于应答的应答,也有可能会丢,会绕路,甚至B都宕机了,是不是需要B再这个基础之上再应答,如此的话,就是没完没了了。
只要双方的消息都有去有回,就基本可以了
1.大部分情况下,A 和 B 建立了连接之后,A 会马上发送数据的,这个时候很多问题就得到了解决。
//例如 A 发给 B 的应答丢了,当 A 后续发送的数据到达的时候,B 可以认为这个连接已经建立,或者 B 压根就挂了,A 发送的数据,会报错,说 B 不可达,A 就知道 B 出事情了。
2.如果A 比较坏,就是不发数据,建立连接后空着。我们在程序设计的时候,可以要求开启keepalive 机制,即使没有真实的数据包,也有探活包。
3.我们作为服务的提供者来说,如果A一直不发包,则可以主动关闭这个连接
5、包的序号问题
- 三次握手除了双方建立连接外,主要还是为了沟通一件事情,就是TCP 包的序号的问题。
- 每个连接都要有不同的序号。这个序号的起始序号是随着时间变化的。
- 序号,可以看成一个 32 位的计数器,每 4ms 加一。
- IP 包头里面有个 TTL,也即生存时间。
6、TCP 四次挥手
7、tcp是咋靠谱的?
1.tcp为了保证顺序性,每一个包都有一个 ID
2.在建立连接的时候,会商定起始的 ID 是什么,然后按照 ID 一个个发送。
3.为了保证不丢包,对于发送的包都要进行应答。
累计确认/累计应答:
应答也不是一个一个来的,而是会应答某个之前的 ID,表示都收到了,称为累计确认或者累计应答.
- 由上面可以猜到,这就必须得保存这些统计的结果。
- 为了记录所有发送的包和接收的包,TCP 也需要发送端和接收端分别都有缓存来保存这些记录。
- 发送端的缓存里是按照包的 ID 一个个排列(队列),根据处理的情况分成四个部分,如下所示:
1.第一部分:发送了并且已经确认的
2.第二部分:发送了并且尚未确认的
3.没有发送,但是已经等待发送的
4.没有发送,并且暂时还不会发送的
为什么要区分第三部分和第四部分呢?
这就是之前提到的流量控制,如果对方的处理能力弱,就等等再发。
Advertised window:
- 在 TCP 里,接收端会给发送端报一个窗口的大小,称Advertised window
- 窗口的大小应该等于上面的 第二部分 + 第三部分
就是已经发送的正在处理的部分,再加上等待发送的部分已经是接收端处理的上线了,再多,就吃不下了。
发送端的数据结构:
依照服务端的要求,发送端的数据结构如下:
LastByteAcked:第一部分和第二部分的分界线
LastByteSent:第二部分和第三部分的分界线
LastByteAcked + AdvertisedWindow:第三部分和第四部分的分界线
对于接收端来讲,它的缓存里记录的内容相对简单
第一部分:接受并且确认过的
第二部分:还没接收,但是马上就能接收的,即当前最大的处理能力
第三部分:还没接收,也没法接收的,即超负荷了
MaxRcvBuffer:最大缓存的量
LastByteRead 之后是已经接收了,但是还没被应用层读取的
NextByteExpected 是第一部分和第二部分的分界线
第二部分的窗口有多大呢?
1.NextByteExpected 和 LastByteRead 的差其实是还没被应用层读取的部分占用掉的 MaxRcvBuffer 的量,我们定义为 A
2.AdvertisedWindow 其实是 MaxRcvBuffer 减去 A,也就是:AdvertisedWindow=MaxRcvBuffer-((NextByteExpected-1)-LastByteRead)
//那第二部分和第三部分的分界线在哪里呢?
1.NextByteExpected 加 AdvertisedWindow 就是第二部分和第三部分的分界线,其实也就是 LastByteRead 加上 MaxRcvBuffer.
2.第二部分里面,由于接收到的包可能不是顺序的,会出现空挡,只有和第一部分连续的,可以马上进行回复,中间空着的部分需要等待,哪怕后面的已经来了
顺序和丢包问题:涉及了RTT,自适应重传算法等,此处不做过多解读,感兴趣可以Google一下