TCP协议特点及报文格式详解

TCP协议特点

1. 有连接

需要对方一直处于准备接收信息的状态.好比发微信,需要对方在线,可以直接上来就发.通信的双方不需要保存对方的信息.

2.可靠传输

使用TCP协议将数据发出去后,关心对方是否能否收到.

3.面向字节流

字节流作为传输数据的基本单位,一次读写可以读写一个或多个字节的数据,较为灵活.

4.全双工

一条链路,可以进行双向通信.

TCP协议报头格式

1. 源端口号和目的端口号

作为传输层协议,肯定要清楚这个数据从哪里发到哪里.起点和终点一定要明确包含在其中.就像寄一份邮件,上面会写上寄出的地址和收件的地址.

这两个端口号各自占2个字节,也就是各自占16个比特位.

2. 4位首部长度

这里的4位首部长度指的是报头的长度,不是总TCP报文的长度.这说明报头的长度是可以选择进行改变的.

如果"选项"完全没有,TCP报头长度就是20字节.如果"选项"拉满,TCP报头最长时有60个字节.此处的首部长度单位是"4字节",实际的首部长度要在这个数字的基础上*4.这样我们不难计算出剩下的选项部分最多是40字节.

3. 保留6位

UDP报文长度使用两个字节来表示,太小了.TCP虽然将这个上限提到了4个字节.但是,万一某一天TCP需要扩展一些功能,这个保留位就可以发挥作用了,不至于重蹈UDP的覆辙.

4. 16位校验和

这里的校验和和UDP的校验和是一个东西,都是为了确保数据在物理传输过程中不会出现误差,同样使用CRC进行计算得到. 

这里我们先介绍这几个内容,其他的我们将接下来结合TCP协议的核心机制逐步进行具体介绍.

TCP协议的核心机制

1. 确认应答

TCP协议要解决一个非常重要的问题-------可靠传输.这里所说的可靠不可靠,并不是说一定能把数据100%发送给对方,而是尽可能让发送方知道,接收方是否收到.

比如说未来某一天,我给我对象发个消息"一起出来吃饭好不好",我对象看到了之后,就给我回一个"好呀好呀".

这种用于"应答"的数据称为"应答报文".应答的英文为"acknowledge"=>ack,是不是很熟悉,这就是6位标识位中的第二位.

如果"ack"这一位bit位为1,说明这就是一个应答报文.但是上述单纯的应答,就有可能带来麻烦.

比如,我给对象发了一个"一起吃饭好不好",接着马上又发来一个"我能不能不陪你去打会儿游戏".但是,后面发的消息有可能先到达.对象看到的是"我能不能不陪你去打会儿游戏""一起吃饭好不好",说"你敢!","好呀好呀".

这种"后发先至"就可能带来误解,但是这又是在网络通信中没有办法改变的.这里就引入了编号,即使出现了"后发先至",也不会影响对于传输意思的理解.

这里我们就要介绍一下32位序号和32位确认序号.这里的序号就是我们上述所说的编号,是给发送方发送的数据包使用的,确认序号就是给ack报文使用的(ack为1才有效),这样数据就可以根据确认序号区分出要应答上面哪一个报文了.

真实的TCP序号不是按照"条"来编号的,而是按照"字节"来编号的.会给每一个字节都分配一个序号.相邻的字节的编号是连续递增的.

应答报文里面的确认序号也十分有特点,取值就是要应答的数据的最后一个字节的序号再+1.

比如说,A给B发送一个数据报,里面有1000个字节的数据,B收到之后给A返回一个应答报文,这个应答报文里面的确认序号是1000+1=1001.这个1001的确认序号包含两层意思:

1. 对于B来说<1001的数据已经全部收到了.2. B开始向A索要从1001开始之后的数据.

这个确认应答机制极大程度上保障了TCP的"可靠传输".但是上述情况是通过应答报文通知发送方,一切顺利的情况.那么如果数据在传输过程中被丢弃了,被称作"丢包",该怎么办呢?"丢包"在网络通信中是不可完全避免的,TCP就是要在不可靠的环境中构造出"可靠的通信方式".

2. 超时重传

超时重传就是用来应对网络层丢包情况的策略.正常情况下,TCP发送方通过接收方发来的应答报文来知道数据是否被对端接收.

