TCP协议

TCP协议段格式

TCP协议_第1张图片

16位端口号

都是其中的一个重要的组成部分,知道了端口号,才知道进一步这个数据报会交给哪个应用程序

4位首部长度

TCP报头是可变长度的,4bit表示的范围是0->15,此处的单位是4字节,15*4=60,才是真正的报头长度,也就是说TCP报头的最大长度是60字节,

选项

TCP报头的前20个字节,是固定的(TCP报头的最短长度是20个字节),通过选项部分来增大首部的长度,选项可以有,也可以没有,可以有一个,也可以有多个选项,需要使用首部长度,来确认,报头到哪里结束了,数据是从哪里开始的

保留位(6位)

现在虽然不用,先占个位置,后面可能需要,
想知道完整的TCP具体有哪些设定
TCP特点:
有连接,可靠传输,面向字节流,全双工
内核实现的可靠传输,写代码的时候,是感知不到的,成本也低了
可靠传输的实现机制:确认应答

32位序号与确认序号

TCP协议_第2张图片

1.为什么会出现先发后至?
网络上从A->B,中间的路径有很多,两个包,从A->B走的路线不一定相同,另外,每个节点(路由器/交换机)繁忙程度不一样
为了避免这种情况,我们可以采取对数据进行编号
由于TCP是面向字节流,不是按照"条"为单位的
上述图片中的确认应答返回的1001有以下两个含义:
1.1001序号之前的数据,已经收到了
2.接下来应该从1001开始发放数据
应答报文也是要和收到的数据的序号相关联的,但是不是相等
确认序号的数值,就是收到的最后一个字节的编号再加一
TCP协议_第3张图片
只要知道这一串字节的开始编号,以及数据的长度,每个字节的编号也就知道了,只需要再TCP报头中,把这一串字节的第一个字节的编号,表示出来,再结合报文的长度,此时每个字节的编号就确定了
如何区分普通报文和确认应答报文?

ACK(acknowledge)

TCP协议_第4张图片

为0表示这是一个普通的报文,此时只有32位序号是有效的
ACK为1,表示这是一个应答报文,这个报文的序号和确认序号都是有效的
需要注意的是,确认报文的序号和普通报文的序号是没有关联的,是各自主机发送数据的编号
核心就是一句话:确认应答,就是TCP保证可靠性传输的核心机制
超时重传,也是TCP可靠性机制的有效补充

丢包

这是在网络上很可能出现的情况,发一个数据,然后丢了
在结构复杂的情况下,传输的数据也很不稳定,时多时少,如果设备太过于繁忙,后面来的数据等的太久了,就可能会将数据给丢弃掉,网络负载越高,越繁忙,就越容易丢包
这时,我们就应该采取一个新的手段:重传
,当我们没收到数据的时候,在经过一定时间的等待过后,还是没有收到应答,那么我们就要尝试重传,这个操作也称为"超时重传"
下图是消息本身丢包了
TCP协议_第5张图片
下图是应答报文丢了
TCP协议_第6张图片
以上两种情况,发送方是无法区别的,也就都需要重传,而第二中情况下,重传会导致同一条信息发送两次
因此,我们需要这样设计:
接收方收到数据之后,需要对数据进行去重,把重复的数据丢弃掉,保证应用程序,调用input stream.read的时候,读到的不会出现重复
如何高效的判定,当前收到的数据不是重复的呢:
直接使用tcp的需要来作为判定依据
tcp会在内核中,给每个socket对象安排一个内存空间,相当于一个队列,也被称为"接收缓冲区"收到的数据,都会被放到接收缓冲区里,并且按照序号排列好顺序
有序排列,也是非常有意义的,后发先至的问题也得到了解决
等待超时时间,是等多久呢?
咱们不必记忆数值,(数值是可配置的),重点理解策略
超时时间不是一个固定值,会随着超时轮次的增加,而进一步增加
如果几次重传都失败了,说明当前网络本身丢包的概率已经极高了,网络应该是遇到了重大的故障,此时,重传就属于是浪费时间了,拉长一些时间间隔,是为了在给网络恢复留有一个时间上的余地

RST

