写在前面
计算机网络分了五层(应用层,运输层,网络层,链路层,物理层),《自顶向下》的第三章详细讲解了运输层的两大协议以及其背后原理,其中tcp协议是本章乃至整个计算机网络的一个重点内容,鉴于此,我觉得有必要对本章作个笔记,以巩固运输层的知识。
正文
运输层的目的在于为不同主机(host)上的应用或者说进程提供逻辑通信——通过运输层协议,两台电脑仿佛直接相连一样。应用无需知道底层内部实现的原理和细节,比如怎么把远隔世界两地电脑上的数据进行相互传输。
本章的一些注意点:
- 运输层协议仅仅实现在网络的边缘处,例如主机,电脑,手机等。如路由器,交换机这些网络核心设备,是没有实现运输层协议的
- 每一层协议仅仅检查分组相应的协议层字段
- 最常用的两种输入层协议,tcp和udp
- 运输层的下面是网络层,网络层的目的在于为不同的主机提供逻辑通信,而非主机上的进程
- 网络层常用协议是IP,其提供的是一种不可靠的数据传输服务
- 为了做到为不同主机(host)上的应用或者说进程提供逻辑通信这一目的,运输层协议必须能分辨出数据的来源和去向。为此,运输层存在着两种行为,多路复用和多路分解
多路复用
当运输层收到来自上方应用层的数据时,运输层会为数据封上一些头部信息,根据所有协议不同,封上的信息也不一样。这一行为,被称为多路复用。
多路分解
当运输层收到下方网络层传输来的数据时,运输层会检查多路复用时封上的信息,从而正确的把数据定向到相应的进程。
如何使用运输层的协议? 操作系统提供了被称为socket的接口api供编程人员调用,对socket的形象理解是其是一种抽象,将复杂的实现(tcp, udp)协议的各种行为抽形成简单的几个函数给开发人员使用。就像浏览器将发送请求报文这一http协议规定的行为,抽象成我们只需要输入url然后回车即可。
这里需要注意的一点是,在一般情况下,一个计算机端口只能被一个进程占用。一个进程可以创建多个socket,多个tcp socket可以监听同一个端口,并保证接受的数据依旧是正确的。但多个udp socket就无法监听同一端口。这其中的差异源于tcp和udp协议的不同,tcp是面向连接的,其有足够状态的信息来分辨数据来源,后定向到正确的socket。但udp不需要维持连接,仅仅通过端口号来决定数据的去向,所以会导致冲突。
udp的多路复用和分解
一个udp socket通过一个二元组(目的ip地址,目的端口号)来标识,当输入层收到数据时,通过检查这个二元组,来定向数据该去往哪一个udp socket。这也是为什么多个udp socket无法监听同一个端口的原因,因为系统无法再根据二元组来确定消息的去向(它们的二元组是一样的)。
TCP的多路复用分解
一个tcp socket通过一个四元组(源ip,源端口,目的ip,目的端口号)来标识,这也解释为什么多个tcp socket可以监听同一个端口,尽管目的ip和目的端口号是一样的,但是源ip和源端口的组合总是不同的。
UDP
相比于TCP来讲,UDP是一个简单的协议,就是把网络层IP提供的服务封装了下,实现了多路复用和分解,提供了端到端进程间的通信和错误检验服务。
相对于TCP来说:
- UDP是不可靠的传输服务
- 没有流量和拥塞控制
但:
- 能够够精细的控制数据的发送时间和速率
- 无需事先建立连接
- 无连接状态
- 分组首部开销小
封装
在接收到应用层传输来数据后,在数据前方加上了四个字段,分别是源端口号、目的端口号、长度(包括头部)、检验和,应用数据。
可靠数据传输
数据传输可能遇到的问题:
- 传输中数据被损坏
- 数据丢失
- 数据可能乱序到达
解决方法:
- 检验和
- 序号
- 定时器
- 肯定和否定反馈分组
如何在保证可靠性的前提下,提高其性能?
- 通过引入流水线(pipelining)技术
引入流水线导致了:
- 序号范围需要增加
- 收发双方可能需要缓存乱序到达的分组
- 以上两个的具体实现取决于传输协议如何处理分组丢失、损坏的问题(是选择回退N步,还是选择重传)
回退N步
其核心在于,发送方会维持一个窗口,发送方能发送的数据量取决于窗口长度,并且当丢失时会重送所有未确认的分组。
接收方会丢弃乱序到达的缓存
特点:
- 累计性ACK
- 单一定时器
选择重传
核心在于,收发双方都会维持一个窗口,并且尽力保证窗口的状态是同步的,因此当分包丢失时,发送方只会重送丢失的分组。
接收方会缓存乱序到达的分组
特点:
- 独立性ACK
- 多个定时器
TCP
特点:
- 面向连接
- 全双工的
- 点对点,不存在一次发送将数据传递给多个接收方、
- 在合适的时候发送 发送缓存 里的数据
- 为每个数据封上一个TCP头部
- TCP连接的每一端都具有发送缓存和接受缓存
报文段结构
-
源端口号
-
目的端口号
-
序号(seq) - 所带数据的第一个比特的序号,同时也是接收方期待的序号,等于接收方回复报文中的ACK(n)中的n
-
确认号(ACK) - 对于一个ACK(n)来说,告诉对方n-1前的数据已经收到,下一次期待的序列号为n
-
首部长度
-
URG
-
ACK - 指示,用于指示报文中确认号字段的值是有效的
-
PSH - 指示,立即发送发送缓存里的数据
-
RST - 指示,用于强制关闭连接
-
SYN - 指示,用于握手阶段也就是建立连接的阶段
-
FIN - 指示,用于正常关闭连接
-
接受窗口 - 用于TCP的流量控制功能
-
因特网检验和
-
紧急数据指针
-
选项
-
应用数据
可靠数据传输
- TCP协议为数据的每一Byte都编号,而非针对报文段
- 总是维持最老未经确认的1 Byte的计时器
- 每一次超时重置的计时器时间会加倍
- 其错误恢复机制是回退N步和选择重传的混合体
- 不会丢弃乱序到达的分组,而是缓存起来
- 采用累计性ACK
- 只会重传丢失报文段中的数据
快速重传
当连续收到4个相同的ACK时(一个正常的ACK,三个正常ACK的重复),会触发快速重传,立即重发分组
流量控制
为了防止过高数据流量导致接收者的接受缓存爆掉,接收者会在其tcp报文中通过 接受窗口 指示发收者还能发送多少数据
接受窗口(rwnd)公式:
- rwnd = RcvBuffer - [LastByteRead - LastbyteRead]
- 且:LastByteSent - LastByteAcked <= rwnd
TCP连接管理
建立连接(三次握手)
- 客户端发送SYN位置1的报文段
- 服务端返回SYN为1,ACK为1的报文段
- 客户端发送ACK为1,且附带数据的报文段
断掉连接
- 客户发送FIN为1的报文段
- 服务端返回ACK为1的报文段
- 服务端发送FIN为1的报文段
- 客户端返回ACK为1的报文段
- 客户端在一段时间后,关闭连接
拥塞控制
拥塞的代价
- 导致分组过长的排队时延
- 需要重传因缓存溢出丢失的分组
- 高延时导致重送分组
- 丢包导致运输相关分组的分组交换器所作的工作全部白费
TCP的拥塞控制
TCP采用端到端的拥塞控制
三个主要问题:
- 一个TCP的发送方如何限制自己的发送流量的速率?
通过设置一个拥塞窗口(cwnd),并且保证:LastByteSent - LastByteAcked <= min{cwnd, rwnd}
- 如何感知其发送路径拥塞了?
- timeout
- 收到一次正常ACK后连续收到三次正常ACK的重复
- 感到拥塞时,采用什么样的算法改变发送速率?
- 慢启动
cwnd的值从1 MSS开始,并且对每一个ACK,cwnd值变为原来的2倍,直到超过阈值(ssthresh),转为拥塞避免模式
- 拥塞避免
在每一个RRT时间,cwnd的值增加一个MSS
- 快速恢复
cwnd的值降为一半加上重复收到的重复ACK的数量,并且每一个ACK,cwnd的值增加一个MSS
在实践中,一旦timeout就会会到慢启动的状态,多次重复ACK则会进入快速恢复状态
TCP公平
TCP的公平性在于保证每个连接的吞吐量是平均的,而不是应用或进程间。