现在,还是A给B发送一个数据报,但是这个数据报丢包了,B就收不到数据,也不会发送应答报文.这时A就可以根据是否收到"ACK"来区分是否丢包.正常情况下,A给B发送数据报到收到B发来的应答报文要经历一段时间,A就会进行一定的等待,如果等待时间超过了某个阈值,还没有收到ACK,就可以认为出现了丢包.

这时,A就会把刚才发过的数据再发一遍.那么,如果是B发送的ACK丢包了呢?站在A的角度无法区分是发送的数据丢包了,还是应该收到的ACK丢包了.A能做的就是重新再传一遍.这样,B可能会重复收到多次重复的数据,不过TCP层次上,接收方会针对收到的数据进行去重.

接收方,操作系统内核中存在一个数据结构,"接收缓冲区",类似于阻塞队列.在收到数据后,层层分用,分用到TCP这一层的时候,就把收到的数据放到这个阻塞队列中了.放的时候会根据当前数据的序号,判断这个数据是否在队列中存在过,如果存在过这个数据就是重复接收的,直接被丢弃.

超时重传时间的是多少呢?这个时间实际上不是固定数值,而是会动态变化的.随着重传轮次的增加,会变的越来越长.具体时间是多少,怎么增长要取决于系统怎么实现.如果重传次数很多,说明网络出现问题了,如果重传次数达到了阈值,这时候就会尝试"重置连接",触发一个''复位报文''.(相当于连接重新开始).这个"复位报文"就是将6位标志位的第四位"RST"设置为1.

确认应答和超时重传这两个机制是TCP可靠传输的关键.

3. 连接管理

建立连接的流程:三次握手

握手,是一个形象的比喻.握手行为没有具体实质的含义,仅仅是一种打招呼的礼节,往往是两个人刚见面才会握手.两个机器一见面就开始打招呼,没有实际业务上的数据,交互只是为了打招呼而传输一些数据.

建立连接,就是通信双方,各自保存对端的信息,具体完成上述过程,需要经过三次网络交互.三次交互的过程可以简化为下图.

谁是客户端?谁发起请求,谁就是客户端.SYN,叫做"同步报文",这个数据报不携带任何的业务数据,载荷部分是空的,只有TCP报头.这个TCP报头,6位标志位的SYN这一位为1.

上述流程,客户端和服务器各自给对方发送syn,再各自给对方发送一个ack,一共是四次交互.关键在于,中间的两次交互ACK和SYN可以合并成一个网络数据报(把ACK和SYN标志位同时设置为1).各自给对方发送SYN是因为要确保"互斥性".这三次握手时,让对方保存自己的信息,只有双放都把对方信息保存好,连接才算是建立完成.

三次握手的意义

三次握手到底解决了啥问题?

1. 三次握手,相当于"投石问路":在正式传输业务数据之前,确认一下通信链路是否通畅.

2. 通过三次握手,来确认通信双方,发送和接收能力都是正常的.进行三次握手,本质上就是完成上述确认的过程.

3. 三次握手过程中,还需要协商一些必要的参数.比如数据的起始序号,就需要连接时双方进行协商得出.

TCP为啥要三次握手,两次行不行?四次行不行?

两次:不行,服务器这边对于通信双方的发送能力和接受能力的验证还没完成.

四次:行,但是没必要,拆开中间的这次交互,虽然不影响TCP的正常功能,但是性能会有损失.

三次握手中TCP状态

listen是服务器出现的状态.当服务器绑定端口成功之后,就会进入到listen状态.此时意味着,就随时有客户端可以连接上来了.established表示建立完成,可以随时进行后续通信了.

断开连接的流程:四次挥手

三次握手,一定是客户端发起第一次请求的.但是四次挥手则不一定,客户端和服务器都可以主动发起.下面是四次挥手的简图.客户端代码调用socket.close方法或者客户端进程结束就会发送结束报文.

所谓的"结束报文",就是将TCP报头中的FIN这一位设置为1.

那么四次挥手中间的两次交互能否合并呢?有时候能合并,有时候不能合并.

对于三次握手的过程来说,服务器收到来自客户端的SYN,立即返回ACK和SYN,所以它们可以合并成一个数据报.但是对于四次挥手这个过程,服务器收到来自客户端的FIN,要理解返回一个ACK;但是,服务器发送FIN的时机由应用程序代码中调用close方法的时机决定,它们触发的时机是不同的,因此难以进行合并.