重传达到一定的次数,就会放弃重传,此时就会尝试"重置"tcp连接了(tcp复位报文rst)
如果网络已经出现严重故障,复位操作/重置操作,也无法成功,最终就只能放弃连接,只有把自己对端的信息给删除掉了
TCP协议_第7张图片

连接管理

建立连接SYN(三次握手)

这个操作也是一种保证可靠性的机制
发一个打招呼的数据,这个数据并不会携带业务信息
A和B完成建立连接的过程,就需要三次这样的打招呼的数据交互
TCP协议_第8张图片
这样的四次交互过后,连接看就算建立好了,此时双方就保存了对端的信息了
看起来是四次,中间这两次,能够合并成一次
TCP协议_第9张图片

为什么要合并呢?
封装和分用,降低资源和成本,提高效率
TCP协议_第10张图片
ACK是应答报文
SYN是申请建立连接的请求"同步报文段",如果这一位为1,就是客户端要尝试和服务器建立连接(谁申请连接,谁就是客户端)
TCP协议_第11张图片

三次握手,第一次SYN一定是客户端发起的,客户端是主动的一方
当我们new一个socket对象的时候,也就是客户端在与对应端口号建立连接的时候,三次握手就已经完成了,三次握手是内核完成的工作,应用程序无法干涉,只能控制什么时候进行三次握手,同时,服务器这边针对三次握手的配合是不需要涉及到任何的应用层代码的,只要你这个进程是绑定了对应TCP端口,就可以在内核中自动的配合完成三次握手,无论你服务器代码是怎么写的
服务器这里的accpet的作用是在客户端和服务器连接之后,就可以从连接队列中取出首元素,进一步的获取到其中的socket对象,来和对端通信
三次握手为啥是三次,因为三次恰好能验证双方的发送和接收是否是正常的,并且把这样的信息同步给双方
三次握手,还能起到"消息协商"的效果,通信的时候涉及到一些参数,需要双方保持一致的,通过协商来确定参数具体是多少
TCP通信过程中,有很多消息是需要进行协商的,比如双方的序号从几开始(一般不会从0/1开始)
这样做的主要目的就是,保证两次连接,信息的序号能够有较大的差异,从而好去判定某个消息是否是属于这个来连接的

断开连接FIN(四次挥手)

如果不需要连接,就得及时的释放上述存储空间
不一定是客户端主动发起的,也可能是服务器主动发起的
FIN:结束报文段的该标志位为1
TCP协议_第12张图片

TCP协议_第13张图片
经过上述四个步骤过后,连接就彻底不存在了,双方就已经把各自的内存空间给释放掉了
为什么这里中间的两次不能被合并呢?
有时候能合并,有时候不能合并,因为这里的FIN的触发是应用程序的代码来控制的(socket.close),或者进程结束,就会触发FIN,相比之下,ACK是内核控制的,只要收到FIN 就会立即返回ACK
如果服务器,始终不进行close,会怎么样,客户端的连接就始终不关闭吗?
此时,服务器的TCP状态,就处于CLOSE_WAIT状态
站在服务器的角度,虽然这里的连接没有关闭,但是这个连接其实已经不能正常使用了
针对当前socket进行读操作,如果数据还没读完(缓冲区还没读完),可以继续读完该数据
如果当前已经读完一个数据,读取下一个数据的时候,就会读到EOF(对于字节流来说,返回-1,如果是scanner,hasNext就会为false)
如果针对当前socket进行写操作,就会触发报错或异常
更极端的情况,比如代码写出bug,close忘记写了,此时站在客户端的角度,迟迟收不到对方的FIN,也会进行等待,如果一直等不到,就会单方面放弃连接(客户端就会直接把保存的对端的信息给删了,释放掉)
TCP协议_第14张图片

