Tcp协议作为传输层的重要协议之一,想必每个稍有网络编程知识的人都不会感觉到陌生,三次握手/四次挥手这些基本概念也都是耳熟能详。但是当你们进行具体的网络编程的时候发现有很多事情并没有想象中的那么简单,会遇到很多·奇奇怪怪·的问题,说白了还是对于tcp协议的了解太过浮于表面。作为一个开发人员而言可能经常会去网上搜索一下有没有相关答案,但是即便问题解决了也是知其然不知其所以然。而不停的搜索答案试错是一种十分低劣的解决问题手法,个人观点认为当你遇见一个陌生的问题时如果时间允许的话先去了解它,这样解决问题会事半功倍(虽然前期效率可能会低一些)。
为了我们以后在网络编程的时候少挖一些坑,我们来仔细扒一扒tcp协议,下面的内容都引用于《计算机网络技术自上向下》第六版,话不多说开整。
一、tcp协议的数据结构
上图是tcp的段数据结构:
source port:数据来源端口 16bit
dest port:数据目的端口 16bit
sequence number:数据序列号 32bit
acknowledge number:数据应答编号 32bit
Header Length:头部长度 4bit
Unused:保留位 6bit
flage filed:标识位 6bit
receive window:接受窗口大小 16bit
internet checksum:校验位 16bit
urgent data pointer: 16bit
options:可选数据域 长度可变
data: 负载数据 长度可变
一般情况下options的长度为0,此时tcp协议数据段头部为20bytes,相比udp协议数据段头部多12bytes。这里要对数据序列号和确认号进行一下单独的说明,所谓的序列号与直观印象上的报文编号(1、2、3、4)这种不一样,我们举一个例子会直观一些比较:
例子: Alice 向 Bob 发送一个4M的文件:
假设Alice 的Seq起始值是 0,
第一个报文:
Seg0( Alice send to Bob): ....|Seq 0 | .... | Data (0~999 bytes)
Ack0 (Bob to Alice): ....| Ack 1000 ....
第二个报文
Seg1( Alice send to Bob): ....|Seq 1000 | Data (1000~2999 bytes)
Ack2 (Bob to Alice): .... Ack 3000 ....
... ...
细心的大佬就会发现了,这里的序列号和确认号实际上嘞是传输字节的序列,而不是报文的顺序,而确认号是接收方期待的下一个报文的序列号。
二、tcp的逻辑描述
我们在这里对tcp进行一个比较简单的逻辑上的描述,大家都知道网络层ip协议是管杀不管埋的,你报文能不能发送到?发送的完不完整?它一概不管,反正我是发了。所以tcp就在ip上封装了一层,来完成“全双工的可靠传输”--通过超时机制和冗余确认技术进行报文恢复,从而保证可靠传输。
tcp它不是一个停等协议,而是一个流传输协议。在整个tcp的传输流程中,有三个比较重要的事件:
Event 0:进程下发数据
Event 1:定时器超时
Event 2:Ack确认消息
下面附上书中的一段伪代码:
/* Assume sender is not constrained by TCP flow or congestion control, that data from above is less
than MSS in size, and that data transfer is in one direction only. */
NextSeqNum=InitialSeqNumber
SendBase=InitialSeqNumber
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)
break;
event: timer timeout
retransmit not-yet-acknowledged segment with smallest sequence number
start timer
break;
event: ACK received, with ACK field value of y
if (y > SendBase) {
SendBase=y
if (there are currently any not-yet-acknowledged segments)
start timer
}
break;
} /* end of loop forever */
这段伪代码很好理解,主要就是处理我们上面说的三种重要的event:
event0 检查是否开启定时器 + 发送数据 + 序列号递增;
event1 从没有收到确认信息的最小序列号报文开始重传 + 开启定时器;
event2 更新sendBase+ 如果当前没有未被确认的seg就开启下一轮计时
ps:这里面start time后就会重新计时,timer只有一个,不是每发送一个报文就要开启一个timer。
逻辑上是不是很简单?这是那个搞得人头晕的tcp吗?没错,它的抽象逻辑就是这么简单,就是这么简单的逻辑,实现了我们现在网络的基石协议之一。当然这只是一个概述而已,实际情况要比这个复杂的多,不要误以为tcp实现起来很简单。我们要在这个简单模型的基础上不断的去完善它,我下面说三种情况,大家来思考一下有什么启发,理解一下这个模型有什么精妙之处,看看这个模型是怎么来保证可靠传输的:
我们假设这三种情况之前都是正常的!
第一种情况:
1)ACK缺失,timout时间内未收到确认;
2)timeout 触发event1,这时从序列号最小的未被确认报文重传 seq92,重启timer;
第二种情况比较复杂那么一点点:
1)我们可以看到在第一个timeout 内 Seq 92、Seq 100的Ack并未返回;
2)当timeout时 event1 被触发,我们重传了Seq 92 ,重置了timer;
3) 在 第二次time out 之前 Ack 100和 Ack 120收到 ,此时没有未被确认的segments(这样我们就不需要重传seq100), 重置timer;
4)再次收到 Ack 120
ps:这里面有老铁会迷惑:我在第二个timeout中重传了Seq92,为对方返回的是Ack 120? 这个问题容我等下解答。
第三种情况:
1)发送seq 92和 Seq 100
2)ack100丢失
3)在timeout 前Ack120收到,此时无需进行重传。
ps:这里又多了个疑点,为啥Ack100丢失不用重传Seq92?明明在情况一种需要重传啊。
这都在下一回合解答。