在特殊情况下,使用TCP的"延时应答"机制,就可以合并这两个数据.对于一般情况是不能合并的,因此最终还是把断开连接的过程称为"四次挥手".

四次挥手中TCP状态

CLOSE_WAIT: 等待应用程序调用close方法,如果程序出现了问题,close没有即使调用或者忘记调用,就可能使机器上出现大量的CLOSE_WAIT.

TIME_WAIT: 存在的意义,是为了对应最后一个ACK丢包的场景.这个状态来等待可能到达的FIN重传的数据.这个状态不是持续的,而是有一定时间的,如果一定时间内,没有收到重传的FIN,就说明了最后一个ACK已经被对方发送了,TIME_WAIT就可以释放了.这个等待的时间一般是2MSL.

4. 滑动窗口

TCP除了保证可靠传输之外,也希望能够尽可能高效地完成传输任务.滑动窗口就是一种提高传输效率的机制.下面先给出滑动窗口的简要示意图:

可以看到,引入滑动窗口后的效果是从"一条一条发送数据"变成"批量发送数据".但是,虽然批量大宋数据,还是要等待一会ACK."滑动窗口",这个概念中不等待ACK,批量发送多少数据,这个就是"窗口大小".它高效就高效在,可以在一段时间内同时"接收发送前面数据后返回的ACK""发送后面的数据".

比如,我们现在规定窗口大小为3000,把它分为三组数据,窗口中每组数据占1000个字节.

第一次发送的数据是1-1000号字节,然后直接发送1001-2000号字节,再直接发送2001-3000字节.在这个过程中.如果我们收到了1001ACK,就说明1001号前的数据已经成功被接收到了.窗口就向后移动,开始发送3001-4000的数据,如下图:

滑动窗口中丢包问题

情况一: 数据包已经到达,但是ACK出现丢包.

比如,A给B发送一个包含1-1000号字节的数据包,这时B应该给A返回一个1001号ACK,但是这个ACK被丢了.但是,当A给B发送一个包含1001-2000号字节的数据包,B此时正常返回了一个2001号ACK.因为,字节的编号是升序增加+1的,2001就告诉发送方,1-2000号字节的数据已经全部收到,2001涵盖了1001的效果.

总结:对于ACK丢失,不做任何处理.

情况二: 数据包出现丢包.

比如,A给B发送一个包含1-1000号字节的数据包,B正常给A返回一个1001号ACK.然后A给B发的包含1001-2000号字节的数据包,但是它被丢弃了.此时,就算B接收到了A的3001-4000号字节的数据,给A返回的还是2001号ACK.只要B还没有收到1001-2000号数据包,之后返回的每一个ACK都是2001号.

那么B是怎么知道1001-2000号字节的数据还没有收到呢?在B的内部,有一个接受缓存区,1001-2000的数据缺失了,返回的ACK,确认序号都是在索要缺口的数据.

5. 流量控制

流量控制,就是站在接收方的角度,通过调整"窗口大小"来控制传输数据的速度.窗口的大小是可以改变的,窗口越大,单位时间内发的数据就多,效率就越高;窗口越小,单位时间内发的数据就越少,效率较低.我们通过流量控制来尽可能提高传输的效率,但是高效的前提还是可靠.如果发送速度太快,接收方处理不过来,可能就还是会引起丢包.

那么我们具体如何衡量接收方的处理速度呢?接受方,有一个接收缓冲区(阻塞队列).

以空闲空间大小作为发送方发送数据的窗口大小.把这个数值告诉发送方.接收方会给发送方返回ACK.在ACK报文中,在TCP报头里,指定了一个字段-----16位窗口大小,表示上述的空闲空间大小.但是,这里的16位并不代表窗口大小最多就是64kb,在"选项"中,可以选择"窗口的扩展因子",实际此处的窗口大小,可以非常大.

初始情况下,接收缓存区是4000,一旦使用满了,就会告诉发送方,暂停发送.但是,接收方会不断地消费数据,使得剩余空间变大.那么接收方怎么告诉发送方缓存区的情况呢?发送方会周期性发送"窗口探测报文",也是不携带业务数据的TCP数据包.主要的目的为了触发ACK,从而知道缓冲区的情况.

6. 拥塞控制

