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

Golang中使用io.Copy在两个TCP连接间代理数据的正确用法

在Golang中实现TCP连接间数据复制的正确方式

我在Golang中实现两个TCP连接间的数据复制时,发现io.Copy的用法存在分歧——有人认为应该在goroutine内调用Close,有人持相反意见。测试了两种常见实现模式,表面上都能正常运行,但担心边缘场景有问题,无法判断哪种是正确的。

以下是两种常见实现:

package main

import (
    "io"
    "net"
    "sync"
)

func proxy1(conn1, conn2 net.Conn) {
    defer conn1.Close()
    defer conn2.Close()

    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        io.Copy(conn1, conn2)
        wg.Done()
    }()
    go func() {
        io.Copy(conn2, conn1)
        wg.Done()
    }()

    wg.Wait()
}

func proxy2(conn1, conn2 net.Conn) {
    var wg sync.WaitGroup
    wg.Add(2)

    go func() {
        io.Copy(conn1, conn2)
        wg.Done()
        conn1.Close()
    }()
    go func() {
        io.Copy(conn2, conn1)
        wg.Done()
        conn2.Close()
    }()

    wg.Wait()
}

两种实现的问题分析

proxy1的潜在问题

proxy1在主goroutine中用defer延迟关闭两个连接,然后等待两个复制goroutine完成。这种写法的核心问题是可能导致永久阻塞:

  • 当其中一个io.Copy完成(比如conn2的发送端关闭,io.Copy(conn1, conn2)读到EOF返回),对应的goroutine调用wg.Done(),但另一个io.Copy可能仍在阻塞等待数据。
  • 例如客户端conn1发送完数据后只关闭写端(保持读端等待响应),此时io.Copy(conn2, conn1)完成,但io.Copy(conn1, conn2)会一直阻塞等待conn2的输入。主goroutine会卡在wg.Wait()上,永远无法执行defer的Close,导致两个连接长期挂起。

proxy2的合理性

proxy2在每个复制goroutine完成后关闭对应的连接,这种写法能避免proxy1的阻塞问题:

  • 当io.Copy(conn1, conn2)完成(说明conn2的读取端到EOF),关闭conn1会让另一个goroutine中的io.Copy(conn2, conn1)因为conn1被关闭而收到EOF,从而正常结束并调用wg.Done(),最后关闭conn2。
  • 这样两个goroutine都会正常终止,wg.Wait()能顺利完成,不会出现连接挂起的情况。

优化建议

如果想更精细地控制连接状态,可以将Close()换成CloseWrite()——只关闭连接的写端,保留读端以读取对方可能剩余的响应数据。不过对于大多数通用代理场景,直接调用Close()已经能满足需求,因为当一个方向的复制完成时,对方通常已经关闭了写端,此时关闭整个连接不会丢失关键数据。

内容的提问来源于stack exchange,提问作者Ace Bond

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.01 11:25:30