如果通信中,又出现丢包了怎么处理?
这里也是涉及到到超时重传的,三次握手,四次挥手,也都是带有超时重传机制的
尽可能重传,如果重传仍然失败,连续多次,此时仍然会单方面释放连接
注意:
第四次挥手需要注意的是,
站在A的角度,当收到这个FIN之后,并且发出去了ACK,此时A就视为四次挥手已经结束了,此时A 就可以直接释放掉连接了吗,答案是否定的,因为最后一个ACK也可能会丢包,如果最后一次挥手的ACK丢包了,B服务器就会重新发送一个FIN,而此时AB之间的连接就已经被A给释放掉冷,就没有能力再来对重传的FIN进行ACK了,因此,就需要让A在发送完一个ACK之后,让链接再等一会(主要就是看,等的过程中,会不会收到对方重传的FIN)
等待多久合适呢?
等待时间就是网络上任意两点之间传输数据的最大时间*2,这个时间就定义为MSL,如果超时时间达到MSL上限,说明这个包已经100%丢了
MSL在Linux上默认值是60s,而一般的超时重传的时间是500ms
还有极端情况,比如,A在等2MSL时间的过程中,B在反复重传FIN多次,这些FIN都丢了,如果真出现这个情况,那么当前网络一定是出现严重故障了,这个时候,是不具备"可靠传输"前提条件的,因此A就单方面释放资源,也无所谓了

总结

TCP是如果实现可靠性传输的
确认应答(核心)
超时重传
连接管理(三次握手,四次挥手)

滑动窗口

提高传输效率,更准确的说,是让TCP在可靠传输的前提下,效率不要台拉跨
使用滑动窗口,不能使得TCP变得比UDP快,但是可以缩小差距
上述没有采用滑动窗口的时候,会带有确认应答,虽然能够保证可靠性,但是时间大量都消耗在ack上了
使用滑动窗口,就是为了缩短等待时间
TCP协议_第15张图片
一次发出一组数据,发送一组数据的过程中,不需要等待ack,直到一组数据发送完毕,最后,再统一等待ack,也就是用一份时间来等待四个ack,把一次发送多少个数据,不能等ack这样的大小,称为窗口,窗口越大,此时发送的数据就越多,效率就越高,但是窗口不能无限大,如果是无限大,就相当于不等ack,此时就和不可靠传输差不多了,而且接收方能不能处理过来,中间的网络设备能否承受得住,也是一个未知数
TCP协议_第16张图片
上图再发送4条数据之后,等待第 一个ack到达A之后,A立即发送下一条数,保证需要等待的窗口大小始终是4
直观上看起来,就是窗口往后滑动了一个格子
如果按照上述这种批量的方式进行传输,中间出现丢包的情况怎么办
对于TCP来说,提高效率,必然是不能影响到可靠性的
两种情况:
1.数据包丢了:
TCP协议_第17张图片
此时必须要进行重传
什么时候进行重传,怎么进行重传
再1001-2000丢失之后,2001-3000这个数据到达了B
B返回的ACK确认序号,仍然是1001,
B仍然在向A索要1001这个数据,
当A连续几次都收到了来自于B索要的1001的数据,
A就明白了,1001是已经丢包了
A就会尝试重传1001
接收方,有个缓冲区在接收数据
TCP协议_第18张图片
上图接收缓冲区,1001-200是丢失了的,因此,返回的ack就会始终索要1001这个数据报,一旦这个数据报被补上,接下来就看缓冲区中是否有其他的数据丢失了,如果有,就索要对应的数据,如果没有,那么就直接索要队尾元素的下一个数据报,此时,就相当于用最小的成本完成了重传,这个操作被称呼为快速重传,是超时重传,结合滑动窗口下,产生的变形操作(本质上还是超时重传)
滑动窗口,也不是说,只要是TCP就一定会涉及到,如果通信双方大规模传输数据,就肯定要用到滑动窗口(此时按照快速重传来工作),
如果通信双方的传输数据规模比较小,这个时候就不会使用滑动窗口了(仍然按照之前的超时重传来工作)

2.ack丢了:
ack如果丢了,不用做任何处理,也是正确的
TCP协议_第19张图片
虽然A没有收到1001这个ack,但是2001这个ack包含了1001,表示1001也收到了
除非是所有的ack都丢了,如果只是一部分,对于可靠传输并没有任何影响

流量控制

