
本文介绍一种无需修改第三方库、不依赖 rtmpt 封装、仅通过首字节特征识别协议类型的方式,让 go 程序在同一 tcp 端口上智能分流 http 和 rtmp 流量,并分别交由对应处理器处理。
本文介绍一种无需修改第三方库、不依赖 rtmpt 封装、仅通过首字节特征识别协议类型的方式,让 go 程序在同一 tcp 端口上智能分流 http 和 rtmp 流量,并分别交由对应处理器处理。
在标准 Go Web 开发中,http.ListenAndServe 仅支持 HTTP/HTTPS 协议,而 RTMP 是一种基于 TCP 的二进制流协议,无法直接映射为 HTTP 路由(如 /rtmp)。因此,试图用 http.HandleFunc("/rtmp", ...) 处理 RTMP 请求本质上是无效的——HTTP 服务器根本不会将原始 RTMP 握手数据(如 0x03 开头的 connect 消息)解析为合法 HTTP 请求。
真正的解决方案是绕过 HTTP 服务器的协议解析层,直接监听底层 TCP 连接,根据初始字节特征进行协议识别与分流。RTMP 协议规范规定:客户端连接建立后发送的第一个字节恒为 0x03(表示 RTMP 版本号),而 HTTP 请求首行必为 ASCII 文本(如 GET / HTTP/1.1\r\n),其首个字节绝不可能是 0x03。这一显著差异构成了可靠分流的基础。
以下是一个生产就绪的轻量级实现思路:
-
统一监听端口:使用
net.ListenTCP在目标端口(如:8000)创建裸 TCP 监听器; - 首字节探查:对每个新连接,读取第一个字节;
-
智能路由:
- 若为
0x03→ 视为 RTMP 流量,转发至本地 RTMP 服务(如gortmp.Server所监听的:1935); - 否则 → 视为 HTTP 流量,转发至本地 HTTP 服务(如
http.Server监听的:8080);
- 若为
- 双向代理:建立两个连接间的全双工数据透传(含 goroutine 并发读写)。
示例核心代理逻辑如下:
func proxyConnection(client *net.TCPConn) {
defer client.Close()
// 读取首字节判断协议
var firstByte [1]byte
n, err := client.Read(firstByte[:])
if err != nil || n == 0 {
return
}
var backendAddr string
if firstByte[0] == 0x03 {
backendAddr = "127.0.0.1:1935" // RTMP server
} else {
backendAddr = "127.0.0.1:8080" // HTTP server
}
backend, err := net.Dial("tcp", backendAddr)
if err != nil {
return
}
defer backend.Close()
// 将首字节转发给后端
backend.Write(firstByte[:n])
// 并发透传:client → backend
go func() {
io.Copy(backend, client)
}()
// 同步透传:backend → client
io.Copy(client, backend)
}
func main() {
listener, err := net.Listen("tcp", ":8000")
if err != nil {
panic(err)
}
defer listener.Close()
// 启动 HTTP 服务(独立端口)
go func() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write([]byte("HTTP OK"))
})
http.ListenAndServe(":8080", nil)
}()
// 启动 RTMP 服务(独立端口,例如使用 gortmp)
go func() {
srv := gortmp.NewServer()
srv.HandleConn = func(conn net.Conn) {
// 自定义 RTMP 会话逻辑
fmt.Println("RTMP client connected")
}
srv.ListenAndServe("127.0.0.1:1935")
}()
// 主监听循环:接收连接并代理
for {
conn, err := listener.Accept()
if err != nil {
continue
}
if tcpConn, ok := conn.(*net.TCPConn); ok {
go proxyConnection(tcpConn)
}
}
}
⚠️ 注意事项:
- 此方案要求 HTTP 和 RTMP 服务分别运行在不同本地端口(如
:8080和:1935),对外仅暴露一个端口(如:8000); - 首字节检测足够可靠(RTMP v1.0+ 强制以
0x03开始;HTTP 请求首字符为可打印 ASCII),但不适用于 TLS/SSL 加密流量(需 TLS 分流需更复杂 ALPN 或 SNI 解析); - 实际部署时建议增加超时控制(
SetReadDeadline)、连接数限制及错误日志,避免资源耗尽; - 若需支持 HTTPS + RTMP 混合,应改用
TLSListener并结合 ALPN 协商(如h2,http/1.1,rtmp),但需服务端与客户端共同支持。
该方法在保持各协议栈纯净性的同时,实现了优雅的单端口复用,已被用于多个流媒体网关项目(如 Streamroller),兼具简洁性与工程可用性。










