使用cmux实现网络端口复用

315 阅读2分钟

cmux的作用


一般情况下,每个端口只能为一个服务所用,如果复用,会报"port is already in use"

如果需要复用某个端口,那么可以使用cmux来实现(其实大多数情况下必要性不大.比如我就图8888端口吉利,http/grpc等服务都用这个端口)

cmux 全称 Connection Mux, 是Go生态来复用端口的库, 可以在同一个TCP监听器上服务 gRPC、SSH、HTTPS、HTTP、Go RPC等几乎任何其他协议。


实现原理


cmux的实现原理主要是通过"查看"连接的第一个数据包来确定连接的类型。每种类型的连接都有一个匹配器,这个匹配器可以是自定义的,也可以使用cmux库提供的一些内置匹配器。

当一个连接进来时,cmux会将其数据包传递给所有匹配器,看看哪个匹配器会匹配这个数据包。一旦找到一个匹配的匹配器,cmux就会将该连接传递给对应的服务去处理。

cmux 源码分析


示例代码


以下示例创建了一个主监听器,然后为该监听器创建了一个cmux,然后匹配不同的连接并为每种类型的连接创建对应的服务。

package main

import (
	"context"
	"log"
	"net"
	"net/http"

	"google.golang.org/grpc"
	pb "google.golang.org/grpc/examples/helloworld/helloworld"

	"github.com/soheilhy/cmux"
)

func main() {
	lis, err := net.Listen("tcp", "localhost:8888")
	if err != nil {
		log.Fatalf("failed to listen: %v", err)
	}

	mux := cmux.New(lis)

	// gRPC 匹配规则
	grpcL := mux.MatchWithWriters(
		cmux.HTTP2MatchHeaderFieldSendSettings("content-type", "application/grpc"),
	)
	// otherwise serve http
	httpL := mux.Match(cmux.Any())

	// gRPC server
	grpcS := grpc.NewServer()
	pb.RegisterGreeterServer(grpcS, &server{})

	// HTTP server
	httpS := &http.Server{
		Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			w.Write([]byte("greet from HTTP\n"))
		}),
	}

	// start serving!
	go grpcS.Serve(grpcL)
	go httpS.Serve(httpL)
	log.Printf("serve both grpc and http at %v", lis.Addr())
	if err := mux.Serve(); err != nil {
		log.Fatalf("failed to serve: %v", err)
	}
}

type server struct {
	pb.UnimplementedGreeterServer
}

func (server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
	return &pb.HelloReply{Message: "greet from gRPC"}, nil
}


go mod tidy 而后 go run main.go运行如上代码,


在浏览器地址栏中输入 http://localhost:8888/

在命令行中执行 greeter_client -addr localhost:8888

如果没有安装greeter_client,可以 go install google.golang.org/grpc/examples/helloworld/greeter_client,

然后再 ${GOPATH}/bin/greeter_client -addr localhost:8888


更多可参考 Go每日一库之139:cmux (连接多路复用)

完整代码: github.com/cuishuang/c…