目录
TCP协议
TCP报文格式
- 源/目的端口号:表示数据是从哪个进程来,到哪个进程去;
- 32位序号/32位确认号:;
- 4位TCP报头长度:表示该TCP头部有多少个32位bit(有多少个4字节);所以TCP头部最大长度是15 * 4 = 60
-
6 位标志位 :URG :紧急指针是否有效ACK :确认号是否有效PSH :提示接收端应用程序立刻从 TCP 缓冲区把数据读走RST :对方要求重新建立连接;我们把携带 RST 标识的称为 复位报文段SYN :请求建立连接;我们把携带 SYN 标识的称为 同步报文段FIN :通知对方,本端要关闭了,我们称携带 FIN 标识的为 结束报文段
-
16 位窗口大小:
-
16 位校验和:发送端填充, CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不光包含 TCP 首部,也包含 TCP 数据部分。
-
16位紧急指针:标识哪部分数据是紧急数据;
-
选项:option,可选的,可以有也可以没有,选项也是报头的一部分
TCP原理
TCP 对数据传输提供的管控机制,主要体现在两个方面:安全和效率。这些机制和多线程的设计原则类似:保证数据传输安全的前提下,尽可能的提高传输效率。TCP有四大特性:有连接、可靠传输、面向字节流、全双工
1.确认应答机制(安全机制)
什么是确认应答?
发送方把数据发给接收方后,接收方收到数据就会给发送方返回一个应答报文(acknowledge,ack),发送方如果收到了这个应答报文就知道自己的数据是否发送成功了。那确认应答是怎样完成的呢?
![](https://img-blog.csdnimg.cn/direct/310ccb719ffe4d9bbab1c18eee18ed92.png)
![](https://img-blog.csdnimg.cn/direct/f8e2b0e689c84ffeb96893c04eb04372.png)
![](https://img-blog.csdnimg.cn/direct/8990e52311fd4b56a0023e90f0b10864.png)
2.超时重传机制(安全机制)
确认应答描述的是一个比较理想的情况,如果网络传输中出现丢包了,发送方就无法收到ack了这种情况该怎么办?遇到这种情况就可以使用超时重传机制,针对确认应答进行补充。
由于丢包是一个"随机"的事件,因此在TCP传输过程中就存在两种情况:
1.传输的数据丢了
2.返回的ack丢了
![](https://img-blog.csdnimg.cn/direct/1a5f32955e8f44608a803732416b07e4.png)
3.连接管理机制(安全机制)
连接管理分为建立连接和断开连接,也就是我们常说的三次握手和四次挥手,在正常情况下,TCP要经过三次握手建立连接,四次挥手断开连接。
TCP的握手就是给对方传输一个简短的,没有实际意义的数据包,通过这个数据包来唤起对方的注意,从而触发后续的操作。握手这个操作不是TCP独有的,甚至不是网络通信独有的,计算机中的很多操作都会涉及到"握手"。比如给手机充电时,使用不同的充电头,手机这边显示的充电方式不一样(普通充电/快速充电),这个插的过程就要先"握手"。
1)三次握手(经典面试题)
TCP的三次握手,是在建立连接的过程中,需要通信双方一共"打三次招呼"才能够完成连接的建立。
A想和B建立连接,A就会主动发起握手操作。实际开发中,主动发起的一方就是所谓的"客户端",被动接受的一方就是"服务器"。
syn:同步报文段,就是一个特殊的TCP数据包,不携带任何的业务数据。
![](https://img-blog.csdnimg.cn/direct/9c98f00cee594aab8336c44fc6b0cb41.png)
2)四次挥手
建立连接,一般都是由客户端主动发起的;但是断开连接,客户端和服务器都可以主动发起
FIN:结束报文段
![](https://img-blog.csdnimg.cn/direct/b81aff3bbb4f4ca5b229b8e88a9032c1.png)
![](https://img-blog.csdnimg.cn/direct/66fdef6b5fde49d68ac88d462556153c.png)
4.滑动窗口(效率机制)
![](https://img-blog.csdnimg.cn/direct/28002c0c2b65490db964842f89f08c01.png)
![](https://img-blog.csdnimg.cn/direct/aa7d842308714784a628a24c5e376a43.png)
![](https://img-blog.csdnimg.cn/direct/3e21db8894574d029d42ca91320c3f25.png)
![](https://img-blog.csdnimg.cn/direct/8d50968088654aafa28fb9f31530f078.png)
![](https://img-blog.csdnimg.cn/direct/c90662c9a1934ece8d2a6b25a380acd7.png)
5.流量控制(安全机制)
![](https://img-blog.csdnimg.cn/direct/0794f3d299b848a2abedac1a7a4918fb.png)
![](https://img-blog.csdnimg.cn/direct/86d3b8327e594bc79d940748be8a3d3a.png)
接收端如何把窗口大小告诉发送端呢?回忆我们的TCP首部中,有一个16位窗口字段,就是存放了窗口大小信息。
那么问题来了,16位数字最大表示65535,那么TCP窗口最大就是65535字节(64kb)么?其实不是的,TCP报头中,选项部分里有一项叫做"窗口扩展因子",通过扩展因子,就可以让窗口大小表示一个更大的值。
6.拥塞控制(安全机制)
流量控制,考虑的是接收方的处理能力,而拥塞控制考虑的不仅仅是接收方,还有通信过程中中间节点的情况。
A发送数据到B,这中间的的转发过程中,任何一个节点处理能力达到上限,都可能对发送方产生影响,甚至可能会影响到可靠传输。
由于中间节点不像接收方一样很容易对它的处理能力进行量化,中间节点结构更复杂,难以直接进行量化,因此就可以使用"实验"的方式来找到合适值。
首先,让A先按照一个比较低的速度发送数据,如果数据传输的非常顺利,没有丢包,就尝试使用更大的窗口,更高的速度进行发送;随着窗口不断增大,达到一定程度,可能中间的节点就会出现丢包,发送方如果发现丢包了,就把窗口大小调小,此时如果发现还是继续丢包,就继续调小;如果不丢包了,就继续尝试增大。在这个过程中,发送方不断调整窗口的大小,逐渐达成"动态平衡"。
这种做法,就相当于把中间节点看成一个整体,通过"实验"的方式,来找到中间节点的瓶颈在哪里。整个"实验"的过曾,可以参考下面这个图。
流量控制和拥塞控制都是在限制发送方的窗口大小,最终实际发送的窗口大小,是取流量控制和拥塞控制中窗口的较小值。
7.延时应答(效率机制)
正常情况下,A把数据传给B,B会立即但会ack给A;但也有时候,A传给B,B等一会再返回ack给A,这就是延时应答。
延时应答的本质也是为了提升传输效率。延时返回ack,给接收方更多的时间来读取接收缓冲区的数据,此时接收方读取数据之后,缓冲区的剩余空间就变大了,这样返回的窗口就变大了。
比如说,初始情况下,接收缓冲区的剩余空间是10kb,如果立即返回ack,那么返回的就是10kb这么大的窗口;如果延时200ms再返回,在这200ms的过程中,接受方的应用程序又读了2kb,此时返回ack,就可以返回12kb的窗口了。而发送方窗口的大小,就是传输效率的关键。窗口越大,传输效率就越高。我们的目的是在保证网络不拥塞的情况下尽量提高传输效率; 那么所有的包都可以延迟应答么?肯定也不是。有两点限制:
1)数量限制:每隔N个包就应答一次; 2) 时间限制:超过最大延迟时间有应答一次;
8.捎带机制(效率机制)
捎带机制就是在延时应答的基础上,进一步提高传输效率。
网络通信中,通常是这种"一问一答"的通信模型。
这里的request和response跟之前的FIN不一样,这里是业务上的数据。这里的ack也是内核立即返回的,但response是由应用程序代码来返回的,这两者时机是不同的。但是由于TCP引入了延时应答,ack不一定是立即返回的,可能需要等一会,在等一会的过程中,B就正好把response给计算好了.计算好了之后就会返回response,此时,就可以顺便把刚才要返回的ack捎带上一起返回。所以ack和response这两个数据就合并成一个数据了。
本来是要传输两个TCP数据包(封装分成两次发送),通过上述操作就可以把两个包合并成一个,此时就可以得到更高的传输效率了。
9.面向字节流
与UDP面向数据报的通信方式不同,TCP是面向字节流的。面向字节流就会有一个最重要的问题
—粘包问题,粘包问题不是TCP独有的,面向字节流的机制都有类似的情况。而此处的"包"是应用层数据包,如果同时有多个应用层数据包被传输过去,此时就会出现粘包问题。
什么是粘包问题呢?就是两个(多个)数据包粘在一起,没办法区分哪些数据是A数据包的内容,哪些数据是B数据包的内容。为什么会出现粘包这样的情况呢?这就跟接收缓冲区的读取形式有关。
举个简单的栗子:
A向B发送三个数据包,数据分别是aaa,bbb,ccc;这些数据会被放在B的接收缓冲区,再由B使用read之类的方法读取接收缓冲区的数据。但是数据缓冲区的数据是这样存放的:
所以接收缓冲区中,这三个应用层数据包的数据,就是以字节的形式紧紧挨在一起的,而B读取数据的方式是字节流,它可以一次读一个字节,也可以读两个字节,也可以读N个字节......但是最终的目标是为了得到完整的应用层数据包,而B此时根本不知道缓冲区里的数据从哪里到哪里是一个完整的应用层数据包了。
那么该如何解决粘包问题呢?
解决这个问题的核心思路就是:通过定义好的应用层协议,明确应用层数据包之间的边界。基于这个思路,有两种目前常用的方法:
1)引用分隔符
2)引用长度
举个简单的例子说明一下这两个方法是如何应用的:
1)用\n作为分隔符:
A向B传输的数据在B的接收缓冲区中的存放形式如下图:
这个时候B读取数据的时候,就可以一直读取数据,直到读到\n为止。
2)引入长度
A向B传输数据时就将数据的长度一并传输给B
那数据在B接收缓冲区的存放形式就如下图:
这样的话,B读取数据的时候,就可以先读两个字节,得到数据包的长度,再根据这个长度继续读取对应字节的个数。
10.异常情况处理
在使用TCP的过程中也是会出现一些异常情况的,常见的异常情况主要有以下四个:
1)进程崩溃; 2)主机关机(正常流程); 3)主机掉电(非正常流程); 4)网线断开
那我们遇到这些处理时TCP会如何去应对呢?
1).进程崩溃
进程崩溃,就是进程异常终止,这个进程就没有了,那进程描述符也就会释放,此时相当于调用close方法。此时就会触发FIN,对方收到后自然就会返回FIN和ACK。此时就是正常的四次挥手断开连接的流程。所以从这里我们也可以得到一个结论:TCP的连接是可以独立于进程存在,也就是说,如果进程没有了,但TCP的连接不一定就没有了。
2).主机关机(正常流程)
当我们再进行关机的时候,就会先触发强制终止进程操作(相当于上述情况1)。此时就会触发FIN,自然会返回FIN和ACK。但此时不仅仅是进程没了,可能整个系统都关闭了,如果在系统关闭之前,对端返回了FIN和ACK,此时系统还是可以返回ACK,进行正常的四次挥手; 但如果对端返回FIN和ACK时系统已经关闭了,那就无法进行后续的ACK响应。站在对端的角度,对端就以为是自己的FIN丢包了,就会重传FIN,重传几次都没有响应,就会放弃连接,即把对端的信息删除掉。
3).主机掉电(非正常流程)
当主机突然一下就掉电了,此时就是一瞬间的事情,系统根本来不及杀进程,也来不及发送FIN主机就直接停机了。站在对端的角度,对端就不一定知道这个事情,那怎么办?会分为两种情况分别应对:
1)如果对端是在发送数据(接收方掉电),发送的数据就会一直等待ack,就会触发超时重传,然后触发TCP连接重置功能,发起"复位报文段",如果复位报文段(TCP报文六位标志位中的"RST")发出去后还是没有效果,此时就会释放连接。
2)如果对端是在接收数据(发送方掉电),对端还在等待数据到达...但是等了半天也没有消息,此时接收方无法区分时对端没发消息还是挂了。TCP中提供了心跳包机制,就是接收方会周期性的给发送方发起一个特殊的、不携带业务数据的数据包,并期待对方返回一个应答;如果对方没有应答,并且重复多次没有应答,此时接收方就会认为对端挂了,就会单方面的释放连接。
4).网线断开
网线断开其实和刚才的主机掉电情况非常类似。
假设A正在给B发送数据,突然网线断开,A就相当于就会触发超值重传->连接重置->单方面释放连接;
B就会触发心跳包->发现对端一直没有响应->单方面释放连接。
TCP小结
与UDP相比,TCP为什么会这么复杂? 那是因为TCP又要保证了可靠性,同时又尽可能的提高性能。可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
-
超时重发
-
连接管理
-
流量控制
-
拥塞控制
- 滑动窗口
-
快速重传
-
延迟应答
-
捎带应答
以上就是这次介绍的全部内容啦,主要介绍了TCP中十个比较重要的机制,这并不是就说TCP只有这是个机制,还有其他很多机制,只是这里讲述了会比较常用/常见的几种机制,小伙伴们有兴趣的话可以多去了解了解~