1. 程式人生 > 實用技巧 >soheilhy/cmux 網路埠複用服務

soheilhy/cmux 網路埠複用服務

同一個埠可以進行不同的操作還是很有用的,比如一個埠同時提供ssh,http,rpc 服務
soheilhy/cmux 是一個不錯的選擇,以下是一個簡單的試用,程式碼來自官方文件

程式碼

main.go

package main
import (
  "context"
  "fmt"
  "io"
  "log"
  "net"
  "net/http"
  "net/rpc"
  "strings"
  "github.com/soheilhy/cmux"
  "golang.org/x/net/websocket"
  "google.golang.org/grpc"
  grpchello "google.golang.org/grpc/examples/helloworld/helloworld"
)
type exampleHTTPHandler struct{}
func (h *exampleHTTPHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  fmt.Fprintf(w, "example http response")
}
func serveHTTP(l net.Listener) {
  s := &http.Server{
    Handler: &exampleHTTPHandler{},
   }
  if err := s.Serve(l); err != cmux.ErrListenerClosed {
    panic(err)
   }
}
func EchoServer(ws *websocket.Conn) {
  if _, err := io.Copy(ws, ws); err != nil {
    panic(err)
   }
}
func serveWS(l net.Listener) {
  s := &http.Server{
    Handler: websocket.Handler(EchoServer),
   }
  if err := s.Serve(l); err != cmux.ErrListenerClosed {
    panic(err)
   }
}
type ExampleRPCRcvr struct{}
func (r *ExampleRPCRcvr) Cube(i int, j *int) error {
  *j = i * i
  return nil
}
func serveRPC(l net.Listener) {
  s := rpc.NewServer()
  if err := s.Register(&ExampleRPCRcvr{}); err != nil {
    panic(err)
   }
  for {
    conn, err := l.Accept()
    if err != nil {
      if err != cmux.ErrListenerClosed {
        panic(err)
       }
      return
     }
    go s.ServeConn(conn)
   }
}
type grpcServer struct {
  // must embedd this
  grpchello.UnimplementedGreeterServer
}
func (s *grpcServer) SayHello(ctx context.Context, in *grpchello.HelloRequest) (
  *grpchello.HelloReply, error) {
  return &grpchello.HelloReply{Message: "Hello " + in.Name + " from cmux"}, nil
}
func serveGRPC(l net.Listener) {
  grpcs := grpc.NewServer()
  grpchello.RegisterGreeterServer(grpcs, &grpcServer{})
  if err := grpcs.Serve(l); err != cmux.ErrListenerClosed {
    panic(err)
   }
}
type mylogin struct {
}
func (my mylogin) ServeHTTP(res http.ResponseWriter, req *http.Request) {
  res.Header().Add("Content-Type", "text/html")
  res.Write([]byte("dalong demo"))
}
func main() {
  l, err := net.Listen("tcp", ":50051")
  if err != nil {
    log.Panic(err)
   }
  m := cmux.New(l)
  // We first match the connection against HTTP2 fields. If matched, the
  // connection will be sent through the "grpcl" listener.
  grpcl := m.Match(cmux.HTTP2HeaderFieldPrefix("content-type", "application/grpc"))
  //Otherwise, we match it againts a websocket upgrade request.
  wsl := m.Match(cmux.HTTP1HeaderField("Upgrade", "websocket"))
  // Otherwise, we match it againts HTTP1 methods. If matched,
  // it is sent through the "httpl" listener.
  httpl := m.Match(cmux.HTTP1Fast())
  // If not matched by HTTP, we assume it is an RPC connection.
  rpcl := m.Match(cmux.Any())
  // Then we used the muxed listeners.
  go serveGRPC(grpcl)
  go serveWS(wsl)
  // go serveHTTP(httpl)
  go serveRPC(rpcl)
  go func() {
    http.Serve(httpl, &mylogin{})
   }()
  if err := m.Serve(); !strings.Contains(err.Error(), "use of closed network connection") {
    panic(err)
   }
}

簡單說明:
以上程式碼使用了同一個埠提供http,websocket,rpc 服務(grpc,以及rpc)

執行效果

說明

類似的有一個比較好玩的linux 核心對於tcp reuseport 特性的支援,可以複用埠(比如軟體升級)同時提升系統的效能,實際上我們可以
整合go_reuseport
核心程式碼部分只需要簡單的修改

l, err := reuseport.Listen("tcp", ":50051")
  if err != nil {
    log.Panic(err)
   }

參考資料

https://github.com/soheilhy/cmux
https://github.com/rongfengliang/cmux-learning
https://github.com/kavu/go_reuseport
https://segmentfault.com/a/1190000020524323
http://www.blogjava.net/yongboy/archive/2015/02/12/422893.html