Files
.github
app
core
extras
auth
correctnet
masq
obfs
outbounds
acl
speedtest
client.go
protocol.go
protocol_test.go
server.go
.mockery.yaml
acl.go
acl_test.go
dns_https.go
dns_standard.go
dns_system.go
interface.go
interface_test.go
mock_PluggableOutbound.go
mock_UDPConn.go
ob_direct.go
ob_direct_linux.go
ob_direct_others.go
ob_http.go
ob_socks5.go
speedtest.go
utils.go
utils_test.go
trafficlogger
transport
go.mod
go.sum
media-kit
scripts
.gitignore
CHANGELOG.md
Dockerfile
LICENSE.md
PROTOCOL.md
README.md
go.work
go.work.sum
hyperbole.py
logo.svg
platforms.txt
hysteria-dev/extras/outbounds/speedtest/client.go
2024-03-09 20:38:30 -08:00

126 lines
3.0 KiB
Go

package speedtest
import (
"fmt"
"io"
"net"
"sync/atomic"
"time"
)
type Client struct {
Conn net.Conn
}
// Download requests the server to send l bytes of data.
// The callback function cb is called every second with the time since the last call,
// and the number of bytes received in that time.
func (c *Client) Download(l uint32, cb func(time.Duration, uint32, bool)) error {
err := writeDownloadRequest(c.Conn, l)
if err != nil {
return err
}
ok, msg, err := readDownloadResponse(c.Conn)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("server rejected download request: %s", msg)
}
var counter uint32
stopChan := make(chan struct{})
defer close(stopChan)
// Call the callback function every second,
// with the time since the last call and the number of bytes received in that time.
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
t := time.Now()
for {
select {
case <-stopChan:
return
case <-ticker.C:
cb(time.Since(t), atomic.SwapUint32(&counter, 0), false)
t = time.Now()
}
}
}()
buf := make([]byte, chunkSize)
startTime := time.Now()
remaining := l
for remaining > 0 {
n := remaining
if n > chunkSize {
n = chunkSize
}
rn, err := c.Conn.Read(buf[:n])
remaining -= uint32(rn)
atomic.AddUint32(&counter, uint32(rn))
if err != nil && !(remaining == 0 && err == io.EOF) {
return err
}
}
// One last call to the callback function to report the total time and bytes received.
cb(time.Since(startTime), l, true)
return nil
}
// Upload requests the server to receive l bytes of data.
// The callback function cb is called every second with the time since the last call,
// and the number of bytes sent in that time.
func (c *Client) Upload(l uint32, cb func(time.Duration, uint32, bool)) error {
err := writeUploadRequest(c.Conn, l)
if err != nil {
return err
}
ok, msg, err := readUploadResponse(c.Conn)
if err != nil {
return err
}
if !ok {
return fmt.Errorf("server rejected upload request: %s", msg)
}
var counter uint32
stopChan := make(chan struct{})
defer close(stopChan)
// Call the callback function every second,
// with the time since the last call and the number of bytes sent in that time.
go func() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
t := time.Now()
for {
select {
case <-stopChan:
return
case <-ticker.C:
cb(time.Since(t), atomic.SwapUint32(&counter, 0), false)
t = time.Now()
}
}
}()
buf := make([]byte, chunkSize)
remaining := l
for remaining > 0 {
n := remaining
if n > chunkSize {
n = chunkSize
}
_, err := c.Conn.Write(buf[:n])
if err != nil {
return err
}
remaining -= n
atomic.AddUint32(&counter, n)
}
// Now we should receive the upload summary from the server.
elapsed, received, err := readUploadSummary(c.Conn)
if err != nil {
return err
}
// One last call to the callback function to report the total time and bytes sent.
cb(elapsed, received, true)
return nil
}