滑动窗口,窗口越大,传输效率越高,但是窗口也不能无限大,如果窗口太大了,就可能使得接收方处理不过来了,或者是使得传输的中间链路层出现处理不过来的情况,这样就会导致丢包,就得重传,窗口太大并没有提高效率,反而影响了效率
举例生产者消费者模型:
A这边生产速度很快
B这边消费速度跟不上
接收缓冲区就会越来越多,最终就满了
满了之后,继续发送数据,就会丢包
因此,流量控制,就是根据接收方的处理能力,来限制发送方的发送速度(窗口大小)
如何衡量接收方的处理速度?
此处就使用接收缓冲区剩余空间大小来作为衡量指标
如果剩余空间越大,应用程序消费数据的速度就越快

16位窗口号

此处,就会直接把接收缓冲区的剩余空间大小,通过ack报文(16位窗口号)反馈给发送方,作为发送方下一次发送数据的参考依据
TCP协议_第20张图片
TCP协议_第21张图片

窗口探测包(不会携带具体数据),就只是为了触发ack(查询当前接收缓冲区的情况),一但发现返回的16位窗口大小不是0了,就可以继续发送数据了

拥塞控制

总的传输效率,是一个木桶效应,取决于最短板
TCP协议_第22张图片
如果中间某个环节,转发能力特别差,此时A的发送速度就不应该超过这里的阈值
虽然中间的设备,一般是运营商提高的企业级的路由器/交换机
整体的转发能力是非常强的,但是也架不住量大,毕竟,再宽的路也架不住过年的时候堵车
怎样去衡量中间设备的转发能力呢
此处,并不会针对中间舍舍设备的转发能力进行量化,
而是把中间的设备都看成一个整体
采取实验的方式,动态调整,产生一个合适的窗口大小
1.使用一个较小的窗口传输,如果传输通常,就调大窗口
2.使用一个较大的窗口传输,如果传输丢包,就调小窗口
拥塞窗口:在拥塞机制控制下,采用的窗口大小
TCP中,拥塞控制具体是这样展开的:
1.慢启动:刚开始进行通信的时候,会使用一个非常小的窗口来试试水,如果网络堵塞,一上来就采用一个很大的窗口,就会使得本来就不富裕的网络带宽雪上加霜
2.指数增长:在传输通畅的过程中,拥塞窗口就会指数增长(*2)
3.线性增长:当拥塞窗口在指数增长的情况下,增长到一定的阈值之后,就会转为线性增长(+n)
4…拥塞窗口回归小窗口:在窗口大小增长的过程中,如果出现丢包,认为当前网络出现拥堵了,此时,就会把窗口大小调整为最初的慢窗口,继续回到之前的2,3步骤,此外,此处也会根据当前出现拥塞(丢包)的窗口大小,调整阈值(指数->线性)
TCP协议_第23张图片
新的阈值: 出现丢包的窗口大小/2
这样的调整,就可以很好的适应多变的网络环境,当然,这里也是有不少的性能损失的(每次回到慢开始,都会使得传输速度大打折扣)
实际发送的窗口大小:min(拥塞窗口,流量控制窗口)
拥塞控制和流量控制,共同的限制了滑动窗口机制,可以使得滑动窗口,能够在可靠性的前提下,提高传输效率

延迟应答

提高传输效率的机制,围绕滑动窗口的
是否有办法,在条件允许的情况下,尽可能的提高窗口大小呢?
需要在返回ack的时候,拖延一点时间,利用拖延的时间,就可以给应用程序腾出更多的消费数据的时间,接收缓冲区的剩余空间,就会更大了
TCP协议_第24张图片
如果,此时立即返回ack,此时返回的窗口大小就是3kb,如果再等待500ms,可能500ms之后,应用程序又消费了2kb,那么此时返回的窗口大小就是5kb了
此时到底通过延时应答,能提高多少速度,还得取决于接收方应用程序的实际的处理能力
那么所有的包都可以延时应答吗?肯定不是
数量限制:每隔n个包就应答一次
时间限制:超过最大延迟时间就应答一次
具体的数量和时间,不同操作系统有不同的值

捎带应答

在延时应答的基础上,引入一个进一步提高效率的方式
延时应答,是让ack传输的时机更慢,捎带应答,基于延时应答,让数据 进行合并
TCP协议_第25张图片
TCP协议_第26张图片
数据报从两个合并成一个,效率会有明显的提升,主要还是因为撤离每次传输数据都是需要封装分用的
能合并的原因,一方面是时机上是可以同时的,一方面是ack数据本身是不需要携带载荷,和正常的数据也不冲突,完全就可以让这一个数据报,既能携带载荷数据,又能带ack信息(ack标志位,窗口大小,确认序号)

