По данным mptcp.io на 1 ноября 2023 года в глобальной сети функционируют около 350 тысяч ресурсов с поддержкой Multipath TCP (далее - MPTCP).
Ранее уже был проведен некоторый анализ внедрения стандарта.
И, если коротенечко, то MPTCP позволяет использовать несколько каналов связи. Например на смартфоне использовать одновременно WiFi и сотовую сеть.
Предназначен для решения задачи улучшения производительности (утилизации каналов) и надежности передачи данных в сети.
Другим инструментом решения этой задачи можно считать QUIC (пример реализации).
Отличие между MPTCP и QUIC заключается в том, что MPTCP работает поверх протокола TCP и позволяет использовать несколько сетевых путей для передачи данных, в то время как QUIC работает поверх протокола UDP и обеспечивает улучшенную производительность и надежность передачи данных через множество оптимизаций (как TCP, только с хуками). Как-то писал о механизмах надежной передачи данных.
Преимущества MPTCP включают возможность использования нескольких сетевых путей для улучшения производительности и надежности передачи данных.
Преимущества QUIC включают улучшенную производительность и надежность передачи данных за счет использования протокола UDP и оптимизаций, таких как мультиплексирование и быстрое установление соединения.
Некоторое сравнение с численными показателями имеется.
История развития стандарта MPTCP стартует с 2009 года. IETF свободно предоставляет полный комплект документов к ознакомлению. И есть варианты блекджеком и формулами.
Для меня еще интересны ресурсы: multipath-tcp.org и еще их код из репозитория.
Стандарт давно подвезли в iOS, ядро Linux, в документацию Red Hat и т.д.
И вот в Go, начиная с версии 1.21, MPTCP доступен в стандартной библиотеке. Методы
клиент
func (*Dialer) SetMultipathTCP(enabled bool)
func (*Dialer) MultipathTCP() bool
сервер
func (*ListenConfig) SetMultipathTCP(enabled bool)
func (*ListenConfig) MultipathTCP() bool
По умолчанию не используется. И может быть использован, только если ядро поддерживает.
On Linux, the net package can now use Multipath TCP when the kernel supports it. It is not used by default. To use Multipath TCP when available on a client, call the
Dialer.SetMultipathTCP
method before calling theDialer.Dial
orDialer.DialContext
methods. To use Multipath TCP when available on a server, call theListenConfig.SetMultipathTCP
method before calling theListenConfig.Listen
method. Specify the network as"tcp"
or"tcp4"
or"tcp6"
as usual. If Multipath TCP is not supported by the kernel or the remote host, the connection will silently fall back to TCP. To test whether a particular connection is using Multipath TCP, use theTCPConn.MultipathTCP
method.In a future Go release we may enable Multipath TCP by default on systems that support it.
Но, в общем и целом, его использование может выглядеть так:
сервер
package main
import (
"context"
"errors"
"flag"
"fmt"
"io"
"log/slog"
"net"
"os"
)
func main() {
log := slog.New(slog.NewTextHandler(
os.Stdout,
&slog.HandlerOptions{AddSource: true},
))
log.Info("starting server")
// проверка поддержки mptcp
lc := &net.ListenConfig{}
if lc.MultipathTCP() {
log.Error("multipathTCP should be off by default")
os.Exit(1)
}
log.Info("mptcp supported")
// прямо включить поддержку mptcp
lc.SetMultipathTCP(true)
ln, err := lc.Listen(context.Background(), "tcp", ":8080")
if err != nil {
log.Error(err.Error())
os.Exit(1)
}
log.Info("listening on port 8080")
for {
conn, err := ln.Accept()
if err != nil {
log.Error("conn", "error", err)
continue
}
go func() {
defer conn.Close()
// проверка поддержки mptcp
isMultipathTCP, err := conn.(*net.TCPConn).MultipathTCP()
fmt.Printf("accepted connection from %s with mptcp: %t, err: %v\n", conn.RemoteAddr(), isMultipathTCP, err)
for {
buf := make([]byte, 1024)
n, err := conn.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
return
}
log.Error("read", "error", err)
os.Exit(1)
}
if _, err := conn.Write(buf[:n]); err != nil {
log.Error("write", "error", err)
os.Exit(1)
}
}
}()
}
}
клиент
package main
import (
"fmt"
"log/slog"
"net"
"os"
"time"
)
func main() {
log := slog.New(slog.NewTextHandler(
os.Stdout,
&slog.HandlerOptions{AddSource: true},
))
log.Info("starting client")
// проверка поддержки MPTCP
d := &net.Dialer{}
if d.MultipathTCP() {
log.Error("multipathTCP should be off by default")
os.Exit(1)
}
// включение MPTCP
d.SetMultipathTCP(true)
if !d.MultipathTCP() {
log.Error("multipathTCP is not on after having been forced to o")
os.Exit(1)
}
log.Info("mpTCP enabled")
c, err := d.Dial("tcp", "127.0.0.1:8080")
if err != nil {
log.Error("multipathTCP should be off by default")
os.Exit(1)
}
defer c.Close()
tcp, ok := c.(*net.TCPConn)
if !ok {
log.Error("struct is not a TCPConn")
os.Exit(1)
}
// Действительно ли установленное соединение поддерживает mptcp
mptcp, err := tcp.MultipathTCP()
if err != nil {
log.Error("multipathTCP should be off by default")
os.Exit(1)
}
log.Info("outgoing connection from 127.0.0.1:8080 with mptcp")
// mptcp нет поддержки
if !mptcp {
log.Error("outgoing connection is not with MPTCP")
os.Exit(1)
}
for {
snt := []byte("MPTCP TEST")
if _, err := c.Write(snt); err != nil {
log.Error("write", "error", err)
os.Exit(1)
}
b := make([]byte, len(snt))
if _, err := c.Read(b); err != nil {
log.Error("read", "error", err)
os.Exit(1)
}
fmt.Println(string(b))
time.Sleep(time.Second)
}
}
За основу взят код из статьи.
Комментарии (6)
darkwrat
08.11.2023 05:09+2Можно включить MPTCP без пересборки приложения, используя переменные окружения.
% GODEBUG=multipathtcp=1 go run mptcp.go & [1] 70247 % mptcpize run curl 'http://127.0.0.1:8080/' conn.MultipathTCP() = (true, <nil>)
Исходник сервера
package main import ( "context" "fmt" "log" "net" "net/http" ) type ctxKey uint8 const( ctxHttpConnKey = ctxKey(iota) ) func ctxHttpSaveConn(ctx context.Context, conn net.Conn) context.Context { return context.WithValue(ctx, ctxHttpConnKey, conn) } func ctxHttpLoadConn(ctx context.Context) *net.TCPConn { return ctx.Value(ctxHttpConnKey).(*net.TCPConn) } func main() { serv := &http.Server{ Addr: ":8080", ConnContext: ctxHttpSaveConn, Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn := ctxHttpLoadConn(r.Context()) mptcp, err := conn.MultipathTCP() fmt.Fprintf(w, "conn.MultipathTCP() = (%t, %v)", mptcp, err) }), } log.Fatal(serv.ListenAndServe()) }
PrinceKorwin
08.11.2023 05:09Скажите, а почему выбрали Go для такой задачи? Не мешает ли его GC периодическими просадками?
Dreddsa Автор
08.11.2023 05:09+2Здравствуйте, потому что:
люблю писать на Go
статья о добавлении в стандартную библиотеку функционала MPTCP, в бою не пробовал.
При этом, на мой дилетантский взгляд, GC может помешать только если нужна потребность в обработке данных в режиме реального времени (типа систем реального времени), в остальном GC никому не мешает, а даже помогает.
itmind
Получается, что например, если у меня две сетевых карты, каждая подключена к интернету через своего провайдера и я запущу такой сервер на Go, то все запросы на любой сетевой интерфейс будут идти в этот Go-сервис и ответ этот сервис будет слать через любой из двух сетевых интерфейсов выбрав по определенному алгоритму? Или в чем смысл такого сервера?
С клиентом более менее понятно, например можно грузить большой файл одновременно и через WiFi и через сотовую сеть.
keylase
Всё так, мультиплексирование каналов возможно с обеих точек- и как с клиента, и так и с сервера. Основное требование- поддержка на уровне ядра ОС данного протокола.
igorsd
Смысл на сервере в том, что сервер должен уметь отправлять клиенту данные в рамках одного соединения на разные адреса клиента. При это у самого сервера совершенно необязательно должно быть несколько интерфейсов.