TCP概述
TCP特点
- 面向连接: 在连接的基础上双方才可以通信
- 端到端: 通信只有两个端点
- 可靠交付: 传输什么样的数据, 就接受什么样的数据
- 全双工通信: 通信是双向的, 而且不会相互干扰
- 面向字节流: 数据以字节传输, 表明TCP不知道数据的含义
TCP连接
什么是TCP连接? TCP连接就是一个抽象表示, 指出了通信双方的身份.
通信中的一方我们用套接字(socket)表示
套接字socket = ( IP地址: 端口号 )
而且我们知道TCP连接是端到端
的, 所以TCP连接可以这样表示:
TCP连接 ::= { socket1, socket2 } = { (IP1: port1), (IP2: port2) }
TCP报文段
TCP报文段的组成很简单, 只有首部和数据, 表示如下:
TCP报文段 ::= TCP首部 + 数据部分
TCP首部信息如下图所示(注:数字表示位数):
首部信息中各字段解释如下:
- 源端口: 发送方port, 可知端口范围为0~65535
- 目的端口: 接收方port, 可知端口范围为0~65535
- 序号: 报文段的编号, 可知序号范围0~2^32^-1, 由于TCP是面向字节流的,所以一个序号表示一个字节
- 确认号: 告知对方自己期望的下一个报文段编号
- 数据偏移: TCP首部长度, 表示到达数据部分的偏移量, 范围为0~15, 所以首部长度最多为60字节
- 保留: 未定义
- URG: 紧急位, 用于设置紧急指针
- ACK: 确认位, 用于设置确认号
- PSH: 推送位, 告知TCP数据发送
- RST: 重置位, 告知对方TCP出现错误,需要重新连接
- SYN: 同步位, 建立连接的标志
- FIN: 结束位, 释放连接的标志
- 窗口: 告知对方自己的接受窗口
- 检验和: 检验TCP报文段的有效性, 包括TCP首部和数据部分
- 紧急指针: URG置1有效, 定位紧急数据
- 选项(长度可变): 用于扩展TCP首部信息
- 填充: 填充TCP首部格式使得满足以4字节为单位
TCP报文段的规则中定义了
MSS
(Maximum Segment Size:最大报文段长度), 表示TCP报文段中数据部分的最大长度, 默认值是536, 所以因特网中的所有主机都应能够接受536+20=556字节的最大报文段
TCP可靠传输
可靠传输有三个方面的含义:
- 无差错: 数据传输之前之后是一样的
- 不丢失: 发送方发送的数据, 接收方一定可以接收到
- 不重复: 发送方发送的数据, 接收方不会重复接受
滑动窗口
滑动窗口是基于字节流的, 所以滑动的单位为1字节, 其结构如下图所示:
说明如下:
- Category #1: 已发送, 已确认
- Category #2: 已发送, 未确认
- Category #3: 未发送, 可发送
- Category #4: 未发送, 不可发送
需要注意的细节问题:
- 发送窗口可能要比接受窗口小, 因为发送方要考虑网络拥塞问题
- 对于不按序到达的数据如何处理, TCP标准没有说明. 但是考虑网络资源的珍贵性, 一般情况下接收方都会接受下来暂时保存着
- 接收方必须要有累计确认的功能, 因为这样可以很好的减小开销
超时重传
超时重传是为了保证可靠传输, 其实现涉及两个东西: RTT
(Round-Time Trip)和RTO
(Retransmission Time-Out).
RTT
: 报文段往返时间RTO
: 超时重传时间, 略大于RTT
一个报文段发送出去, 如果过了RTO
时间还没有接收到确认, 那么就需要重传报文段
我们知道网络情况是动态的, 有时候网络良好, 有时候网络拥塞, 那么RTT
就应该也是动态变化的,RTO
自然而然也就跟着RTT
动态变化. 这个动态变化就需要设计一个算法来计算, 我们这里就简单讲一下3个算法.
-
自适应算法:
RTTs = (1-a)*(旧RTTs) + a*(新RTT样本) RTO = RTTs + 4*RTTD (注:RTTD表示RTT偏差加权平均值)
- Karn算法: 在自适应算法基础上, 提出只要报文段重传, 那么就不采用其样本RTT
- Karn修正算法: 在Karn算法基础上, 剔除只要报文段重传, 那么RTO就增大一点
选择确认
SAK(Selective ACK)选择确认是指在发生数据未按序到达的时候, 接收方告诉发送方自己接收了哪些数据, 从而发送发可以决定重发丢失的数据, 而不是全部重新重发, 使得网络资源得到更好的利用
SAK的实现原理是借助TCP首部信息中的
选项(可选长度)
这一部分. 我们知道一个连续的字节段可以由头序号和尾序号表示, 一个序号需要4个字节, 所以一个字节段需要8个字节表示, 那么我们是不是一次告诉发送方5个字节段呢?(首部最长60字节,20字节是固定的,所以有40字节/8字节=5
). 然而并没有, 因为还需要2个字节, 分别表明使用SAK选项和选项长度, 所以只有40-2=38个字节可用, 所以一次最多告诉发送方4个字节段
TCP流量控制
所谓流量控制, 就是接收方让发送方不要发送太多的数据, 好让接收方来得及接受, 过程如下图所示:
从上图可以看出, 接收方一共进行了3次流量控制
注意: 现在考虑一种情况, 在上图中, 接下来B处理完了缓冲区的数据, 那么B就会发送一个rwnd=400的报文段, 然而报文段却在中途丢失了, 那么A就一直等B的通知, 而B也一直等A发数据, 因为B认为自己已经通知A可以发送了, 这样就形成了一个死锁. 所以TCP为每一个连接都设置了一个
持续计时器
, 每当接收方得知发送方的发送窗口值为0的时候, 启动持续计时器
, 时间一到接收方就发送一个探测报文段
, 一直持续这个过程直到发送方的发送窗口不为0
TCP拥塞控制
所谓拥塞控制, 就是防止过多的数据注入到网络中, 使得网络中的路由器或链路不致过载
注意: 拥塞控制不同于流量控制, 拥塞控制是基于整个网络考虑, 而流量控制是基于通信双方考虑
拥塞控制原理
拥塞控制从大方面考虑可以分为两种: 开环控制
和闭环控制
开环控制
是指在网络设计的时候把相关拥塞因素考虑进来, 一旦网络运行起来就不变更了; 闭环控制
则是动态地考虑拥塞情况并做出控制, 我们这里讲一下闭环控制
的原理
闭环控制
是基于反馈环路的概念.属于闭环控制
有以下几步
- 检测网络系统得知拥塞发生的时间地点
- 把拥塞信息发送到可以采取行动的地方
- 调整网络系统的运行以解决拥塞问题
拥塞控制方法
- 慢开始(slow-start): 从发送数据开始, 拥塞窗口逐渐增大
- 拥塞避免(congestion avoidance): 从感知到拥塞开始, 拥塞窗口缓慢增大
- 快重传(fast retransmit): 从丢失报文段开始, 不断重复确认丢失报文段, 使得尽快重传报文段
- 快恢复(fast recovery): 从发现快重传开始, 缩小一半拥塞窗口
一般情况下慢开始
和拥塞避免
一起使用, 快重传
和快恢复
一起使用
慢开始
和拥塞避免
快重传
和快恢复
注:
- ssthresh是指慢开始门限, 也就是慢开始算法切换掉的分割线
- 慢开始算法的拥塞窗口起始值是1
- 发送方窗口上限值 = Min [ rwnd, cwnd ]
TCP连接管理
建立连接
这里为什么需要三次握手, 而不是两次握手呢?
主要是从服务端角度考虑, 目的是为了不浪费服务端资源
考虑两次握手的情景, 客户端发送了请求连接(1), 但是延迟了, 于是客户端再发送一次请求连接(2), 并且完成了通信和释放了连接(2), 之后服务端收到了连接(1)并建立连接, 但是客户端已经不需要连接(1)了, 所以连接(1)会造成服务端网络资源的浪费
但是使用三次握手会引来另外一个问题: 客户端资源的浪费
主要是因为客户端发送第三次握手过后, 客户端就可以发送数据了, 但是连接是否建立成功还不一定, 所以就有可能造成客户端资源的浪费
释放连接
TCP连接建立和释放过程中, 每一步报文段分别发生丢失我们要怎么处理呢?
关键在于理解: 无论客户端还是服务端, 只要是状态变更的发起者, 那么就要负责重传服务
例如1: 在TCP连接建立过程中, 客户端从CLOSED到SYN-SENT, 状态发生变更发起者是客户端, 客户端就要负责失败重传; 服务端从LISTEN到SYN-RCVD, 状态发生变更发起者是服务器, 服务端要负责失败重传
例如2:在TCP连接释放过程中, 客户端从ESTABLISHED到FIN-WAIT1, 状态发生变更发起者是客户端, 客户端份负责重传; 服务端从CLOSE-WAIT到LAST-ACK, 状态发生变更发起者是服务端, 服务端负责重传