TCP 和 UDP 都是属于传输层的协议,TCP 全称为 "传输控制协议( Transmission Control Protocol "). ⼈如其名, 要对数据的传输进行⼀个详细的控制;
其实我们在之前的序列化的时候就学习过TCP,TCP是支持全双工的,为什么呢?就是因为它有发送缓冲区和接收缓冲区,把数据从缓冲区拷贝到应用层,或者把数据拷贝到缓冲区,刷新由TCP自己决定,所以呢平常我们使用的接口比如read write recv 什么的,本质上都是拷贝,数据怎么发,什么时候刷新,怎么发现出错,出错怎么办都得靠TCP自己决定。
TCP怎么会和文件很像?
TCP和文件就是很像,TCP调用系统的接口就是,把用户层的缓冲区拷贝到发送缓冲区或者把接收的数据拷贝到接收缓冲区里,刷新由TCP自己决定,OS里面会存在大量报文,网络就是把磁盘换成了网卡,网络和Linux分不开,这也证实了Linux中一切皆是文件的思想。
为什么UDP没有缓冲区,TCP需要缓冲区
首先我们要知道UDP的特点就是不可靠,它只管发送,你接受不到他也不关心,TCP就不一样,他要保证可靠性,而且也要考虑对方还有没有能力接收数据的问题,所以它不是直接把用户层的数据拿过来封装个报头就发送,而是放在缓冲区,严格的保证结果发送。而接收缓冲区的设计也可以帮对方了解你的接收能力,和你是否在接收数据,接收缓冲区如果满了的话,就说明接收端一直没有处理数据。
一 :TCP报头格式

学习报头,就我们就要解决几个老生常谈的问题,TCP是如何解包的,TCP是如何分用的。
来了解一下报头对应的字段的作用
源/目的端口号:表示数据是从哪个进程来,到哪个进程去。
32位序号 / 32位确认号:后面详细讲。
4位TCP报头长度:表示该TCP头部有多少个32位bit(即多少个4字节);所以TCP头部最大长度是 15 × 4 = 60 字节。
6位标志位(Flags)
URG:紧急指针是否有效。
ACK:确认号是否有效。
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走。
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段。
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段。
FIN:通知对方,本端要关闭了;我们称携带FIN标识的为结束报文段。其他字段
16位窗口大小:后面再说。
16位校验和:发送端填充,CRC校验。接收端校验不通过,则认为数据有问题。此处的检验和不仅包含TCP首部,也包含TCP数据部分。
16位紧急指针:标识哪部分数据是紧急数据。
了解完TCP报头的各个字段的就很好回答刚才的两个问题,TCP里面有TCP报头长度这个字段,所以直接就要可以把TCP的报头长度计算出来,剩下的字段就是数据。
如何分用呢?字段中还有16位的目的端口号,这样交付给上层就可以知道上交给哪个进程。
TCP的报头怎么没有整个报文的长度?TCP时面向字节流的,一直发送,一直接收,主要是TCP 的长度信息由下层(IP 层)提供,不需要自己再存一遍。
二:TCP的应答机制
我们可以把关于可靠性的东西理解到这个确认应答机制。
为什么要确认应答呢?什么是个确认应答?
首先提个问题,如果你和家人说话,距离就是面对面,你会不会担心你说的话对方没有听到(正常交流),如果你在卧室里,你家人在厨房或者是在楼下,距离变得遥远你是不是就会担心对方没有接收到你的消息。当然在网络里也是这样,距离越长越容易产生问题。
确认应答是怎么样的呢?
就是一方给你发数据,对方得返回一个我收到了的消息。如果你再给对方返回一个应答是确认收到 收到了的消息,是不是就保证了刚才的收到消息,你收到了,但是你刚才的应答是不是就没法保证了,所以确认应答的特点就是无法保证100%的全部可靠,但是能保证有应答的老消息一定被接收到了,新消息有没有接收到全是在赌。
此时我们就要想到一开始的缓冲区了,此时缓冲区的作用就来了,如果第一次发送数据,你没有接收到,或者是接收到了但是并不完整,缓冲区里的数据是不是就有了用途,它可以给你重新发送一次。
确认应答里面还有一个非常厉害的机制-----捎带应答。A给B发送了数据,B是不是要给A发一个收到消息的应答,如果这时候B也想给A发送数据呢,那就一起发过来,也就是应答和数据一起发过来了,所以就是把应道捎带上了。
这个应答机制是如何在字段上面体现的呢?A给B发送数据后,B的TCP报头,将ACK的位段置为一,表示这个消息是个应答的消息,确认刚才发的消息收到了。
三:各个位段的详细含义,以及搭配使用。
- 32位序号
给对方发数据,如果是一连串的发送数据,这个数据在网络上没有按照你原本发送的顺序到达对发那个的主机上,这个问题该如何解决?每个报文都要进行应答,如何按照有效的顺序进行应答呢?
此时我们的序号和确认序号就可以帮助解决这个问题,首先序号就是这些数据的正确的顺序,假设我现在一共有3000字节长度的数据,我分成5块,标记好它每块对应的顺序,谁在一号位就是谁在最前面,谁是五号位谁在最后,当对方收到报文的时候,查看这个是序号一,就把数据接收到,然后回复对方应答,在确认序号呢显示1,表示一号数据已经接收到了。
!!!此时有个重点,这个确认序号表示呢----期望收到的下一个序号是 ,也就是确认序号是二,表示1号数据接收完成了,2号及以后的数据还没有接收到,希望你下一个数据序号是2。
这时候又有一个经典的问题了,
场景描述:
发送方发送:
数据1(序号1)
数据2(序号2)
数据3(序号3)
接收方收到:
✅ 数据1(序号1) → 正常
❌ 数据2(序号2) → 丢失或延迟
✅ 数据3(序号3) → 提前到达(乱序)
此时,接收方收到了 序号3,但 序号2 还没来。TCP 是按序交付给应用层的,所以不能把3交给应用(因为2还没到)。但是,接收方可以缓存这个乱序到达的 序号3 数据。然后发送一个确认2的应答,期待2的到达,等到2真的到达的时候,他就会直接发送确认应答为4的,因为123都已经收到了。
!!!此时也能解决另一个问题,为什么要有序号和确认序号这两组序号,如果应答的时候,把发过来的序号加一返回不就好了,但是为社么还要再增添一组新的序号呢,我们刚才是不是还说了更强的应答机制,就是捎带应答,如果我给你的应答还有我的数据呢,那我的数据是不是也要序号,回应我的消息是不是也要确认序号。
3.六个标记位-及其作用
URG:紧急指针是否有效。
ACK:确认号是否有效。
PSH:提示接收端应用程序立刻从TCP缓冲区把数据读走。
RST:对方要求重新建立连接;我们把携带RST标识的称为复位报文段。
SYN:请求建立连接;我们把携带SYN标识的称为同步报文段。
FIN:通知对方,本端要关闭了;我们称携带FIN标识的为结束报文段。
介绍完之后,我们用实际例子来了解这六个标记位的应用场景。
我们可以用一个TCP的链接方式---三次握手来认识几个标记位。

