经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
推荐两个网络复用相关的 Go pkg: cmux smux
来源:cnblogs  作者:小胖西瓜  时间:2024/2/23 8:57:35  对本文有异议

推荐两个网络复用相关的 Go pkg: cmux/smux

只写一下如何使用,不对实现进行大量描述,两个库的代码都比较精炼,花一会看一下就行。

  • cmux 对端口进行复用,单端口可以建立不同协议的连接(本质都是 TCP),如 TCP/TLS/HTTP/gRPC 或自定义协议
  • smux 对TCP连接复用,单TCP连接承载多条 smux stream

适用使用场景

  • cmux 一些对外只提供单端口的服务,比如一个端口同时提供 HTTP/HTTPS 功能,其实还能够更多
  • smux
    • 一些性能敏感的地方,比如大量TLS的短连接请求,对于频繁的握手非常消耗CPU,见性能压测
    • 反向连接,将 TCP 客户端抽象为服务端,方便如 HTTP/gRPC 的服务开发

cmux 的使用

借用一些官方示例,使用还是相对简单的,23456 端口同时提供了 gRPC/HTTP/tRPC 复用。

  1. // Create the main listener.
  2. l, err := net.Listen("tcp", ":23456")
  3. if err != nil {
  4. log.Fatal(err)
  5. }
  6. // Create a cmux.
  7. m := cmux.New(l)
  8. // Match connections in order:
  9. // First grpc, then HTTP, and otherwise Go RPC/TCP.
  10. grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc"))
  11. httpL := m.Match(cmux.HTTP1Fast())
  12. trpcL := m.Match(cmux.Any()) // Any means anything that is not yet matched.
  13. // Create your protocol servers.
  14. grpcS := grpc.NewServer()
  15. grpchello.RegisterGreeterServer(grpcS, &server{})
  16. httpS := &http.Server{ Handler: &helloHTTP1Handler{} }
  17. trpcS := rpc.NewServer()
  18. trpcS.Register(&ExampleRPCRcvr{})
  19. // Use the muxed listeners for your servers.
  20. go grpcS.Serve(grpcL)
  21. go httpS.Serve(httpL)
  22. go trpcS.Accept(trpcL)
  23. // Start serving!
  24. m.Serve()

自定义 Matcher

cmux 的实现上是对 payload 进行匹配,cmux.HTTP1Fast 是一个匹配函数,为内置的集中匹配函数中的一种,这类匹配函数可以同时设置多个。
内部将 net.Conn 的数据读入至 buffer 内,依次调用各个匹配函数对这个 buffer 进行分析,如果匹配成功则 httpL 被返回,httpS 服务收到请求。

所以我们可以自定义一些 Matcher,比如一些携带 Magic/字符串 头的数据

  1. const (
  2. PacketMagic = 0x00114514
  3. PacketToken = "xyz_token"
  4. )
  5. func PacketMagicMatcher(r io.Reader) bool {
  6. buf := make([]byte, 4)
  7. n, err := io.ReadFull(r, buf)
  8. if err != nil {
  9. return false
  10. }
  11. return binary.BigEndian.Uint32(buf[:n]) == PacketMagic
  12. }
  13. func PacketTokenMatcher(r io.Reader) bool {
  14. buf := make([]byte, len(PacketToken))
  15. n, err := io.ReadFull(r, buf)
  16. if err != nil {
  17. return false
  18. }
  19. return string(buf[:n]) == PacketToken
  20. }

使用上和 cmux.HTTP1Fast 相同,需要注意的是,net.Conn 头部的 Magic/Token 是和连接相关的,和业务数据无关在使用这些数据之前,需要先将其读取出来

  1. tcpMux := cmux.New(lis)
  2. magicLis := tcpMux.Match(PacketMagicMatcher)
  3. go func() {
  4. conn, _ := magicLis.Accept()
  5. buf := make([]byte, 4)
  6. io.ReadAtLeast(conn, buf, len(buf)) // Read header magic length
  7. // Handle data ...
  8. }()
  9. tcpMux.Serve()

