diff --git a/cmd/netstack/main.go b/cmd/netstack/main.go index 95fcf73..c91a7ff 100644 --- a/cmd/netstack/main.go +++ b/cmd/netstack/main.go @@ -184,7 +184,7 @@ func main() { log.Printf("\n\n客户端 写入数据") cnt := 0 - for i := 0; i < 10; i++ { + for i := 0; i < 20; i++ { conn.Write(make([]byte, 1<<(5))) cnt += 1<<(5) //buf := make([]byte, 1024) diff --git a/tcpip/link/loopback/loopback.go b/tcpip/link/loopback/loopback.go index 8f7b463..08c990e 100644 --- a/tcpip/link/loopback/loopback.go +++ b/tcpip/link/loopback/loopback.go @@ -2,12 +2,10 @@ package loopback import ( "fmt" - "math/rand" "netstack/logger" "netstack/tcpip" "netstack/tcpip/buffer" "netstack/tcpip/stack" - "time" ) type endpoint struct { @@ -49,11 +47,11 @@ func (e *endpoint) WritePacket(r *stack.Route, hdr buffer.Prependable, payload b vv := buffer.NewVectorisedView(len(views[0])+payload.Size(), views) // TODO 这里整点活 在特定的情况下丢掉数据报 模拟网络阻塞 - rand.Seed(time.Now().Unix()) + //rand.Seed(time.Now().Unix()) //time.Sleep(time.Duration(rand.Intn(50)+50) * time.Millisecond) e.count++ - if e.count == -1 { // 丢掉客户端写入的第二个包 + if e.count == 10 { // 丢掉客户端写入的第二个包 logger.NOTICE(fmt.Sprintf("统计 %d 丢掉这个报文", e.count)) return nil } diff --git a/tcpip/transport/tcp/README.md b/tcpip/transport/tcp/README.md index 3e30dfd..8a92606 100644 --- a/tcpip/transport/tcp/README.md +++ b/tcpip/transport/tcp/README.md @@ -324,8 +324,27 @@ TCP 通过维护一个拥塞窗口(cwnd 全称 Congestion Window)来进行拥塞 ### 慢启动(slow start) +慢启动的意思是,加入网络的连接,一点一点地提速,不要一上来就突发流量挤占通道。 慢启动的算法如下: + +1. 连接建好的开始先初始化cwnd = 1,表明可以传一个 MSS 大小的数据。 +2. 每当收到一个 ACK,cwnd++; 呈线性上升 +3. 每当过了一个 RTT,cwnd = cwnd*2; 呈指数上升 +4. 设置一个慢启动阀值 ssthresh(slow start threshold),是慢启动和拥塞避免的一个临界值,当cwnd >= ssthresh时,就会进入拥塞避免阶段 + ### 拥塞避免(congestion avoidance) +当 cwnd >= ssthresh 时,就会进入“拥塞避免算法”。一般来说初始 ssthresh 的值是很大的,当 cwnd 达到这个值时后,算法如下: + +- 每当收到一个 ACK 时,cwnd = cwnd + 1/cwnd +- 每当过一个 RTT 时,cwnd = cwnd + 1 + ### 快速重传(Fast Retransmit) ### 快速恢复(Fast Recovery) + + +- 1988 年,TCP Tahoe 提出了 1)慢启动,2)拥塞避免,3)快速重传。 +- 1990 年,TCP Reno 在 Tahoe 的基础上增加了 4)快速恢复,是现有的众多拥塞控制算法的基础,被认为标准 tcp 拥塞行为。 +- 2004 年,TCP BIC(Binary Increase Congestion control),在Linux 2.6.8中是默认拥塞控制算法,用的是 Binary Search——二分查找来找拥塞窗口。 +- 2008 年,TCP CUBIC 是比 BIC 更温和和系统化的分支版本,其使用三次函数代替二分算法作为其拥塞窗口算法,并且使用函数拐点作为拥塞窗口的设置值。 Linux 2.6.19后使用该算法作为默认 TCP 拥塞算法,现在也是。 +- 2016 年,TCP BBR 是由 Google 设计,于 2016 年发布的拥塞算法,交替测量一段时间内的带宽极大值和时延极小值,将其乘积作为作为拥塞窗口大小,认为当网络上的数据包总量大于瓶颈链路带宽和时延的乘积时才出现了拥塞。 diff --git a/tcpip/transport/tcp/reno.go b/tcpip/transport/tcp/reno.go index f92f047..0b4cdad 100644 --- a/tcpip/transport/tcp/reno.go +++ b/tcpip/transport/tcp/reno.go @@ -1,6 +1,8 @@ package tcp -import "netstack/logger" +import ( + "netstack/logger" +) type renoState struct { s *sender diff --git a/tcpip/transport/tcp/snd.go b/tcpip/transport/tcp/snd.go index 7146d60..a911d26 100644 --- a/tcpip/transport/tcp/snd.go +++ b/tcpip/transport/tcp/snd.go @@ -506,8 +506,8 @@ func (s *sender) retransmitTimerExpired() bool { s.outstanding = 0 s.writeNext = s.writeList.Front() // 重新发送数据包 - logger.NOTICE("超时重发") - s.sendData() + logger.NOTICE("暂时关闭超时重发", s.rto.String()) + //s.sendData() return true } @@ -634,6 +634,7 @@ func (s *sender) leaveFastRecovery() { // Deflate cwnd. It had been artificially inflated when new dups arrived. s.sndCwnd = s.sndSsthresh s.cc.PostRecovery() + logger.NOTICE("退出快速恢复") } @@ -641,9 +642,43 @@ func (s *sender) leaveFastRecovery() { // 并根据RFC 6582(NewReno)中的规则确定是否需要重新传输 func (s *sender) checkDuplicateAck(seg *segment) (rtx bool) { ack := seg.ackNumber - //logger.NOTICE("注意测试", atoi(s.sndCwnd)) // 已经启动了快速恢复 if s.fr.active { + log.Fatal("启动了快速恢复") + if !ack.InRange(s.sndUna, s.sndNxt+1) { + return false + } + + // 如果它确认此快速恢复涵盖的所有数据,退出快速恢复。 + if s.fr.last.LessThan(ack) { + s.leaveFastRecovery() + return false + } + // 如果该tcp段有负载或者正在更新窗口,那么不计算这个ack + if seg.logicalLen() != 0 || s.sndWnd != seg.window { + return false + } + + // Inflate the congestion window if we're getting duplicate acks + // for the packet we retransmitted. + if ack == s.fr.first { + // We received a dup, inflate the congestion window by 1 + // packet if we're not at the max yet. + if s.sndCwnd < s.fr.maxCwnd { + s.sndCwnd++ + } + return false + } + + // A partial ack was received. Retransmit this packet and + // remember it so that we don't retransmit it again. We don't + // inflate the window because we're putting the same packet back + // onto the wire. + // + // N.B. The retransmit timer will be reset by the caller. + s.fr.first = ack + s.dupAckCount = 0 + return true } // 我们还没有进入快速恢复状态,只有当段不携带任何数据并且不更新发送窗口时,才认为该段是重复的。