TCP双向代理数据损坏问题求助:Go语言实现异常排查
解决TCP双向代理的数据损坏问题
看起来你写的TCP双向代理遇到了典型的读写字节数不匹配问题,这也是新手写代理时容易踩的坑。咱们一步步拆解问题:
核心错误点
你的handleConnection函数里,读取到数据后直接把整个1024字节的切片写入目标连接:
conn2.Write([]byte(input))
但实际上conn1.Read(input)返回的n是实际读取到的有效字节数,当n < 1024时,input切片里剩下的空间是未被覆盖的(可能是零值或者之前读取残留的数据),这些无效数据被写入后,就会导致接收端拿到的文件出现末尾损坏。
比如你读取了512字节有效数据,却把1024字节都写出去了,多出来的512字节就是垃圾数据,叠加起来就会让文件末尾不可读。
次要问题:空指针panic风险
当net.Dial失败时,conn2可能是nil,这时候直接调用conn2.Close()会触发空指针panic,需要先判断conn2是否不为nil再关闭。
修正后的代码方案
方案1:修复原有的读写逻辑
只需要修改handleConnection里的写入逻辑,只写入实际读取到的n字节,同时优化连接关闭的逻辑:
package main import ( "fmt" "net" "sync" ) func main() { listener, err := net.Listen("tcp", ":8120") if err != nil { fmt.Println(err) return } defer listener.Close() fmt.Println("Server is listening...") for { conn1, err := listener.Accept() if err != nil { fmt.Println(err) continue } conn2, err := net.Dial("tcp", "185.151.245.51:80") if err != nil { fmt.Println(err) conn1.Close() continue } // 用WaitGroup等待两个方向的传输完成,避免提前关闭连接 var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() handleConnection(conn1, conn2) }() go func() { defer wg.Done() handleConnection(conn2, conn1) }() go func() { wg.Wait() conn1.Close() conn2.Close() }() } } func handleConnection(src, dst net.Conn) { defer src.Close() buf := make([]byte, 1024) for { n, err := src.Read(buf) if n > 0 { // 只写入实际读取到的n字节 if _, writeErr := dst.Write(buf[:n]); writeErr != nil { break } } if err != nil { break } } }
方案2:用io.Copy简化代码
Go标准库的io.Copy已经封装了正确的读写逻辑,会自动处理字节数和连接关闭,代码会更简洁可靠:
package main import ( "fmt" "io" "net" "sync" ) func main() { listener, err := net.Listen("tcp", ":8120") if err != nil { fmt.Println(err) return } defer listener.Close() fmt.Println("Server is listening...") for { conn1, err := listener.Accept() if err != nil { fmt.Println(err) continue } conn2, err := net.Dial("tcp", "185.151.245.51:80") if err != nil { fmt.Println(err) conn1.Close() continue } var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() io.Copy(conn2, conn1) }() go func() { defer wg.Done() io.Copy(conn1, conn2) }() go func() { wg.Wait() conn1.Close() conn2.Close() }() } }
io.Copy会持续从源连接读取数据并写入目标连接,直到遇到EOF或者错误,完全避免了手动处理字节数的问题。
为什么切片大小会影响问题表现?
- 当切片很小(比如<8字节)时,每次读取的有效字节数大概率等于切片大小,所以多余的无效数据很少,甚至没有,看起来传输正常,但因为每次读写的字节少,速度自然慢。
- 当切片很大时,每次读取的有效字节数远小于切片大小,写入的无效数据更多,损坏就更严重。
内容的提问来源于stack exchange,提问作者Nikita Plinsky
相关产品推荐
相关产品推荐