多 mux 场景

需求:只开放一个端口 12345,需要支持

  • HTTP 协议的包下载
  • 基于 TLS 的 gRPC 服务
  • 基于 TLS 的自定义服务

分析:对于 HTTP 和 TLS 需要使用一个 mux 进行区分,TLS 中的 gRPC 和 自定义服务需要再通过一个 mux 区分

参考的实现(截取了一部分业务代码)

  1. // TCP 分流 http/tls
  2. tcpMux := cmux.New(lis)
  3. installerL := tcpMux.Match(cmux.HTTP1Fast())
  4. anyL := tcpMux.Match(cmux.Any())
  5. // tls.NewListener(anyL, ...)
  6. mtlsL, err := mTLSListener(anyL, tlsEnable, tlsCertPath, tlsKeyPath, tlsCAPath)
  7. if err != nil {
  8. return err
  9. }
  10. tlsMux := cmux.New(mtlsL)
  11. grpcL := tlsMux.Match(cmux.HTTP2())
  12. gwL := tlsMux.Match(gw.Matcher)

smux 的使用

还是放一些官方的简单示例

  1. func client() {
  2. // Get a TCP connection
  3. conn, err := net.Dial(...)
  4. if err != nil {
  5. panic(err)
  6. }
  7. // Setup client side of smux
  8. session, err := smux.Client(conn, nil)
  9. if err != nil {
  10. panic(err)
  11. }
  12. // Open a new stream
  13. stream, err := session.OpenStream()
  14. if err != nil {
  15. panic(err)
  16. }
  17. // Stream implements io.ReadWriteCloser
  18. stream.Write([]byte("ping"))
  19. stream.Close()
  20. session.Close()
  21. }
  22. func server() {
  23. // Accept a TCP connection
  24. conn, err := listener.Accept()
  25. if err != nil {
  26. panic(err)
  27. }
  28. // Setup server side of smux
  29. session, err := smux.Server(conn, nil)
  30. if err != nil {
  31. panic(err)
  32. }
  33. // Accept a stream
  34. stream, err := session.AcceptStream()
  35. if err != nil {
  36. panic(err)
  37. }
  38. // Listen for a message
  39. buf := make([]byte, 4)
  40. stream.Read(buf)
  41. stream.Close()
  42. session.Close()
  43. }

smux.Session 和 net.Conn 对应,smux.Stream 实现了 net.Conn 接口,所以使用起来和普通的连接无异。smux.Session 是双向的,Client/Server 的区分仅仅是内部的 Id 区别,这就为反向连接打下了基础

基于 smux 的反向连接

对于一个普通的 TCP 服务而言,A(client) -> B(server)。在 B 上 建立 gRPC/HTTP 服务是一件非常自然的事情。

在某些场景下,比如 A 在公网,B 在内网,不做公网映射的话,只能够 B(client) -> A(server)。但是这个情况下,B 上面的 gRPC/HTTP 的服务就不能直接建立了。

上面说过 smux.Session 再使用上时没有方向的,并且提供了和 net.Listener 相近的接口,如果将 smux.Session 封装实现 net.Listener,加上 smux.Stream 是 net.Conn,那么 B 连接 A 继续在 B 上建立 gRPC/HTTP 服务是可以的,内部感知不到具体的实现细节。

对 smux.Session 的封装如下

  1. type SmuxSession struct{ *smux.Session }
  2. func (s *SmuxSession) Addr() net.Addr { return s.Session.LocalAddr() }
  3. func (s *SmuxSession) Accept() (net.Conn, error) { return s.Session.AcceptStream() }
  4. func (s *SmuxSession) Close() error { return s.Session.Close() }

