首页 > 解决方案 > Golang TCP 代理(io.Copy)在使用 io.Multiwriter 时中断

问题描述

我目前正在涉足 Go,但无法弄清楚以下内容:

我正在尝试通过我的应用程序代理 MySQL 连接,以对来自客户端的请求和来自服务器的响应进行一些内省。我对 Redis 连接做了完全相同的事情,效果很好。

现在的问题是,如果我只是这样做

go io.Copy(serverConnection, clientConnection)
go io.Copy(clientConnection, serverConnection)

这工作正常。我可以使用 mysql 客户端连接到我的代理并向 MySQL 服务器发出查询和接收响应。

但是,如果我尝试通过写入 来“窥探”连接io.MultiWriter,则连接将挂起,等待初始响应。

实现(简化)如下所示(并以这种方式工作):

package main

import (
    "fmt"
    "io"
    "log"
    "net"
)

func main () {
    port := 21001

    listenAddr, err := net.ResolveTCPAddr("tcp", fmt.Sprintf("127.0.0.1:%d", port))

    if err != nil {
        log.Fatalln(err)
    }

    targetAddr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:11001")

    if err != nil {
        log.Fatalln(err)
    }

    listener, err := net.ListenTCP("tcp", listenAddr)

    if err != nil {
        log.Fatalln(err)
    }

    for {
        clientConnection, err := listener.AcceptTCP()

        if err != nil {
            log.Fatalln(err)
        }

        go func () {
            serverConnection, err := net.DialTCP("tcp", nil, targetAddr)

            if err != nil {
                return
            }

            connectionDone := make(chan bool)

            go monitorClientConnection(clientConnection, serverConnection, connectionDone)
            go monitorServerConnection(serverConnection, clientConnection, connectionDone)

            _ = <-connectionDone
            _ = <-connectionDone

            fmt.Println("MySQL connection closed")
        }()
    }
}

func monitorClientConnection(clientConnection *net.TCPConn, serverConnection *net.TCPConn, connectionDone chan bool) {
    go func() {
        _, err := io.Copy(serverConnection, clientConnection)

        fmt.Println(err)

        _ = serverConnection.Close()

        connectionDone <- true
    }()
}

func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
    go func() {
        _, err := io.Copy(clientConnection, serverConnection)

        fmt.Println(err)

        _ = clientConnection.Close()

        connectionDone <- true
    }()
}

它开始失败的地方

如果我从(客户端和服务器相同,只是颠倒)更改监视功能的实现:

func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
    go func() {
        _, err := io.Copy(clientConnection, serverConnection)

        fmt.Println(err)

        _ = clientConnection.Close()

        connectionDone <- true
    }()
}

func monitorServerConnection(serverConnection *net.TCPConn, clientConnection *net.TCPConn, connectionDone chan bool) {
    _, w := io.Pipe() // The reader here would be used to consume the stream, left out for simplicity

    go func() {
        mw := io.MultiWriter(clientConnection, w)

        _, err := io.Copy(mw, serverConnection)

        fmt.Println(err)

        _ = clientConnection.Close()

        connectionDone <- true
    }()
}

它只是挂起。这与适用于 Redis 代理的方法完全相同(Redis 序列化协议是明文,换行符分隔,可能是一个因素?)。

我想知道的

  1. 据我了解,为什么使用io.MultiWriter导致流挂起?
  2. 有没有更好的方法来“监视”这个流,或者将它放到一个单独的 goroutine 中,该 goroutine 可以读取/解析流,同时它也被转发到上游服务器?

标签: gotcpproxystreamtee

解决方案


推荐阅读