// spmux 包示例:单端口多路复用——同一端口上按报文特征把流量分发给 HTTP / HTTPS / TCP 三个 Listener package main import ( "bufio" "fmt" "io" "log" "net" "net/http" "os" "time" "git.zeroonesoft.cn/golib/zogo/spmux" ) // stdoutLogger 适配 spmux.Logger 接口(*logrus.Logger 天然满足,无需适配) type stdoutLogger struct{} func (stdoutLogger) Debugf(format string, a ...any) { log.Printf("DEBUG "+format, a...) } func (stdoutLogger) Errorf(format string, a ...any) { log.Printf("ERROR "+format, a...) } func main() { // 找一个空闲端口 l, err := net.Listen("tcp", "127.0.0.1:0") if err != nil { panic(err) } port := l.Addr().(*net.TCPAddr).Port l.Close() // 创建端口复用器(logger 实现 spmux.Logger 即可,也可传 nil 静默) pm := spmux.NewPortMux(port, stdoutLogger{}) if err := pm.Start(); err != nil { panic(err) } // HTTP 流量交给 HttpListener go http.Serve(pm.HttpListener(), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello from http over spmux") })) // 其他 TCP 流量交给 TcpListener(echo 服务,回显内容并补换行) tcpL := pm.TcpListener() go func() { for { c, err := tcpL.Accept() if err != nil { return } go func(cc net.Conn) { defer cc.Close() buf := make([]byte, 1024) for { n, err := cc.Read(buf) if n > 0 { cc.Write(append(buf[:n:n], '\n')) } if err != nil { return } } }(c) } }() time.Sleep(100 * time.Millisecond) // HTTP 请求会被识别并路由到 HttpListener resp, err := http.Get(fmt.Sprintf("http://127.0.0.1:%d/", port)) if err != nil { panic(err) } body, _ := io.ReadAll(resp.Body) resp.Body.Close() fmt.Print("HTTP 响应:", string(body)) // 普通数据会被路由到 TcpListener raw, err := net.Dial("tcp", fmt.Sprintf("127.0.0.1:%d", port)) if err != nil { panic(err) } fmt.Fprint(raw, "ping") line, _ := bufio.NewReader(raw).ReadString('\n') fmt.Print("TCP 响应:", line) raw.Close() _ = os.Stdout }