You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 06:42:08