将 B(tcp:client) -> A(tcp:server) 的场景改为 A(gRPC:client) -> B(gRPC:Server),关键实现如下:

  1. // 忽略错误处理
  2. // 在 A 上的实现如下,cc 为后续使用的 gRPC client
  3. func handleConn(conn net.Conn) {
  4. sess, _ := smux.Client(conn, nil)
  5. cc, _ := grpc.Dial(
  6. "",
  7. grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { return sess.OpenStream() }),
  8. grpc.WithTransportCredentials(insecure.NewCredentials()),
  9. )
  10. // do something.
  11. }
  12. // 在 B 的实现如下
  13. func dialAndServe() {
  14. conn, _ := net.Dial(...)
  15. sess, _ := smux.Server(conn, nil)
  16. return g.server.Serve(&SmuxSession{Session: sess})
  17. }

cmux/smux 结合使用

其实 cmux/smux 是两个不同的维度:单端口/单连接,所以只要保证做好 net.Listener/net.Conn 的抽象,使用起来是感知不到的。

比如上面的反向连接中,handleConn 在上面的 gwL := tlsMux.Match(gw.Matcher) 中驱动的

  1. // A 的实现
  2. func handleConn(conn net.Conn) {
  3. // 增加的代码:读取 Header
  4. io.CopyN(io.Discard, conn, int64(len(gw.MatcherToken)))
  5. sess, _ := smux.Client(conn, nil)
  6. cc, _ := grpc.Dial(
  7. "",
  8. grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { return sess.OpenStream() }),
  9. grpc.WithTransportCredentials(insecure.NewCredentials()),
  10. )
  11. }
  12. // B 的实现
  13. func dialAndServe() {
  14. conn, _ := net.Dial(...)
  15. conn.Write([]byte(gw.MatcherToken)) // 增加的代码:发送一个头
  16. sess, _ := smux.Server(conn, nil)
  17. return g.server.Serve(&SmuxSession{Session: sess})
  18. }

性能压测

性能压测代码见此.

长连接的读写

测试的连接的 case:

  • TCP 连接,作为一个参考基准
  • TLS
  • Smux
    • TCP,底层协议为 TCP 的情况
    • TLS,底层协议为 TLS 的情况
  • Cmux
    • TCP,底层协议为 TCP 的多个 Matcher
    • TLS,底层协议为 TLS 的单个 Matcher,复合 mux
  1. $ go test -v -benchtime=10s -benchmem -run=^$ -bench ^BenchmarkConn .
  2. goos: linux
  3. goarch: amd64
  4. pkg: benchmark/connection
  5. cpu: 12th Gen Intel(R) Core(TM) i7-12700
  6. BenchmarkConnCmux
  7. BenchmarkConnCmux/MagicMatcher
  8. BenchmarkConnCmux/MagicMatcher-20 997550 11862 ns/op 11049.73 MB/s 0 B/op 0 allocs/op
  9. BenchmarkConnCmux/TokenMatcher
  10. BenchmarkConnCmux/TokenMatcher-20 958461 11714 ns/op 11188.94 MB/s 0 B/op 0 allocs/op
  11. BenchmarkConnCmux/TLSMatcher
  12. BenchmarkConnCmux/TLSMatcher/TLS
  13. BenchmarkConnCmux/TLSMatcher/TLS-20 295111 40471 ns/op 3238.68 MB/s 192 B/op 7 allocs/op
  14. BenchmarkConnCmux/TLSMatcher/MagicMatcher
  15. BenchmarkConnCmux/TLSMatcher/MagicMatcher-20 296203 39566 ns/op 3312.75 MB/s 192 B/op 7 allocs/op
  16. BenchmarkConnCmux/AnyMatcher
  17. BenchmarkConnCmux/AnyMatcher-20 932871 11870 ns/op 11041.90 MB/s 0 B/op 0 allocs/op
  18. BenchmarkConnSmux
  19. BenchmarkConnSmux/OverTCP
  20. BenchmarkConnSmux/OverTCP-20 438889 24703 ns/op 5305.97 MB/s 1380 B/op 26 allocs/op
  21. BenchmarkConnSmux/OverTLS
  22. BenchmarkConnSmux/OverTLS-20 210336 57345 ns/op 2285.69 MB/s 1596 B/op 36 allocs/op
  23. BenchmarkConnTCP
  24. BenchmarkConnTCP-20 917894 12120 ns/op 10814.60 MB/s 0 B/op 0 allocs/op
  25. BenchmarkConnTLS
  26. BenchmarkConnTLS-20 292843 40310 ns/op 3251.57 MB/s 192 B/op 7 allocs/op
  27. PASS
  28. ok benchmark/connection 106.287s

