TCP概述
报文格式、发送方接收方状态机模型、流量控制、拥塞控制、三次握手四次挥手
TCP
什么是TCP连接:
- 用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。
TCP 四元组可以唯一的确定一个连接,四元组包括如下:
- 源地址
- 源端口
- 目的地址
- 目的端口
概述
- 点对点:
- 一个发送方,一个接收方
- 多播对于TCP来说是不可能的。(一个发送方传送给多个接收方)
- 可靠的、按顺序的字节流:
- 没有报文边界
- 管道化(流水线):
- TCP拥塞控制和流量控制设置窗口大小
- 发送和接收缓存
- MSS(MAX Segment Size):放入TCP报文段的最大应用层报文段大小。(应用层报文段的最大大小)
- MTU(Maximum Transmission Unit) = IP Header + TCP Header + MSS(应用层报文段)
- 全双工数据:
- 在同一连接中数据流双向流动
- 面向连接:
- 三次握手:在数据交换之前,通过握手(交换控制报文) 初始化发送方、接收方的状态变量
- TCP连接是一条逻辑连接,其共同状态仅保存在两个通信端系统的TCP程序中。
- TCP连接的组成包括:一台主机上的缓存、变量和与进程连接的socket,另一台主机上的另一组缓存、变量和与进程连接的socket
- 中间的网络元素如 路由器、交换机、中继器,没有为该连接分配任何缓存和变量。也即,不会维持TCP连接状态。
- 中间路由器对TCP连接完全视而不见,它们看到的是数据报,而不是连接。
- 有流量控制:
- 发送方不会淹没接收方
报文结构
源port、目的port:
- 多路复用/分解来自或送到上层的数据
检验和check sum
序号sequence number (32bit) and 确认号acknowledgement number (32bit)
- 用于实现可靠数据传输服务
首部长度header length
- TCP Header 长度
标志
- RSF位的组合代表了是第几次握手
- …
接收窗口receive window
- 用于流量控制,指示接收方愿意接受的字节数量
选项options
序号、确认号
- 好图
- 序号:
- 报文段首字节的在字节流的编号
- 即 一个报文段的序号 是该TCP报文段的数据部分的首字节的字节流编号
- 确认号:
- 期望从另一方收到的下一个字节的序号
- 累计确认(与SR协议不同,SR协议是非累计确认)
- ack number:已经收到了[begin,ack-1]的所有bytes,期待收到的下一个byte是ack 。 即期待收到的下一个TCP报文段的body的起始字节的序号是ack
- TCP没有规定 接收方如何处理乱序的报文段
- 丢弃
- 缓存。(实际中使用的)
- 对于一条TCP连接,双方随机选择初始序号
- ?
- telnet例子
- 捎带:B到A的数据的ack确认被 捎带(装载) 在一个B到A的数据包文段中。
- 顺带一句:初始序号随机 防止老连接的包造成影响。不过对方是如何知道自己的初始序号的?
往返延时(RTT)和 TCP超时
- 怎样设置TCP超时?
- 比RTT要长
- 但RTT是变化的
- 太短:太早超时
- 不必要的重传
- 太长:对报文段丢失
- 反应太慢,消极
- 比RTT要长
往返时间RTT预估
- 怎样估计RTT?
- SampleRTT:测量从报文段发出到收到确认的时间
- 如果有重传,忽略此次测量
- SampleRTT会变化,因此估计的RTT应该比较平滑
- 对几个最近的测量值求平均,而不是仅用当前的SampleRTT
- SampleRTT:测量从报文段发出到收到确认的时间
- EstimatedRTT = (1 - α) * EstimatedRTT + α * SampleRTT
- 表示 当前最近一段时间的往返平均值
- 指数加权移动平均:越近的样本权重越大
RTT变化 预估
- DevRTT = (1-β) * DevRTT + β * |SampleRTT - EstimatedRTT|
- 表示 当前采样值SampleRTT 离 估算平均值EstimatedRTT 的偏差程度 的一个平均值
- 同样采用指数移动加权平均。
设置和管理 重传超时间隔
- TimeoutInterval = EstimatedRTT + 4*DevRTT
TCP: 如何rdt?(可靠数据传输)
- TCP在IP不可靠服务的基础上建立了rdt
- pipeline的报文段
- GBN + SR
- 累计确认:像GBN
- 单个定时器:发送方只设置一个定时器(GBN),且该定时器只和最老的段关联.像GBN
- TCP没有规定 接收方如何处理乱序的报文段
- 通过以下事件触发重传
- 超时:当超时的时候 只**重放一个段(SR)**,且是最老的段。
- 冗余ACK:触发快速重传机制
- 例如收到ACK50之后,又收到3个ACK50
- pipeline的报文段
TCP发送方(简化)
- 接下来考虑简化的TCP发送方
- 忽略重复的确认
- 忽略流量控制和拥塞控制
状态机
发送窗口 : [base,next-1]
- 发送窗口 size = next - base
- next : 即将发送的报文段,也即紧挨着发送窗口的下一个报文段
- next-1 : 最新的 已经发送 未确认的报文段
- base : 最老的 已发送 未确认的报文段
- base - 1 : 最新的被确认的分组
ack number:
- 接收方接收到的顺序到来的最后一个字节 + 1(期待)。
- 发送给发送方,ack告诉发送方[begin,ack-1]已经被确认。
- 发送方的base移动到ack,因此接收到的ack是发送窗口的base
文字版 Action and Event
data received from application : 从应用层接收数据
- 用nextseq创建报文段
- 序号nextseq为报文段首字节的字节流编号
- 如果还没有运行定时器,启动定时器
- 定时器与最老未确认的报文段关联(即 定时器与base分组关联)
timeout : 定时器超时:
- 重传后沿最老的报文段(即 重传base)
- 只重传一个base分组(类似SR)
- 重启timer
- 重传后沿最老的报文段(即 重传base)
ACK received , with ACK field and value y收到确认:
- 如果收到的是对尚未确认的报文段的确认(接收到的ACK > 发送窗口的base)
- 发送窗口的后沿向右侧移动,更新已被确认的报文序号。即 base 右移到ack的位置
- base < next : 如果当前还有已经发送但没被确认的报文段,重启timer。
- base = next : 如果当前没有已经发送但没被确认的报文段,无需启动timer。
- 如果收到的是对尚未确认的报文段的确认(接收到的ACK > 发送窗口的base)
如下就要启动对报文段7的timer
code
- 三件event 和 相应 action
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21NextSeqNum = InitialSeqNum
SendBase = InitialSeqNum
loop (forever) {
switch(event)
event: data received from application above
create TCP segment with sequence number NextSeqNum
if (timer currently not running)
start timer pass segment to IP
NextSeqNum = NextSeqNum + length(data)
event: timer timeout
retransmit not-yet-acknowledged segment with smallest sequence number
start timer
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
}
} /* end of loop forever */
例子
TCP接收方
- RCF对于接收方返回ACK的建议。
(1)(2)
(3)
(4)
补充 : TCP发送方 之 快速重传机制
- 超时周期往往太长:
- 在重传丢失报文段之前的延时太长
- 通过重复的ACK来检测报文段丢失
- 发送方通常连续发送大量报文段
- 如果报文段丢失,通常会引起多个重复的ACK
- 如果发送方收到同一数据的3个冗余ACK,重传最小序号的段:
- 快速重传:在定时器过时之前重发报文段
- 它假设跟在被确认的数据后面的数据丢失了
- 第一个ACK是正常的;
- 收到第二个该段的ACK,表示接收方收到一个该段后的乱序段;
- 收到第3,4个该段的ack,表示接收方收到该段之后的2个,3个乱序段,可能性非常大段丢失了
例子
补充上文中的发送方简化版状态机
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase = y
if (there are currently not-yet-acknowledged segments)
start timer
else
stop timer
}
else {
// ACK = sendBase
increment count of dup ACKs received for y
// ACK重传
if (count of dup ACKs received for y = 3) {
resend segment with sequence number y
}
}
流量控制
概念
- 流量控制:接收方控制发送方,不让发送方发送的太多、太快以至于让接收方的缓冲区溢出。
- TCP接收方 通过填写TCP段头部的receiving window field来告知 TCP发送方 自己的空闲buffer大小。
- 进而控制TCP发送方的发送速度
流程
TCP接收方 通过填写TCP段头部的receiving window field来告知 TCP发送方 自己接收Buffer的空闲大小.
- RcvBuffer大小通过socket选项设置 (典型默认大小为4096 字节)。
- 很多操作系统自动调整RcvBuffer。
TCP发送方知道接收方的receiving window之后,通过限制未确认(“inflight”)字节的个数≤接收方发送过来的rwnd值,来保证接收方不被淹没。(意图控制发送方新发送的(新要送入接收方Buffer的)data size < free buffer space)
接收buffer , recvWindow(free空间) 以及 , data to read by app
连接管理 (即 TCP三次握手 四次挥手)
- 连接关闭的两军问题
TCP连接建立
- TCP连接的建立会显著地增加人们感受到的时延。
- 连接建立的本质
- 知道要和对方通信
- 双方准备通信资源 如 缓冲区
- 控制变量置位 如连接初始序号 和初始化的recvBuffer大小
三次握手
流程:client的进程向和server的进程建立TCP连接
1. client向server发送SYN报文段
- 请求发起连接,并告知对方自己的初始序列号。
- 不含应用层数据
- SYN = 1
- seq = client_isn。
- client状态变化:CLOSE -> SYN_SENT
2. server向client发送SYNACK报文段(SYNACK segment)
- 收到了client的连接请求,收到了对方的初始化序列号,并同意建立连接,并告知对方自己的初始序列号。
- 不含应用层数据
- SYN = 1
- seq num = server_isn
- ACK = 1 , ack num = client_isn + 1
- server状态变化:LISTEN -> SYN_RCVD
3. client向server发送发送ACK报文段
- client告诉server,自己收到了对方对于连接建立的确认,以及收到了对方的初始化序列号。
- 可含应用层数据
- ACK = 1 , ack num = server_isn + 1
- client状态:SYN_SENT -> ESTABLISHED
- server收到client发送的这个ack报文段后:SYN_RCVD -> ESTABLISHED
三次握手问题
- 三次而非两次?见blog TCP2
TCP连接关闭
四次挥手
- 客户端,服务器分别关闭它自己这一侧的连接
- 将TCP连接分为两个方向,每个方向单独拆除。
- 发送FIN bit = 1的TCP段
- 一旦接收到FIN,用ACK回应
- 接到FIN段,ACK可以和它自己发出的FIN段一起发
送
- 接到FIN段,ACK可以和它自己发出的FIN段一起发
- 可以处理同时的FIN交换
- 主动关闭连接的,才有 TIME_WAIT 状态
TIME_WAIT问题
…
连接管理状态机
client的状态机
server的状态机
拥塞控制
见blog 拥塞控制
没有准确定义
网络拥塞特点
延时大
为了让网络达到有效的泵出 我的输入要泵入很多速率
拥塞时会重传没有必要重传的分组 从而让网络更慢
不加控制 马上不可控制 网络瘫痪
拥塞控制目的:
不是为了不发生拥塞,而是在不发生拥塞的情况下,尽可能地提高它的发送速率。
TCP采用端到端控制
(互联网架构将复杂性放在端上)
流量窗口 拥塞窗口
警戒值之后线性增长
警戒值之前指数增长
喝的大醉 滑动窗口变为1 警戒值变为发疯的一半
半倒不倒 3个冗余ack
喝的半醉 滑动窗口减为一半,进入拥塞避免,省了一个慢启动阶段。
慢启动阶段并不慢。很快 指数型增加
慢启动 很快 时间忽略不计
tcp公平
udp对tcp不友好 只顾着自己
返回的ack 是 最老的 未确认的字节的编号
滑动窗口协议GO BY N ,SR 窗口如何滚动 接收窗口作用 这部分内容很重要 对理解pipeline协议 提高效率的同时实现可靠传递 很重要
快速重传:在定时器到时之前 如果收到3个冗余的ack 就重传
快速重传的各种情况
捎带发送确认
流量控制目的:发送方发送的不至于太快,