Network-CS144 の 2 TCP Model

TCP模型

性质 行为
字节流 可靠的字节传输服务
可靠传输 确认机制保证了正确有序的传输;checksum检测错误数据,序列号检测丢失数据,滑动窗口式流量控制让值接收方过载;超时和重传机制
有序 有序同步传输(序列号和确认号机制)
用塞控制

TCP连接的唯一ID

1
[TCP Data src port , dest port][Ipv4 header, IP src addr, IP dest addr, protocol ID = 'TCP']

一般由src port , dest port][Ipv4 header, IP src addr, IP dest addr, protocol ID = 'TCP'组成唯一ID

Host A increments src port for every new connection

TCP picks ISN(initial seq number) to avoid overlap with previous connections with same ID(防止和上一个TCP连接的数据混淆)

TCP如何可靠?

停止与等待/滑动窗口

端到端原则

为什么Network不做更多的事情:

  • 压缩数据
  • reformat/imporove requests
  • Serve cached data
  • add security
  • Migrate connections across the network
  • or one of any of a huge number of other things

Network仅仅负责传输的事情,不负责其他的,如存储,如果像上面所说,把很多东西都交给了network来做,实际上很多做不到,或者说有缺陷,所以实际实现中,网络的设计应该满足端到端原则,只负责两边的传输相关功能

Finite State Machine

1
2
3
state1 =>([event causing state transition | actions taken on state transition]) state2

state1 =>[event | action] state3

flow control(流量控制)

流量控制要解决的问题:要解决的问题是发送方和接收方速度差异不一致问题,两种方案:

  • Stop and wait
  • slide window

举个例子:

sender的发送速率: 500, 000 packets/second

receive的接收速率r: 200, 000 packets/second

如果全速发送,将会丢包40%

流量控制:不要发送超过接收方可以处理的数据包

idea:接收方可以发送反馈,这里有两种方式

  • 停止等待 (其实是window size = 1的滑动窗口)
  • 滑动窗口

停止等待:

  • 任意时间链路中最多仅有一个packet
  • 发送者每次发送一个
  • 接收方收到后返回响应
  • 当接收方收到响应,再次发送新的packet
  • 一旦超时,重新发送当前数据
image-20230611121206723

简单停止等待的🌰:

image-20230611121246484

case 1: 没有信息丢失

case 2: req 丢失,timeout 重新发送

case 3: response 丢失, timeout 重新发送

case 4: 前面一个丢失的数据报仅仅是delay了,这样就会有重复

solution: Use 1-bit counter in data and acknowledgements

​ Receiver can tell if new data or duplicated

image-20230611121527344

这样间隔的数据报counter位是不一样的

1bit基于两个假设:

  • 延迟不会超过多个timeout,这样间隔的数据counter位是一样的
  • 网络本身并复制packet,因为如果发送重复packet,那么前后两个packet本身是一致的,但是由于计数却是不一致的,这样两个packet就会同时存在

滑动窗口

考虑一个发送数据包的🌰:

上海 => 北京

网络性能上限是 10Mb/s

RTT = 50ms

Ethnet的传输速度是12kb/s

这样1s可以发送20次,总共可以发20 * 12 = 240kb/s

240kb/s/10Mb/s = 240/10, 000 = 2.4%

假如使用滑动窗口(这里考虑发送方和接收方窗口大小一致):我们可以使用一个可以达到新能瓶颈的window size

240 * 42 = 1080,大约48个window size即可充分使用整个网络带宽

滑动窗口:

  • Gerneration of stop-and-wait: allow multiple un-packed segments
  • Bound on number of un-packed segments, called window
  • Can keep pipe full (充分使用整个网络带宽)

Sliding window Sender

  • 窗口内的每个segment都有一个序列号
  • 发送方维护三个变量
    • send window size(sws)
    • Last acknowledgment received (LAR)
    • Last segment sent(LSS)
  • Maintain invariant: (LSS - LAR) <= SWS
  • 缓存窗口内的segments
  • 当有新的确认到来,扩张窗口

Sliding window Receiver

  • 维护三个变量
    • Receive window size(sws)
    • Last acceptable segment (LAS)
    • Last segment received(LSR)
  • Maintain invariant: (LAS - LSR) <= RWS
  • 如果接受的Packet小于LAS,回复确认
  • 累计确认:如果收到了1,2,3,5,则回复ack = 3

比如:RWS=5, LSR = 3, 那么本次将会接收4 ,5 ,6, 7, 8, 如果有10,是不会接收的

RWS, SWS and Seq Space

  • RWS >= 1, SWS >= 1, RWS <= SWS
  • if RWS = 1, go back N protocol, need SWS + 1 seq numbers
  • if RWS = SWS, need 2SWS seq numbers
  • Generally need RWS + SWS seq numbers

TCP流量控制:

  • 接收方 advertises RWS using window field
  • Sender can only send data up to LAR + window

累计确认:累积确认这个概念应该不只适用于TCP协议,也适用其他层,比如链路层。

一般地讲,如果发送方发了包1,包2,包3,包4;接受方成功收到包1,包2,包3。那么接受方可以发回一个确认包,序号为4(4表示期望下一个收到的包的序号;当然你约定好用3表示也可以),那么发送方就知道包1到包3都发送接收成功,必要时重发包4。一个确认包确认了累积到某一序号的所有包,而不是对每个序号都发确认包。

具体到TCP,它对字节编号。比如发送方发了包1,包2,包3;包1含字节0到10,包2含字节11到20,包3含字节21到30。接受方成功收到包1,包2。那么接受方发回一个包含确认序号21的包,发送方就知道字节0到20(包1,包2)都成功收到,必要时要重发的只需从字节21开始。

重传策略

现在已经有了window size的概念,累计确认的概念, 每个Packet都有自己的定时器

如果定时器超时怎么办?

  • Go-back-N: 一旦一个包丢失了将会重发整个窗口数据,本质上是因为接收方的窗口大小仅仅为1
  • 选择重传:一旦一个包丢失了只会重发整个窗口数据丢失的那个包

https://blog.csdn.net/qq_44807642/article/details/103054914 https://zhuanlan.zhihu.com/p/589068775 https://blog.csdn.net/u011617742/article/details/50387670#:~:text=%E5%9C%A8%E8%BF%94%E5%9B%9EN%E5%8D%8F%E8%AE%AE%E4%B8%AD%EF%BC%8C%E5%8F%91%E9%80%81%E7%AA%97%E5%8F%A3%E5%A4%A7%E5%B0%8F%E5%BF%85%E9%A1%BB%3C%3D2m-1%2C%E6%8E%A5%E6%94%B6%E7%AA%97%E5%8F%A3%E5%A4%A7%E5%B0%8F%E5%A7%8B%E7%BB%88%E4%B8%BA1%E3%80%82,%E6%9C%AC%E8%B4%A8%E5%B0%B1%E6%98%AF%E7%AA%97%E5%8F%A3%E5%A4%A7%E5%B0%8F%E4%B8%8D%E8%83%BD%20%E8%B6%85%E8%BF%87%E5%BA%8F%E5%8F%B7%E8%83%BD%E8%A1%A8%E7%A4%BA%E7%9A%84%E8%8C%83%E5%9B%B4%EF%BC%8C%E4%BE%8B%E5%A6%82%E6%9C%80%E5%A4%A7%E5%BA%8F%E5%8F%B7%E6%98%AF3%EF%BC%8C%E7%AA%97%E5%8F%A3%E6%98%AF6%E7%AA%97%E5%8F%A3%E6%AF%94%E6%9C%80%E5%A4%A7%E5%BA%8F%E5%8F%B7%E5%A4%A7%EF%BC%8C%E4%BC%9A%E5%87%BA%E7%8E%B00%EF%BC%8C1%EF%BC%8C2%2C3%2C0%2C1%E8%BF%99%E6%A0%B7%E7%9A%84%E7%AA%97%E5%8F%A3%EF%BC%8C%E4%BD%A0%E6%97%A0%E6%B3%95%E5%88%A4%E6%96%AD%E6%8E%A5%E6%94%B6%E7%AA%97%E5%8F%A3%E8%BF%94%E5%9B%9E%E7%9A%84ACK0%E6%98%AF%E4%BB%A3%E8%A1%A8%E7%AC%AC%E4%B8%80%E4%B8%AA0%E6%94%B6%E5%88%B0%E8%BF%98%E6%98%AF%E7%AC%AC%E4%BA%8C%E4%B8%AA0%E6%94%B6%E5%88%B0%E3%80%82

TCP header

TCP的Setup & Teardown

可靠的通信依赖于通信双方的状态

问题是链接建立的时候如何维护这个状态?

当断连的时候如何清理这些状态?

Cleaning Up Safely

  • Problems with closed socket
    • What if final ack is lost in the network
    • What if the same port pair is immediately resued for a new connection ?
  • Solution: 'active' closer goes into TIME WAIT
    • Active close is sendingFIN before receving one
    • Keep socket around for 2 MSL (twice the 'maximum segment lifetime')
  • Can pose problems with servers
    • OS has too many socketd in TIME WAIT, slows things down
    • Hack: Can send RST and delete socket, so SO_LINGER socket option to time 0
    • OS won't let you re-start server because port still in use (SO_REUSEADDR) option lets you re-bind used port number.

TCP🌰:

不管是发送方还是接收方都会维护一个序列号(用于同步自己发送的数据)和确认号(用于同步对方的序列号),即使是像建立连接的时候数据段长度为0,但是对应的序列号和确认号也要消耗掉一个序号(即自增)

image.png

来看一个🌰:以下都是相对序列号

img

客户端 => 服务端: [SYN], seq = 0, len = 0, syn = 1, len = 0 (客户端传递seq希望和服务端同步自己的数据, 同时消耗掉客户端一个序列号,下一次客户端序列号就是从1开始)

客户端 <= 服务端: [SYN, ACK] , seq = 0, ack = 1, syn = 1, len = 0 (服务端回应同步,传递seq希望和客户端同步自己的数据, 同时消耗掉服务端一个序列号, 下一次服务端序列号就是从1开始)

客户端 => 服务端: [ACK], ack = 1, seq = 1, len = 0(客户端回应同步)

开始发送数据

客户端 => 服务端:[ACK], seq = 1, len = 474, ack = 1(ack用于同步对方的消息)

客户端 <= 服务端:[ACK], seq = 1, ack = 475(ack用于同步对方的消息, 表示我已经收到你的前474个数据包),服务端无响应数据,仅仅是ack数据包

客户端 <= 服务端:[ACK], seq = 1, ack = 475, len = 1448, next seq = 1449, 是对上一次的客户端请求的一次tcp响应包

客户端 <= 服务端:[ACK], seq = 1449, ack = 475, len = 1448, next seq = 2897, 依旧是对上一次的客户端请求的一次tcp响应包

客户端 <= 服务端:[ACK], seq = 2897, ack = 475, len = 1334, next seq = 2897 + 1334, 还是对上一次的客户端请求的一次tcp响应包

上面的序列号是相对的,所以看起来都是从0开始,但是实际上起始序列号是随机的,为什么要初始化起始序列号?

  • 避免偶然性,防止意外乱入,但也不是绝对安全的TCP连接时,随机产生初始化序列号的原因 - 知乎 (zhihu.com)
  • 安全性,防止恶意插入,因为如果起始序列号是固定的活着容易猜到的,在网络代理中,抓到tcp包,即可插入自己的数据,但是实际上几乎所有代理工具都无法解析到tcp的起始序列号。

https://www.cnblogs.com/163yun/p/9552368.html

终断连接的方式:

不干净中端:由于特殊原因(像断电)直接中断,TCPConnection发送或接收到一个首部字段中的RST标志位被设置的segment 。这种情况下,inbound和outbound的ByteStream都处于error state,并且active()方法可以马上返回false 。

干净中断:在没有error的情况下关闭(active()=false)。这种情况可以尽可能地保证两个字节流都完全可靠地交付到接收对等方由于两将军问题,不可能保证对等方都能完全干净关闭连接,但是可以非常接近。

如何尽量做到干净中断:

  1. 对于发送方来说,应用层读取结束(如EOF)且数据缓冲区(byte_stream)为空,此时可以发送FIN报文
  2. 对于接收方来说,只能被动结束,在收到了FIN,且重组缓冲区为空(这个时候说明数据没问题,所以收到FIN并不代表输入结束,有可能发生乱序,这个时候不应该对FIN确认);对于应用层来说,如果byte_stream,表明整个读取输入流的结束

所以结合上面2点来看,在满足下面几个条件之后说明双方干净关闭:

  1. 对于接收方,输入流被完全确认,且数据没问题(Reassembler 缓冲区为空),

  2. 对于发送方。应用层读取结束,Bytesteam为空

  3. 发送方已经收到远方的ACK

  4. 条件3比较苛刻,因为一般发送FIN报文对时候_outstanding_segments不一定为空,为了满足这个条件有两种方案:

    1. 两个流都已经结束后 linger 一段时间,由于StreamReassembler为空不好直接判断,无法发送ack(ack只能确保收到了),我们可以让本地的TCPConnection等待一段时间,如果对等端没有重传任何东西,那么就可以相信对等端收到了ack。

    2. 被动关闭

      如果在TCPConnection发送FIN之前,TCPConnection的输入流就结束了(收到了FIN),那么这个TCPConnection在两个流结束后不需要 linger 。(因为FIN在发送ack之后,所以FIN的seqno大于之前发送的ack,所以对方对FIN的确认,就相当于确认了之前发送的所有ack)

A => B

B是接收方,对于B来说每个segment都会发送ack,但是最后一次的ack他并不知道A是否收到了,B不知道什么时候关闭,所以A和B在收到对方的FIN之后,共同等待一段时间。

当然有一种情况比较特殊,也就是说A发送了FIN之前,B关闭了它的ByteStream。具体地说,在TCPSender发送带FIN的segment之前,如果TCPConnection的入向stream已经早早地结束了,那么TCPConnection就不需要做等待一段时间再关闭连接的操作了。


Network-CS144 の 2 TCP Model
https://mingmingjiang1.github.io/emocoder/2023/06/11/计算机网络二/
作者
迷途知返
发布于
2023年6月11日
许可协议