我们之前说的UDP,基本包含了传输层所需的字段,但是TCP协议比起UDP协议,在其中加了一些数据结构来保证两端的连接性
我们首先看一下TCP的包头格式
从包头看,比UDP要复杂的多
我们的源端口和目标端口是必不可少的,这一点和UDP一样,如果没有这两个端口,数据就不知道该发给谁,然后是多了一个序号的情况,这个序号是给包进行编号用的,为了解决乱序的问题,不编好号怎么知道谁先来后到呢?然后就是确认序号,发出去的包邮确认,不然怎么知道对方有没有收到呢?没有收到就要重新发送,直到送达,这个也很好理解
然后就是一些状态位,比如SYN是发起连接,ACK是回复,RST是重连,FIN是结束连接,因为网络世界内部虽然是未知的,但是我们可以用一些标识来表明我们连接的状况
然后是窗口大小,TCP要做流量控制,这就需要通信的双方各自声明一个窗口,标识自己的处理能力,别发送太快,也不要发送太慢
总结下来,就是
顺序问题,稳重不乱
丢包问题,承诺靠谱
连接维护,有始有终
流量控制,把握分寸
拥塞控制,知进知退
TCP的三次握手
在握手的时候,我们必须要建立一个连接,我们首先看一下连接维护的问题’
TCP的连接建立,常被称为三次握手
A:你好B,我是A
B:你好A,我是B\
A:你好B
对应在网络环境下,就是 请求->应答->应答之应答,
至于为什么是三次,而不是两次,按理说,两个人打招呼,一来一回够了啊,而且,为了靠谱,为什么不是四次?
对于这个问题,我们这样解释,
我们假设一条道路的情况不稳定,那么A发起了一个连接,在发了第一个请求之后鸟无音讯,可能会出现很多情况,比如丢失了,比如超时了,比如B虽然收到了,但是不想连接
A不能确认结果,于是多次尝试,直到到达重试次数,如果有一个到达了B,B也可以建立了连接,
会发送对应的应答包给A,对于B来说,这个应答包是一入网络深似海,也不知道A收到了吗,这时候B肯定不能认为连接就建立好了,而且,如果这样就认为建立好了,那么可能A和B建立了连接,做了简单通信,结束了连接,那么A建立连接的时候,请求包发送了好几次,有的请求包绕了一大圈回来了,那么B也认为是个正常请求,就建立连接了话,那么可能B就是个单相思
于是B发出了这个应答包,并且等待应答之应答,B的应答可能发送多次,但是,只要有一次到达了A,那么A会认为连接建立了,并且发送应答之应答给B,那么B收到了应答之应答,才能认为真正的建立了,虽然说可以无数次的发出消息和返回消息,但是只要双方的消息有来有回,就基本可以了,多余的来回发包也不能保证就一定可靠了
然后两方建立了连接,A会马上发数据出去,一旦发送了数据,双方就好办了,因为这样的话,就可以直接认为两方的连接就能建立了,或者B挂了,A发送了数据报错了,这样A也知道B挂了
如果A迟迟不给B发送数据,连接自然会关闭,为了防止关闭,A会发送keepalive的消息,来表明自己是活着的
序号
三次握手除了双方建立了连接外,还需要沟通一件事,就是TCP的序号问题,A要告诉B,我的包从哪个序号开始,B也会告诉A,收到的从哪个开始
这个序号不能从1开始,因为可能出现的冲突问题,比如A连上了B,发送了 1 2 3三个包,后来发送3的时候,中间丢了,或者绕路超时了,于是重新发送,A又恰好掉线了,上线后的A只想发送 1 和 2,但是没想到3又回来了,发送给了B,B自然认为,这是下一个包,导致第二次的发送的包出现冗余了
因此,每个连接都要有不同的序号,这个序号的起始序号是随着时间变化的,看做一个32位的计数器,每4ms加1,如果如果计算下,重复连接,也需要4个小时,到时候,绕路的包早就超时了
整体的建立流程
1.双方处于CLOSED状态,服务器端监听着某个端口,处于LISTEN
2.客户端发起连接SYN,处于SYN_SENT,服务端收到了连接诶,返回SUN,并且发送一个ACK的SYN,处于了SYN-RCYD状态
3.客户端收到了SYN和ACK之后,发送了ACK的ACK,之后处于ESTABLISHED,服务端收到了ACK的ACK之后,处于ESTABLISHED状态
TCP的四次挥手
连接断开的过程,被称为四次挥手
假如,我玩过了,不想玩了,想拍拍屁股走人了
那么这就需要一个和平分手的过程
A:我不想玩了
B:哦,你不想玩了,我知道了
这时候是A不想玩了,那么A可能不再发送数据,但是B能不能直接关闭?
不能,因为B并没有处理完数据,还能再发送数据的,这种状态被称为半关闭的状态
然后A可以选择不再接受数据,也可以再接受最后一段数据,等待B也主动关闭
B发了一句:我也不想玩了
A:好的,拜拜
这个关闭就关闭了,有没有异常的情况呢?
假如一开始,A发送了不玩了,B说了知道了,这个流程可以的,但是如果A没有收到回复,就会多次的发送,但是如果A发送完:不玩了,直接跑路怎么办,因为B没有结束,而且B即使结束了,发起了结束请求,也不知道A的情况,另一种情况是,A说完 不玩了, B直接跑路,也是有情况的,因为A不知道B是有事情处理,还是过一会结束,那么就必须按照时序转换来发送
整体流程如下
1.A说了我不玩了,就自己进入了FIN_WAIT_1,B收到了A发来的FIN消息,就进入了CLOSED_WAIT的状态,并发送一个ACK的包
2.A收到了回复包,进入了FIN_WAIT_2的状态,这个状态如果B直接跑路,A将永远卡死在这里,这时候系统可以进行关闭,比如Linux可以设置tcp_fin_timeout的参数,超时时间
3.如果B没有跑路,处理完了工作,发送了B也不玩了,转为了LAST_ACK状态
4.B发送的FIN包到达A, A发送 知道B也不玩了,从FIN_WAIT_2的状态结束了
4-1:如果A已经在跑路了,B从CLOSED_WAIT发送了B也不玩了,等待响应的时候,会一直收到不到这个FIN的ACK
所以为了避免这种情况,需要A在FIN_WAIT_2的结束后等待一段时间,等待B的B也不玩了的消息到达,并返回一个ACK包,这个等待的时候,要保证,如果发出的ACK,B没有收到,还能再重发的
等待的时间一般设为2MSL,就是报文最大生存时间,这个是一个时间上的概念,和TTL不一样,TTL是没经过一个路由器就会减一,而MSL规定是2分钟
整体的状态图如下
上面中,阿拉伯的序号,是连接过程
大写中文数字,是断开过程,加粗的实线是客户端A的状态变化,加粗的虚线是服务器端B的变化
本章小结:
TCP的头很复杂,要关注五个问题,顺序问题,丢包问题,连接维护,流量控制,拥塞控制
连接的建立经过了3次握手,断开的时候就是4次挥手
思考问题
1.TCP的连接有这么多的状态,如何知道某个连接的状态
2.为了维护连接的状态,还有其他的数据结构来处理其他的四个问题,知道是什么吗?
1.使用netstat或者lsof命令 grep看一下
或者是 establish listen closew_wait
2.为何有大量的连接处于了TIME_WAIT的状态呢?
对于一次数据服务,查看发现异常是connection reset,netstat看了下ip 发现都是timewait,连接不多,但是都是超时
根据上图来看,处于TIMEWAIT状态,说明双方之前建立成功过连接,并且发送了最后的ACK,处于这个状态,而且是客户端(连接发起者)处于这个状态
如果存在大量的TIMEWAIT,说明短连接太多,不断的创建并释放连接,导致连接数量满了,导致无法建立新的连接,
解决可以为
打开tcp_tw_recycle和tcp_timestamps选项
打开tcp_tw_reuse和tcp_timestamps选项
程序中使用SO_LINGER,强制rst关闭
客户端收到Connection Reset,往往收到了TCP的RST消息,RST消息一般在下面的情况下发送
试图连接一个未被监听的服务端
对方处于TIMEWAIT的状态,或者连接已经关闭,处于CLOSED状态,或者重新监听seq num不匹配
发起连接时候超时,重传超时,keepalive超时
在程序使用了SO_LINGER,关闭连接的时候放弃缓存中数据,给对方发送RST
起始序列号如何计算的,对于TCP的中包的序列号
我们做一个基于tcp的物联网应用,tcp会保证重传的基础上,会加上业务层面上的重传机制吗,如果需要,业务多久重传一次
1.TCP的重新传送是网络层面和粒度的,业务层面是否重传,还是看具体的业务,比如发送失败,可能对端在重启,那么在业务层面,可以重传,考虑时间就可以了
1.序号的起始序号随着时间变化,重复需要四个多小时,这个重复时间是如何计算的呢?
没4ms加1,如果有两个TCP连接就都在这个4ms里面建立,是不是就有两个相同的起始序列号
序号随着时间变化,主要是为了区分一个连接发送序号混淆的问题,两个连接的话,端口或者IP肯定不一样了
报文最大的生存时间MSL和IP协议的路由条数TTL有什么关系?报文当前耗时如何计算,TCP有存储相关时间吗?
TTL是指的经过的路由跳数,并非实际的时间单位
更加具体点的说法是
其实ISN是基于时间的,每过4ms就加一,转一圈就要4.55小时
TCP初始化序列号不会为一个固定值的
所以初始化的序列号算法为
ISN=M+F(localhost,localport,remotehost,remoteport)
M是计时器,计时器每隔4MS加1,而F是一个HASH算法,根据源IP,目的IP,源端口,目的端口生成和一个随机数值,保证HASH算法不被外部轻易的推算出