面向字节流

粘包问题:
这里粘的是"应用层数据报"
通过tcp read/write的数据,都是tcp报文的载荷,也就是应用层数据
发送方一次性是可以发送多个应用层数据报的,但是接受的时候,如何区分,从哪里到哪里是一个完整的应用层数据报,如果没有设计好,接收方就会很难区分,甚至出现bug
TCP协议_第27张图片
站在发送方的角度,预期
aaa是一个应用层数据报
bbb是一个应用层数据报
ccc是一个应用层数据报
在这里插入图片描述
上述三个数据报在数据缓冲区中完全粘在一起,应用层协议如果没有正确的设计好协议,就可能无法正确读取每个数据报
接收方的应用程序,读取数据缓冲区的数据,read的可能是一次读一个字节,或者一次读取若干个字节,没有办法一次读一个应用层数据报
因此,这里正确的做法是合理的设计应用层协议,因为这件事本身在传输层这边已经无解了,需要站在应用层的角度来解决这个问题
1.应用层协议中,引入分隔符,区分包之间的边界
TCP协议_第28张图片
TCP协议_第29张图片
接收方的应用程序,就可以通过\n来区分出应用程序数据报的边界了
2.应用层协议中,引入"包长度",也能区分包之间的边界
TCP协议_第30张图片
TCP协议_第31张图片
接收方应用程序读取数据的时候,先读取到包的长度,再根据包的长度往后读取一个完整的数据报,再读取下一个数据报的长度,依次读取数据
粘包问题不仅仅是tcp才有的,只要是面向字节流的机制都有同样的问题,解决方案也都是上述这样的两个方法

TCP连接异常情况的处理

网络本身就会存在一些变数,导致TCP连接不能继续正常工作了
1.进程崩溃
进程就没了->PCB就没了->文件描述符表也就被释放了->相当于调用了socket.close();因为socket在系统内核中也是一个文件,也会被放到文件描述符表中->崩溃的这一方就会发出FIN,进一步的触发了四次挥手机制,此时连接就正常释放了,和进程正常退出没什么区别
2.主机关机(正常关机)
正常关机会尝试关闭所有的进程(强制关闭进程),就和上面的崩溃的处理一样
由于主机关机是有一定的时间的,在这个时间之内,四次挥手可能挥完,也可能没有挥完
TCP协议_第32张图片
这里的主机A尝试关机,给主机B发送FIN,B在接收到FIN之后,可能B的FIN已经发不过去了,因为此时很可能A已经关机了,B收不到ACK就会重传FIN,重传几次都不行,自然就会选择单方面释放该连接
3.主机掉电()
a).
B主机给A主机发送消息,
因为主机A 已经断电了,B无法收到来自A的ack,那么B就会重传,重传几次都没有反应,就会发送触发复位报文(RST字段为1),尝试重置连接,重置操作仍然失败,那么就会触发单方面释放连接了
b).
主机A正在给B发消息,A突然掉电,而B在等待A给它发消息,不知道A是等会就能继续发送,还是说一直都不能发送了,B就会进入阻塞等待,具体等待多久,也不清楚,于是,就涉及到"心跳包"
所谓的心跳包,就是指,B虽然是接收方,但是也会周期性的给对方发送一个不携带任何业务数据的tcp数据报,发这个的目的就是为了触发ack,确认一下主机A是否正常工作/网络是否畅通
虽然TCP中已经有心跳包的支持,但是还不够,往往还需要在应用层,应用程序中重新实现心跳包,因为tcp的心跳包,周期性太长了,而且还是分钟级别的,而在当下高并发的场景下,分钟级是不够的,需要秒级的甚至毫秒级的心跳包,才可以在更短的时间内,发现某个服务器是否存在特殊问题
4.网线断开
TCP协议_第33张图片
这是主机掉电的升级版本
当主机A给主机B发送消息的时候,
A这边就相当于断电时候的情况a,而B这边就相当于情况b

你可能感兴趣的:(tcp/ip,网络,服务器)