短连接的读写

较长连接的 case 变化,减少 Cmux 为一个Matcher,额外引入了 net.HTTP 和 fasthttp 参与 PK。

短连接的测试,包含了连接的建立和关闭的场景。

  1. $ go test -v -benchtime=10s -benchmem -run=^$ -bench ^BenchmarkEcho .
  2. goos: linux
  3. goarch: amd64
  4. pkg: benchmark/connection
  5. cpu: 12th Gen Intel(R) Core(TM) i7-12700
  6. BenchmarkEchoCmux
  7. BenchmarkEchoCmux-20 83162 164356 ns/op 797.49 MB/s 34005 B/op 26 allocs/op
  8. BenchmarkEchoFastHTTP
  9. BenchmarkEchoFastHTTP-20 144302 95231 ns/op 1376.36 MB/s 12941 B/op 41 allocs/op
  10. BenchmarkEchoNetHTTP
  11. BenchmarkEchoNetHTTP-20 65124 239187 ns/op 547.99 MB/s 370816 B/op 59 allocs/op
  12. BenchmarkEchoSmux
  13. BenchmarkEchoSmux/OverTCP
  14. BenchmarkEchoSmux/OverTCP-20 153706 70494 ns/op 1859.34 MB/s 79824 B/op 85 allocs/op
  15. BenchmarkEchoSmux/OverTLS
  16. BenchmarkEchoSmux/OverTLS-20 106585 112120 ns/op 1169.04 MB/s 81776 B/op 102 allocs/op
  17. BenchmarkEchoTCP
  18. BenchmarkEchoTCP-20 308125 39266 ns/op 3338.05 MB/s 1078 B/op 23 allocs/op
  19. BenchmarkEchoTLS
  20. BenchmarkEchoTLS-20 10000 1988704 ns/op 65.91 MB/s 241188 B/op 1112 allocs/op
  21. PASS
  22. ok benchmark/connection 112.673s

性能压测的结论

对照 TCP 为基准

  • cmu
    • 长连接下对性能的影响很小,接近 TCP,测试有的时候还会比 TCP 高一些
    • 短链接下,性能比较低;应该是在 Accept 返回 cmux.MuxConn 之前慢的,多了一次内存拷贝,函数匹配,chan 传递
  • smux
    • 底层协议为 TCP,性能相对 TCP 50% 左右,长连接和短连接表现差不多
    • 底层协议为 TLS,性能相对 TCP 25-30% 左右,长连接和短连接表现也接近这个比例
  • TLS 正常的 Read/Write 性能大概在 50% 左右,在短连接的情况下,性能非常差(TLS 握手攻击原理)
  • fasthttp 速度非常快

从性能的角度看,smux 适用于频繁建立 TLS 短连接的场景,将短连接变成了一般的 TLS 长连接,参考 BenchmarkConnSmux/OverTLS-20BenchmarkConnTLS-20 性能只下降了 30% 左右,还是比较能接受的。

参考

smux, A Stream Multiplexing Library for golang with least memory usage(TDMA)。
cmux, Connection multiplexer for GoLang: serve different services on the same port!
kcptun开发小记, smux 作者在知乎上的一篇文章,里面提到了 smux 被开发的原因。
benchmark for connections,各种连接的压测代码和结果

原文链接:https://www.cnblogs.com/shuqin/p/18027908

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号