我们看一下我三次握手,客户端发送了一个数据报,标记为SYN的位置置为一,传达的意思是我想和你建立起关系,这就相当于谈男女朋友,你问对方我能和你谈恋爱吗,得等对方同意你才能和人家谈恋爱,然后对方呢,也喜欢你,他说我也想和你谈恋爱,然后给你一个SYN,顺带稍待应答了刚才的消息,然后客户端再发送一个ACK,这就意味着你要建立连接,必须双方都要有建立连接的意思。
说完三次握手,看看四次挥手吧

建立连接得双方都有建立连接的想法,当然那你想分手,双方也得都要有分手的想法,客户端发送FIN,表示我要断开连接了,然后服务器给一个消息收到的确认,然后服务器说你要分手,那我也要和你分手,他也发一个FIN,然后客户端也发一个ACK,四次挥手结束。
!!其实三次握手的本质就是四次握手,只是中间一次,用了捎带应答,所以四次变三次。
3.滑动窗口
刚才我们讨论了确认应答策略,对每一个发送的数据段,都要给一个ACK确认应答。收到ACK后再发送下一个数据段。这样做有一个比较大的缺点,就是性能较差。尤其是数据往返的时间较长的时候。既然这样一发一收的方式性能较低,那么我们一次发送多条数据,就可以大大的提高性能(其实是将多个段的等待时间重叠在一起了
但是你发送数据的时候,你得知道对方有没有能力接收吧,如果有能力接收,那你发吧,我慢慢给你应答。
窗口大小指的是无需等待确认应答而可以继续发送数据的最大值。上图的窗口大小就是4000个字节(四个段)。
发送前四个段的时候,不需要等待任何ACK,直接发送;
收到第一个ACK后,滑动窗口向后移动,继续发送第五个段的数据,依次类推;
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答;只有确认应答过的数据,才能从缓冲区删掉;
窗口越大,则网络的吞吐率就越高
画个图演示一下

如果我们发送的数据报丢失了,该怎么办
第一种情况:数据包到达接收端,但是接收端的ACK的丢失了
因为ACK确认是不是有确认序号啊,他代表着期待下一个传递的序号,假如1号的ACK丢失了,但是在后面的ack的确认序号已经更靠前了,这就说明之前的1早就接收到了。
第二种情况:数据包丢了
当某一段报文段丢失之后,发送端会一直收到确认序号是 1001 这样的 ACK,代表着期待下一次的数据期待是1001的数据
如果发送端主机连续三次收到了同样一个"1001"这样的应答,就会将对应的数据 1001~2000 重新发送;
这种机制叫做快重传机制