传输控制协议 TCP 在整个计算机网络中占有很高的地位, 它会控制着网络上数据的传输过程, 当然学习 TCP 还要知道它的几个重要特性, 如 确认应答, 超时重传等, 本文主要对这些特性进行介绍并解释, 力求加深印象.
附 6 个标志位:
- ACK: 确认应答是否有效;
- SYN: 同步报文段, 请求建立连接, 或者称之为握手信号;
- FIN: 结束报文段, 通知对方本端马上要关闭了;
- URG: 紧急指针是否有效;
- PSH: 提示接收端应用程序立刻从 TCP 缓冲区将数据读走;
- RST: 复位报文段: 对方要求重新建立连接.
前面已经说过 TCP 是面向字节流的传输, 也就是说 TCP 的传输是以字节的形式; 因此在数据传输过程中, TCP 将每个字节的数据进行了编号, 也就是序列号, 每一个 ACK 都带有对应的确认序列号, 目的就是为了告诉发送者我已经收到了哪些数据, 下一次的传输从哪里开始就可以了.
总之, 发送方发数据给接收方后, 接收方就会回应一个应答报文; 如果发送方收到了这个应答报文, 那么就知道对方已经收到数据了.
TCP 的核心是可靠性, 而可靠性的核心就在于应答机制.
关于超时重传的概念也很好理解, 就好比我在微信上给别人发了一个消息, 我说收到后回复收到, 但是可能由于网络问题我迟迟没有收到确认信息, 这个时候我就过一会再发一次.
用专业的术语来说就是主机 A 发送数据给主机 B 之后, 可能因为网络堵塞等原因, 数据无法到达主机 B; 而如果主机 A 在一个特定的时间间隔内没有收到 B 发来的确认应答, 就会进行重发.
这时候, 有人可能会疑问, 会不会有一种情况就是别人收到了消息, 但是忘记了回复确认收到???
也就是说主机 A 未收到主机 B 发来的确认应答, 也有可能是 ACK 丢失了, 这时候主机 A 就会一直给主机 B 发送信息, 主机 B 呢就会收到特别多的重复消息, 这时候就需要将这些重复的数据给丢弃掉; 这时候 TCP 会通过序列号识别出哪些是重复的包, 并把这些重复的包给丢弃掉, 起到去重的效果.
关于超时的时间该如何确定这个问题, 其实 Linux 中会以 500ms 为单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍, 累积到一定的重传次数, TCP 就会认为网络或者主机端出现异常, 强制关闭连接.
关于连接管理还是非常重要的, 因为这里涉及到传说中的 “三次握手和四次挥手这个概念”; 上过计算机网络的应该都知道这个概念的重要性, 其实连接管理说的就是如何建立连接 (三次握手), 如何断开连接 (四次挥手).
建立连接的过程就是一个投石问路的过程, 下面以呼叫器的测试为例子进行介绍, 当前小明和小红不知道自己的呼叫器是否能够正常使用, 如下图所示:
说明:
总之, 三次握手的目的就是来确认两个主机之间的传输是否是正常的, 尤其是两者的发送能力和接收能力是否是正常的; 其实这也是表明 TCP 可靠性的体现之一.
断开连接的过程比较容易好理解, 如下图所示:
这里就有个疑问, 在建立连接的时候 syn 和 ack 可以合并到一起进行发送, 那这里的 FIN 和 ack 为什么就不能合并到一起发送呢???
对于小红来说, ACK 和 FIN 的触发时间是不一样的, 理由如下:
断开连接的过程也有两个状态:
上面的确认应答策略对每一个发送的数据段都要给一个 ack 确认应答, 收到 ack 后再发送下一个数据段, 这样的过程太繁琐复杂了, 尤其是数据往返时间较长的时候; 那么我们可不可以一次发送多条数据, 这样不就大大提高了性能了么.
也就是说现在是批量进行发送, 一次发一波, 然后再等一波的 ack, 把多组数据的 ack 的等待时间重叠起来, 这样就会大大提高效率.
一次批量发的数据长度就是窗口大小, 那么窗口无限大的话可以么?
TCP 的主要特性之一就是可靠性传输, 如果窗口无限大的话, 就没有可靠性而言了. 实际上, TCP 为了提高效率, 滑动窗口下并不是每一条数据都有 ack, 会隔几条数据才有一个 ack.
总之:
那么出现丢包情况, 是如何进行重传的呢???
流量控制本质上就是用来控制滑动窗口大小的. 接收端处理数据的速度是有限的, 如果发送端发的太快导致接收端缓冲区溢满, 这时发送端如果继续发送就会造成丢包的风险, 继而引起丢包重传等一系列连锁反应; 因此为了避免出现这样的情况, TCP 会根据接收端的处理能力来决定发送端的发送速度, 这个机制就是流量控制.
总之:
虽然滑动窗口大大提高了数据传输的吞吐量, 但是在发送初期阶段就发送大量的数据可能会引发其它问题, 如当前网络比较拥堵, 在不清楚网络当前情况下贸然发送大量的数据很可能出现更大的丢包问题; 这时候 TCP 就引入了慢启动机制, 先发少量的数据探探路, 当清楚当前网络的拥堵状态后再决定按照一定量的速度传输数据.
总之, 先使用一个比较小的窗口来传输数据, 看看是否丢包, 如果不丢包就说明网络通畅, 这时候可以加大发送的速度; 当出现丢包的情况就意味着网络比较拥堵, 这时候就可以降低发送速率; 通过这样的方式来测试出一个比较合适的窗口大小.
真实的发送窗口大小 = MIN (流量控制的窗口, 拥塞控制的窗口).
在流量控制那已经说过接收端会立刻将剩余的缓冲区容量告诉接收端, 发送端根据接收端的提示来设置滑动窗口大小; 但实际情况下接收端处理数据的速度可能非常快, 会非常迅速的将缓冲区的某一部分数据消费掉, 这也就意味着接收端还能够接受更多的数据; 这时候如果接收端不立刻做出回应而是稍等一会再去应答, 那么这个时候接收端告诉发送端的的窗口大小会比立刻发送这种情况下吞吐量大很多.
我们的目标就是在保证网络不拥堵的情况下尽量提高传输效率; 也就是说让窗口大小在保证可靠的基础上尽量大一点, 但也不是所有的包都可以延迟应答, 而是有数量限制的, 每隔 N 个包就应答一次, 超过最大延迟时间再应答一次就足够了.
在上面的握手挥手操作中可以看到一发一收情况下会做出回应, 那接收端对缓冲区大小的回复也是发送 ack 回复; 在建立连接或者是断开连接过程中只要接收端收到数据内核就会立刻发送 ack, 但是由于有了延迟应答, 返回的 ack 不是立即返回而是要等一会, 就在这等一会这个功夫上服务器正要返回业务上的数据, 这时候就可以把 ack 和业务上的回复进行合二为一, 也就是将两个包变成了一个包, 这就是捎带应答的核心所在.
其实捎带应答并不是每次都能够触发, 这本身就是一个概率性机制!!!
创建一个 TCP socket的同时, 内核会创建一个发送缓冲区和接收缓冲区;
但是, 但是应用程序从接收缓冲区读取数据的时候, 不知道是从哪里读到哪里才是一个完整的应用层数据报, 应用程序此时只能看到接收缓冲区中的一个一个的字节, 无法区分当前接收缓冲区里有多少个应用层数据包, 这就是所谓的粘包问题, 这并不是 TCP 特有的问题, 只要是面向字节流的操作都可能会出现这样的问题!!!
上面已经说了为何会出现粘包问题, 对于粘包问题的解决就是要明确两个包之间的边界, 解决办法如下:
对于 UDP 来说, 会存在粘包问题么???
因为 UDP 是一个一个将数据交付给应用层, 就会有很明确的数据界限, 站在应用层的角度来看, 使用 UDP 的时候要么收到的是完整的 UDP 报文, 要么没有收到, 不会出现半个这样的情况.
上一篇:Java二叉树的前中后序遍历
下一篇:【Linux】网络基础(2)