在本文中,“上传”指 客户端(JS)将文件发送到服务端(Go),而“下载”则指 客户端从服务端获取文件。
文件下载的实现
Server 端(GO)
semaphore.go实现动态加权信号量分配,修改于golang.org/x/sync/semaphore;
dchan.go在semaphore.go的基础上实现了一种带背压的 channel,使用 Little’s Law 简单方法自动调整缓冲区大小。所实现的 Write 和 Read 均为 Context-aware 的阻塞函数,可同时具有多个消费者和生产者。
这两个包允许通过 interface 传入可修改权重,可实现动态调整任务优先级。
之所以这样做,是因为实践证明 Gstreamer 的 Datachannel 的 buffer 缓冲区很大,无限制地压入待发送数据会导致很难低成本无开销地丢弃无用数据。同时,高带宽下避免磁盘 IO 繁忙,预缓冲足够的数据;低带宽时避免过度预读占用内存。
以下为不同带宽下的表现。




附图表说明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| go func() { data := make([][]float64, 3)
for { select { case <-newPeer.ctx.Done(): return default: data[0] = append(data[0], float64(newPeer.sendChan.InUse())/1024/1024) data[1] = append(data[1], float64(newPeer.sendChan.Bps())/1024/1024) data[2] = append(data[2], float64(newPeer.sendChan.Limit())/1024/1024) for i := 0; i < len(data); i++ { if len(data[i]) > 100 { data[i] = data[i][len(data[i])-100:] } }
graph := asciigraph.PlotMany(data, asciigraph.Precision(2), asciigraph.Height(20), asciigraph.SeriesColors(asciigraph.Red, asciigraph.Green, asciigraph.Blue), asciigraph.SeriesLegends("allocated", "bps", "limit"), asciigraph.Caption("graph of "+uid)) fmt.Println(graph) time.Sleep(1000 * time.Millisecond) } } }()
|
探索 WebRTC DataChannel 在不同消息体大小下的传输速率极限
浏览器为 Microsoft Edge,版本 139.0.3405.111 (正式版本) (64 位);使用dd if=/dev/urandom of=randomfile.dat bs=1m count=4096生成 4GB 文件,通过 WebRTC Datachannel 传输,ICE candidate 为 localhost udp,文件块大小为 12KB,下载耗时 140s。



文件分块为 60KB,下载耗时约 170s。



在不修改底层实现的前提下,将分块设置为小于默认 MTU 大小的 1KB,并不能突破 Gstreamer 的 Datachannel 的传输极限。

尝试修改分块大小,观察数据。当分块大小为 4KB 时,前几次测试前期能够始终保持 8K messagesReceived/s,随后浏览器卡住,无法完成下载测试。重启浏览器后,多次测试表现大致相同。

Client 端
TODO
文件上传的实现
Server 端(GO)
combiner.go用于二叉合并,思想来自 blake。而blake3.go则是 blake3 的简单实现。
range.go待写,用于确定特定区间是否完成传输。
实现理由:为了实现一致性保证的断点续传,旧方案会先计算完整文件的哈希再上传,导致大文件上传时初始化阶段耗时长,上传进度始终为零,用户体感差。新方案改为分块计算哈希,边算边比较边传。
用于支持断点续传的临时文件
1 2 3 4 5 6 7 8 9 10 11
| RDF/1 Version: 1 Algorithm: <0..15> File-Size: <uint64> ; bytes Chunk-Size: <uint64> ; bytes Chunks: ceil(File-Size / Chunk-Size) Chunk-Status-Length: ceil(Chunks / 8) ; bytes Fingerprint-Length: L ; hex characters Fingerprint-Encoding: packed-nibbles (ceil(L/2) bytes, odd L -> low nibble padded) Trailer-Layout: [L:uint8][File-Size:uint64][Chunk-Size:uint64][Meta:uint8(V<<4|Alg)] Byte-Order: big-endian for all uint64
|
1 2 3 4 5
| +----------------------+----------------------+----------------------+------------------+ | Payload | Chunk-Status | Fingerprint | Trailer | | (0..N) | ceil(Chunks/8) B | ceil(L/2) B | 18 B | +----------------------+----------------------+----------------------+------------------+ ^ EOF
|
Client 端
TODO