以下介绍TCP通信的十大特性!!
就像我们平时在聊微信的时候,用户A向用户B发一个“hello”,用户B回复一个“hi”。
收到回复“hi”之后,我们就可以确定用户B收到我的消息了。但是当用户B不回复的时候,我们就不知道消息是否发送成功。(网络环境非常复杂,不一定每次传输数据都能成功)。
确认应答机制就是这样的,它在发送数据后,还会返回一个消息告诉你发送成功了,这个返回的消息被称为“应答报文”。
问题一:但是同时用户B可能也要发送一个数据给用户A,那么这个时候怎么区分返回的消息是“应答报文”还是“真实的数据”呢?
TCP就使用6个特殊标志位中的“ack”来区分,如果ack这一位为0,则是普通报文(也就是真实的数据);如果ack这一位为,则是应答报文。
问题二:如果同时传输多条消息,怎么能保证消息是一一对应的,不发生歧义呢?
通过分别编号来保证没有歧义。这时候就用到32位序号和32位确认序号:
32位序号:用在发送数据的一方
32位确认序号:用在做出应答的一方,并且只对“应答报文”有效
由于TCP是字节流,所以编号的时候以字节为基本单位。举例说明双方交互的过程:
发送端:发送序号为1 ----报文长度为1000字节
响应端:发送确认序号为1001 ---- 是由序号和报文长度计算出来的 含义:我已经收 到了1-1000字节的数据,你可以继续发送1001字节之后的数据
发送端:发送序号为1001 ----报文长度为1000字节
响应端:发送确认序号为2001---- 是由序号和报文长度计算出来的 含义:我已经收 到了1001-2000字节的数据,你可以继续发送2001字节之后的数据
问题三:序号和确认序号都是32位的,不会溢出吗?
32位保存的数据是:0-42亿9千万 ,也就是4G 大概率不会溢出
就算溢出了,前面的序号已经用过了,从头开始重新编号即可
通过这样,确认应答机制就保证了TCP传输的可靠性。同时,确认应答机制是保证TCP可靠传输最核心的机制。
如果在网络传输的过程中,发生了丢包:把用户A发送的数据丢了,即把“hello”丢了;或者 把用户B响应的数据丢了,即把“hi”丢了。这是一个非常严重的问题。TCP中就引入了超时重传机制来解决这个问题。
发送端丢包
如果用户A在过了一段时间后,还没有收到响应,他就认为数据丢了。会重新发送一次数据
响应端丢包
如果用户A在过了一段时间后,还没有收到响应,他同样认为数据丢了。也会重新发送一次数据。但是数据发送成功了只是响应丢了而已,因此响应端就会收到两条,甚至多条相同的数据。这时,响应端这边就会针对相同的数据进行去重(根据序号去重),使响应端这边只处理一条数据。
超时的时间该如何确定呢?
超时等待的时间并不是均等的,而是逐渐变大的。比如,丢包的概率是1% 那么第二次丢包的概率就是1% * 1% = 0.01% 概率较小,换句话说就是大概率会到达的不需要重传了。等待的时间就会更长一点。
如果重试了几次之后,任然无法传输数据,就会让TCP断开重连
如果还是连不上,就彻底断开连接不再尝试。
连接管理机制:描述的是TCP建立连接和断开连接的过程。
TCP的连接,只是一个逻辑上的“虚拟的连接“,并不是像网线一样真正的连接起来。
主机A和主机B建立连接:
主机A的系统内核里:记录一个数据结构,包含了和他连接的对方是谁(IP、端口号、协议)
主机B的系统内核里:记录一个数据结构,包含了和他连接的对方是谁(IP、端口号、协议)
问题: 怎么知道发送方发送的是”申请连接“还是“真实的数据”呢?
使用6位特殊标志位中的syn来表示,如果syn这一位为0,表示是普通报文(真实的数据);如果syn这一位为1,表示是同步报文(申请建立连接);
为什么要建立连接? 建立连接的意义是什么?
- 投石问路 检查一下当前的网络是否畅通
- 检查通信双方的发送能力和接收能力都是正常的
- 三次握手也在协商一些重要的参数
LISTEN:服务器启动之后,绑定端口之后(new ServerSocket完成后),表示服务器状态良好,可以让别人建立连接
ESTABUSHED:表示连接已经建立,连接很稳定
同样的,断开连接时发送的消息也需要和真实的数据区分开。
使用6位特殊标志位中的fin来表示,如果syn这一位为0,表示是普通报文(真实的数据);如果syn这一位为1,表示是申请断开连接;
CLOSE_WAIT:表示等待socket调用close方法断开连接
TIME_WAIT:表示主动发起断开的一方进入等待 等待收到fin
注: 在处理完最后一个ack之后,不能立即断开连接,而需要继续保持一段时间。这是为了万一最后一个ack丢了还有机会进行重传。
如果最后一个ack丢包了,此时就会重发一个fin; 如果最后一个ack没有丢包,一段时间后,没有fin重传过来,就认为ack顺利到达了。
上述我们讨论的都是正常情况,如果发送丢包了会怎样呢?
上述过程中,重传的效率是非常高的,也称为“快速重传”。
快速重传:是搭配滑动窗口机制的超时重传,如果传输的数据很多,是批量传输,就使用快速重传
超时重传:TCP默认的丢包后的解决方案。在传输的数据很少时使用,而且在批量传输中,最后一条数据丢包了也被使用。
窗口的大小可以无限大吗? 越大效率越高?
窗口越大传输的效率确实是越高的,但是窗口不能无限的大。
因为:窗口太大时,接收方的处理速度跟不上发送方的发送速度,就会导致接收方丢掉一些数据。这些丢掉的数据还得重传,接收方的处理速度仍然跟不上。这样就起不到提高效率的作用。
接收方的处理速度是有限的,我们就需要在滑动窗口机制上对发送速率做出限制,让窗口不要太大也不要太小。这就引入了流量控制机制。
怎样让发送方知道剩余空间有多大呢?
在ack应答报文中把剩余空间的大小返回。发送方根据情况在16位窗口大小中选择合适的窗口大小进行下次的传输。
16位窗口大小规定窗口最大是64k吗?
不是的。可以通过窗口的扩展因子使其更大。
我们知道接收方的接收效率之后,发送方就可以只按照这个效率来发送数据吗?
不是的。在接收方和发送方之间还有很多的节点进行转发,我们也应该考虑一下这些节点的转发效率。
我们怎样能知道中间节点的转发效率呢?
进行实验:一开始时,先发少量的数据,窗口较小。如果不丢包,就要放大窗口,先让窗口指数增长,达到阙值后再让窗口线性增长。发生丢包了,就让窗口大小回到最初值,再重复刚才的过程,同时降低阙值为刚才丢包时阙值的一半。如此反复…
捎带应答机制是在延迟应答机制的基础上引入的。
在网络通信中,典型的通信模型是”一发一收“。TCP中只要把数据传输过去,对方收到数据了就会通过内核返回一个ack报文,另外的业务数据会通过应用程序负责传输,再收到一个ack报文。
中间进行的两步:内核返回ack报文和应用程序返回业务数据,本来是不同时机传输的,不能合并在一起。但是引入了延迟应答机制后,如果这两步操作间隔的时间不长,就会合并成一个响应、一次传输过去。这就是捎带应答机制。
在面向字节流传输时,都会遇到一个”粘包问题“。
主机A给主机B传输数据后,主机B都把它们保存在主机的接收缓冲区中,需要使用的时候再拿出来。但是,由于是面向字节流传输的,就不知道从哪儿开始到哪儿结束才是一个完整的数据。
解决:
注:UDP没有这个问题。因为它是面向数据报的
TCP在连接时如果遇到了异常该怎么办?
程序奔溃了,进程异常退出。
操作系统就会回收进程的资源。相当于调用了socket的close方法,会触发fin报文,和普通的”四次挥手“一样。
正常关机。
关机的时候会强制退出所有进程并回收资源,和上面一样会触发fin报文,和普通的”四次挥手“一样。
主机掉电
掉电的是接收方
此时发送方不知道对面掉电了,会继续发数据。但是收不到ack报文,会进行超时重传,重传几次之后仍然没有ack报文,会断开连接并重新连接再重传(重置连接会用到6个特殊标志位的rst 如果rst这一位为1 则是复位报文段,进行重新连接),如果还没有ack报文,就会放弃连接。
掉电的是发送方
接收方等一会儿后就会发送一个”心跳包“(周期性触发,不懈怠任何业务数据),如果对方不返回”心跳包“,说明心跳遗失了,就放弃连接。
网线断开
双方主机都以为对方掉电了,分别执行上述主机掉电的两种情况。