拥塞控制和流量控制类似,都是和滑动窗口搭配的机制.流量控制的时候,很容易定量来衡量接收缓冲区的剩余空间的大小,把这个大小作为发送窗口大小.但是,如果考虑中间节点,就复杂了.但是,TCP把它们视为一个整体,通过"实验"的方式来找到一个合适的窗口大小.

刚开始,按照小的速度,小的窗口来发送数据.如果没有出现丢包,就可以增加速度,增加窗口大小.增加到一定速度,发送速度非常快了,此时可能某个设备达到阈值,出现丢包了.此时,发送方立即减少窗口大小,继续发送看看是否丢包.按照这种"动态平衡"的方式,控制传输的速度,就称为"拥塞控制".

流量控制窗口和拥塞控制窗口哪个小就听哪一个.下面是拥塞窗口的大小动态变化示意图:

规律:

1. 刚开始以比较小的窗口来传输数据.(这里的慢是指传输速度慢)

2. 按照指数方式扩大窗口.

3. 指数增长过程中,达到某个阈值,就要变成线性增长.

4. 线性增长到一定程度,就会出现丢包.发送方立即减少窗口大小.

5. 缩小的两种方式:

        1. 直接缩到最小(回到慢增长).

        2. 缩小到出现丢包时窗口大小一半的位置,接下来线性增长.

7. 延时应答

在延时应答下,ACK不会立即返回,而是会稍等一会再返回.延时的核心目的是为了提升传输的效率.而提高传输效率的关键在于尽可能地提高窗口大小.通过延时,就可以使窗口大小得到提升.因为在延时的这段时间内,接收方可能已经读取了一部分数据,做为窗口大小的剩余的空间就会变多.

此处延时时间,结合使用了两种方式:

1. 按照一定时间来指定延时.

2. 按照接收到的数据量来决定延时.

8. 捎带应答

我们之前介绍的"三次握手"中,第二次交互就是将ACK和SYN合并到了一个数据报上.这给我们的启发就是:经过延时应答的ACK可以搭载着响应的业务数据来返回发送方,这个过程就是"捎带应答".

这样的效果是把两次传输合并成一次传输,极大地提升了传输效率.

9. 面向字节流

在字节流的读取过程中,会涉及一个非常关键的问题-------粘包问题.此处粘的包是应用层数据包.我们需要区分,从哪里到哪里是一个完整的数据包,明确包与包之间的界限.

解决方法:

1. 使用分隔符.定义任意字符都可以,只要字符在请求数据中是不存在的就行.

2. 约定包的长度.

粘包不仅仅是TCP的问题,读取文件的过程中也会涉及到.我们现在会使用更多现成的协议和网络工具来解决内部的粘包问题.

10. 异常情况

1. 其中某个进程崩溃了.

进程崩溃和正常结束,操作系统都会回收释放出的PCB,可以释放里面的文件描述符表,相当于调用close.由于操作系统仍然管理着TCP的连接,仍然会正常和对方进行四次挥手.

2. 某个主机被关机(正常关机流程)

操作系统会先尝试强制结束所有的用户进程,然后再进入关机流程.这个过程也和上面一样,结束进程后正常进行四次挥手.主动触发了FIN,但是之后流程没走完,就关机了.对方收到后返回ACK,之后对方准备发送一个FIN,但是发现发了好几次都没有回应ACK,就会把对方删了,断开连接.

3. 某个主机电源掉电(网线断开)

A和B在进行通信,A还没做出任何反应就突然掉电了.

如果B是发送方,接下来B发送数据后都不会受到ACK了,就会触发超时重传,重传几次后,再发送"复位报文",也没有回应,B就会单方面删除保存A的信息.

如果B是接收方,B不知道A上面时候会给它回应,就会发送一个没有载荷的数据包"心跳包".只是为了触发ACK.如果A挂了,就不会对这个"心跳包"返回ACK.可以高频地发送"心跳包"来更快速地发现问题.

如何使用UDP进行可靠传输?

1. 引入确认应答(ACK)

2. 引入序号+确认序号

3. 引入超时重传.

我们现在再来看看TCP报头中哪些属性还没有讲到?

还有URG,PSH和16位紧急指针.

6位标志位中的第一位URG表示发送端向另一端发送紧急数据,第三位:PSH是催促对方尽快给自己回应.

16位紧急指针则标识出在本报文段中紧急数据共有多少个字节.

  • 13
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值