家里现在有三条宽带,两条电信一条移动,下行都还可以,但上行每条只有 40-60M 。家里放了台 NAS ,人在外面的时候经常要从上面拉文件,速度就卡在其中一条线的上行上,几个 G 的东西要等很久。

先看三条线各自的情况(爱快线路测速):

img

三条加起来上行大约 150M 左右。

之前也试过爱快的多线负载,但那个是按连接分流的,开多线程测速确实好看,可单个连接(比如传一个大文件)只会走其中一条线,上行还是 40-60M 。

想让单个连接也叠加,就只能把这个连接的数据拆开,同时走三条线路发出去,再在另一头拼回来。所以另一头也得有个节点配合:家里这头负责拆,外面那头负责拼,思路和 MPTCP / OpenMPTCProuter 是一类的。拆和拼都在两头的节点上完成,NAS 和外面访问它的设备都不用动,跑的还是普通 TCP 。

具体是把连接里的数据切成 16 KB 一段、按顺序编号,三条线各自按实测的速度和延迟去领下一段(快的线领得多),对面按编号排好再交出去;哪条线断了或者卡住,没确认的段换到别的线重发,所以掉一条线连接也不会断。大概实现思路:

// 切成 16 KB 一段,头部写上编号和长度;每条线一个协程,发完一段再去领下一段,
// 所以快的线自然领得多。接收端按编号排好顺序再交出去。
func send(src io.Reader, lines []io.Writer) {
queue := make(chan []byte, len(lines))
go func() {
defer close(queue)
for seq := uint64(0); ; seq++ {
buf := make([]byte, 12+16<<10)
n, err := io.ReadFull(src, buf[12:])
if n > 0 {
binary.BigEndian.PutUint64(buf, seq)
binary.BigEndian.PutUint32(buf[8:], uint32(n))
queue <- buf[:12+n]
}
if err != nil {
return
}
}
}()
var wg sync.WaitGroup
for _, w := range lines {
wg.Add(1)
go func(w io.Writer) {
defer wg.Done()
for seg := range queue {
w.Write(seg)
}
}(w)
}
wg.Wait()
}

把这个思路用到我的自用面板上。两条电信有公网直连,移动那条没有公网,是靠 UDP 打洞连上的。下面是面板里家里这个节点的详情,两个地址是电信那两条,打洞 那一条就是移动:

图片

走聚合,三条连接就会一起用上:

图片

测试用的是 iperf3 单连接,测的是家里的上行。左边是不聚合(只走一条线),右边是三条聚合:

图片

不聚合:接收端平均 52.7 Mbits/sec

聚合:接收端平均 146 Mbits/sec

146 差不多就是三条上行加起来的 147 ,而且是单个 TCP 连接,不是多线程叠出来的。

实测从家里 NAS 往外拉一个大文件,单个下载也能稳在 17 MB/s 左右,也就是 136 Mbps 上下:

img

目前用下来,在外面从 NAS 拉文件明显快了很多。

主楼