文章目录
- 前言
- 1. TCP 协议概述
- 2. TCP报头结构
- 3. 如何理解封装和解包呢?
- 4. TCP的可靠性机制
- 4.1 TCP的确认应答机制
- 4.2 超时重传机制
- 5. TCP链接管理机制
- 5.1 经典面试题:为什么建立连接是三次握手?
- 5.2 经典面试题:为什么要进行四次挥手?
- 6. 流量控制
- 7. 滑动窗口机制
- 8. 拥塞控制
- 9. 面向字节流的特性
- 10. 粘包问题及解决方案
- 11. TCP异常情况处理
- 12. TCP 小结
- 13. TCP与UDP的对比
- 14. 用 UDP 实现可靠传输(经典面试题)
- 总结
前言
在计算机网络中,传输层协议扮演着至关重要的角色,它们负责在网络中的主机之间可靠地传输数据。其中,TCP(传输控制协议)和UDP(用户数据报协议)是最常见的两种传输层协议。TCP以其可靠性、有序性和流量控制等特性,成为了许多关键应用的首选协议,如文件传输、电子邮件、Web 服务等。然而,TCP的复杂性和性能开销也使得它在某些场景下不如UDP灵活和高效。本文将深入探讨TCP协议的各个方面,从其基本机制到高级特性,再到与UDP的对比,帮助读者全面理解TCP的工作原理和应用场景。
1. TCP 协议概述
TCP 全称为 “传输控制协议(Transmission Control Protocol”). 人如其名, 要对数据的传输进行一个详细的控制;
2. TCP报头结构
一眼看去, TCP协议确实比UDP要复杂的多, TCP协议的报头并不是定长的, 你可能会发现它除了选项和数据的长度是定长的20字节。但是它的选项的长度是不定的. 在前20字节中有一个叫4位首部长度的字段。它代表了报头一共有多大, 范围是: 20~60字节
TCP报头的其他字段数据, 比如: 序号, 确认序号, 窗口大小, 6位标志位等. 就是TCP用来保证它的效率和可靠性时需要使用到的字段
3. 如何理解封装和解包呢?
- OS内可不可以同时存在很多个收到的报文,这个报文还没来得及处理,甚至拷贝到传输层的接收缓冲区,甚至没有交给传输层而在网络层和链路层。
- OS内一定会收到大量还没来得及处理的报文,OS要不要对大量的暂未处理,但以及接收到的报文进行管理呢?要!(先描述,再组织)
添加报头只需要将head指针向左移动(封装的过程就是指针移动+填写报头)
解包就是将head指针强转,再提取他的字段,之后将head指针向又移动。
- 封装和解包,在内核中,只需要进行指针移动即可!
4. TCP的可靠性机制
4.1 TCP的确认应答机制
TCP 保证可靠性!
确认应答保证历史消息被收到了,可靠性是对历史消息的可靠性的一种保证!
确认应答机制:
- 比如主机A给主机B发送了1000字节的数据, 那么这个TCP包中的序号就为1000. 当B主机收到TCP包后, 会给A主机发送确认应答, 并且会将确认序号设置为1001, 代表1001以前的数据我都收到了. 可以从1001个字节开始给我发数据了。
- 同理,要是B主机没有给A发送1001确认序号,而是直接发送了2001. 证明2001以前的数据都收到了, 包括1~1000的
-
tcp 发送消息的模式 ——version0
发送数据和发送应答,一般是是 双方OS自动完成的!(通信细节OS自动解决了!) -
tcp 发送消息的模式 ——version1
根据发送报文的顺序进行排序,根据序号,进行tcp的按序到达!
-
32位的确认序号:收到的序号 + 1 (该确认序号之前的数据,我已经全部收到了,下次发送,请从确认序号开始!)
既想做确认应答,又想发数据!这种方式叫做捎带应答!(既是应答,又携带了数据!)
- 序号存在的意义:按序到达,应答和确认应答
- 确认序号&&序号:同时向双方发送数据的同时,也同时在做应答
- 为什么tcp报头中要有标志位?标志位存在的意义?
答:需要区分不同的报文请求!即区分不同的报文类型!
我们的server一定在通信的过程中,会同时收到各种各样的报文!(即TCP报文是需要类型的!)
为什么发SYN其实只是SYN被置1了,发送不同的请求只需要将不同的标记位置1即可!
其中三次握手由OS自动完成! 用户只用使用connect进行发起。
如何进行连接?管理,先描述,再组织!(内核数据结构,内存空间和时间)
维护连接是有成本的!时空成本
断开链接时,双方各自向对方提出断开的要求,然后再断开链接 close(fd) (四次挥手)
确认应答(ACK)机制,是报证可靠性的最基础也是最重要的一种机制!
4.2 超时重传机制
没有收到应答呢?
需要去等,等多长时间呢?
约定,对方没有收到报文,在一个时间端内,没有收到应答
数据丢包:
应答丢包:
5. TCP链接管理机制
正常的情况下,TCP要经过三次握手建立连接,四次挥手断开连接
像SYN、ACK这些是标记位,我们发送过去的是完整的TCP报头,并不是只是这些标志位!
connect(fd,服务器地址端口) 发起了三次握手,accept返回不参与三次握手!
三次握手与四次挥手都是操作系统自己完成的!
-
**三次握手:**不是能100%建立好链接,而是经历三次握手,c/s都是认为连接建立好了
-
RST(reset):重置!
所谓的链接重置,就是让两方,重新进行三次握手!
解决的问题就是连接出现异常的问题!
5.1 经典面试题:为什么建立连接是三次握手?
为什么不进行一次或两次连接,因为这样有明显漏洞,客户端不用应答就可以不停的去连接,这样只用一台客户端就可以对服务器工具(SYN洪水),而三次握手呢,客户端也需要建立连接,这样就不会受到单机的攻击!
三次握手
理由一:需要保重信道(网络)是健康的
三次握手,CS双方,都会有确定的一次收发,CS确认全双工
理由二:确保双方OS(TCP)是健康且愿意通信的(建立了通信的共识)。
服务端回应的时候进行捎带应答,表达了服务器急切的情绪!
三次握手的本质就是四次握手,只不过中间两次被捎带应答了!
5.2 经典面试题:为什么要进行四次挥手?
在思想上三次握手和四次挥手没有区别。
CLOSE_WAIT, TIME_WAIT
如果我们发现我们的服务器上有大量的close_wait状态的时候,这意味着,大概率我们的服务器有bug,主要是没有close_fd
-
TIME_WAIT: 等待一定的时长
-
为什么要等?
1.保证历史的报文有足够的时间进行消散,保证一来一回的时间。
2. 在我们四次挥手的过程中,看起来最后一个ACK没有应答,但其实这个ACK是有应答的,因为它还会发送FIN,如果最后一个客户端没有收到,服务器不可能走到LAST_ACK,等的时候同样会响应FIN,如果等待的时间内ACK没丢那么响应的这么长时间内那么就不会收到另一个FIN了!TIME_WAIT保证了对方的ACK也能顺利完成!(还有重传的机会)
- 时长是多长呢?
等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态
6. 流量控制
主机A发的太快了,主机B来不及接收
主机B可以向主机A通告自己的接收能力
什么叫主机B的接收能力?我的接收缓冲区中剩余空间的大小!
怎么通告?主机B有确认应答机制(在自己的ACK报文中,填写自己的16位窗口大小)
- 具体流量控制
接收端处理数据的速度是有限的. 如果发送端发的太快, 导致接收端的缓冲区被打满, 这个时候如果发送端继续发送, 就会造成丢包, 继而引起丢包重传等等一系列连锁反应.
因此 TCP 支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做流量控制(Flow Control);
- 接收端将自己可以接收的缓冲区大小放入 TCP 首部中的 “窗口大小” 字段, 通过 ACK 端通知发送端;
- 窗口大小字段越大, 说明网络的吞吐量越高;
- 接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值通知给发送端;
- 发送端接受到这个窗口之后, 就会减慢自己的发送速度;
- 如果接收端缓冲区满了, 就会将窗口置为 0; 这时发送方不再发送数据, 但是需
- 要定期发送一个窗口探测数据段, 使接收端把窗口大小告诉发送端.
设置PSH标志位,催促B主机快点发送窗口大小
UGR:紧急指针,让B主机优先处理
7. 滑动窗口机制
解决主机之间发送效率低下的问题,实现并行发送!
滑动窗口的大小:不考虑网络的情况,滑动窗口大小一般是:对方缓冲区中剩余空间的大小(暂时)
滑动窗口是发送缓冲区的一部分
报头中的窗口大小描述的是接收方的发送能力,滑动窗口的大小表示的是发送方的发送能力的问题
- 超时重传?
对发送的报文,并且没有收到的应答的报文进行保存(在滑动窗口中),方便我们经行重传!
- 如何理解这个滑动窗口呢?
a.只能向右滑动吗? 只能向右滑动
b.可以变大吗?可以变小吗? 可以
c.可以为0吗?可以
- 理解它?
滑动窗口的本质其实就是两个整型变量维护的一段数组空间
收到ACK报文,应答报头中的窗口大小,表面对方的接收能力 win
变更滑动窗口
- 确认序号的意义:确认序号之前的报文全部收到了!
丢包:
快重传策略:必须收到3个同样的确认应答则进行重发。
- 最左侧丢包:滑动窗口不能进行右移!(我们不担心最左侧报文丢失,会对丢失的报文进行快重传与超时重传策略)
- 中间报文丢失:右滑转换为最左侧丢包问题
- 最右侧报文丢包:右划转换为最左侧丢包问题
在滑动窗口内,所有的问题全部会转换成为最左侧丢包问题!
流量控制是如何做到的?
滑动窗口在滑动的过程中既可以变大也可以变小,也可以为0,滑动窗口的大小要和对方的接收窗口吻合,滑动窗口的大小暂时等于对方的接收能力。这样保证了既可以保证以最高的速率全力发送,又可以防止对方的数据进行溢出多发,即保证发送速率又保证接收能力。
滑动窗口是流量控制的具体实现,是重传策略的具体实现。
缓冲区是一个类似于环状队列所以不用担心数据的溢出
8. 拥塞控制
虽然 TCP 有了滑动窗口这个大杀器, 能够高效可靠的发送大量的数据. 但是如果在刚开始阶段就发送大量的数据, 仍然可能引发问题.因为网络上有很多的计算机, 可能当前的网络状态就已经比较拥堵. 在不清楚当前网络状态下, 贸然发送大量的数据, 是很有可能引起雪上加霜的.
TCP 引入 慢启动 机制, 先发少量的数据, 探探路, 摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;
网络出现大面积丢包,说明网络拥塞了!
一但发生网络拥塞,识别到的网络拥塞主机全部要进行拥塞控制。
滑动窗口 == min(接收方的窗口(接收能力),拥塞窗口)
2^n -> 慢启动,拥塞控制的核心算法?
探测网络的健康状态:
- ssthresh值:阈值,记录上一次拥塞窗口值(32位)的一半,作为下一次窗口变化的临界值,指数增长变为线性增长!
9. 面向字节流的特性
创建一个 TCP 的 socket, 同时在内核中创建一个 发送缓冲区 和一个 接收缓冲区;
- 调用 write 时, 数据会先写入发送缓冲区中;
- 如果发送的字节数太长, 会被拆分成多个 TCP 的数据包发出;
- 如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
- 接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;
- 然后应用程序可以调用 read 从接收缓冲区拿数据;
- 另一方面, TCP 的一个连接, 既有发送缓冲区, 也有接收缓冲区, 那么对于这一个连接, 既可以读数据, 也可以写数据. 这个概念叫做 全双工
由于缓冲区的存在, TCP 程序的读和写不需要一一匹配, 例如:
- 写 100 个字节数据时, 可以调用一次 write 写 100 个字节, 也可以调用 100 次write, 每次写一个字节;
- 读 100 个字节数据时, 也完全不需要考虑写的时候是怎么写的, 既可以一次read 100 个字节, 也可以一次 read 一个字节, 重复 100 次;
10. 粘包问题及解决方案
- 先要明确, 粘包问题中的 “包” , 是指的应用层的数据包.
- 在 TCP 的协议头中, 没有如同 UDP 一样的 “报文长度” 这样的字段, 但是有一个序号这样的字段.
- 站在传输层的角度, TCP 是一个一个报文过来的. 按照序号排好序放在缓冲区中.
- 站在应用层的角度, 看到的只是一串连续的字节数据.
- 那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包.
那么如何避免粘包问题呢? 归根结底就是一句话, 明确两个包之间的边界.
- 对于定长的包, 保证每次都按固定大小读取即可; 例如上面的 Request 结构, 是固定大小的, 那么就从缓冲区从头开始按 sizeof(Request)依次读取即可;
- 对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;
- 对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可);
思考: 对于 UDP 协议来说, 是否也存在 “粘包问题” 呢?
- 对于 UDP, 如果还没有上层交付数据, UDP 的报文长度仍然在. 同时, UDP 是一个一个把数据交付给应用层. 就有很明确的数据边界.
- 站在应用层的站在应用层的角度, 使用 UDP 的时候, 要么收到完整的 UDP 报文, 要么不收. 不会出现"半个"的情况
11. TCP异常情况处理
进程终止: 进程终止会释放文件描述符, 仍然可以发送 FIN. 和正常关闭没有什么区别.
机器重启: 和进程终止的情况相同.
机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行 reset. 即使没有写入操作, TCP 自己也内置了一个保活定时器, 会定期询问对方是否还在. 如果对方不在, 也会把连接释放.
另外, 应用层的某些协议, 也有一些这样的检测机制. 例如 HTTP 长连接中,
12. TCP 小结
为什么 TCP 这么复杂? 因为要保证可靠性, 同时又尽可能的提高性能!
可靠性:
- 校验和
- 序列号(按序到达)
- 确认应答
- 超时重发
- 连接管理
- 流量控制
- 拥塞控制
提高性能:
- 滑动窗口
- 快速重传
- 延迟应答
- 捎带应答
其他:
- 定时器(超时重传定时器, 保活定时器, TIME_WAIT 定时器等)
基于 TCP 应用层
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
当然, 也包括你自己写 TCP 程序时自定义的应用层协
13. TCP与UDP的对比
我们说了 TCP 是可靠连接, 那么是不是 TCP 一定就优于 UDP 呢? TCP 和 UDP 之间的优点和缺点, 不能简单, 绝对的进行比较
- TCP 用于可靠传输的情况, 应用于文件传输, 重要状态更新等场景;
- UDP 用于对高速传输和实时性要求较高的通信领域, 例如, 早期的 QQ, 视频传输等. 另外 UDP 可以用于广播
归根结底, TCP 和 UDP 都是程序员的工具, 什么时机用, 具体怎么用, 还是要根据具体的需求场景去判定.
14. 用 UDP 实现可靠传输(经典面试题)
参考 TCP 的可靠性机制, 在应用层实现类似的逻辑;
例如:
- 引入序列号, 保证数据顺序;
- 引入确认应答, 确保对端收到了数据;
- 引入超时重传, 如果隔一段时间没有应答, 就重发数据;
- …
总结
经过对TCP协议的全面分析,我们可以得出以下结论:
-
可靠性:TCP通过序列号、确认应答、超时重传、连接管理、流量控制和拥塞控制等机制,确保数据的可靠传输。这些机制虽然增加了协议的复杂性,但也大大提高了数据传输的可靠性。
-
性能:尽管TCP的可靠性机制带来了一定的性能开销,但通过滑动窗口、快速重传、延迟应答和捎带应答等技术,TCP能够在保证可靠性的同时,尽可能地提高传输效率。
-
应用场景:TCP适用于需要可靠传输的应用,如文件传输、电子邮件、Web 服务等。而UDP则适用于对实时性和传输速度要求较高的应用,如视频传输、在线游戏等。
-
与UDP的对比:TCP和UDP各有优缺点,选择哪种协议取决于具体的应用需求。TCP提供了更可靠的传输保障,但可能会牺牲一些性能;UDP则在传输速度和实时性方面表现更优,但传输的可靠性较低。
-
实现可靠传输:即使在UDP这样的不可靠协议上,也可以通过应用层的逻辑实现类似TCP的可靠性机制,如序列号、确认应答和超时重传等。
通过本文的学习,读者应该能够更深入地理解TCP协议的工作原理,掌握其关键特性,并能够根据实际需求选择合适的传输层协议。希望本文能够为读者在网络编程和应用开发中提供有价值的参考。