sockets - 如何使用TCP连接在Go(golang)中编写代理

标签 sockets networking tcp proxy go

如果其中一些问题对于专业网络程序员来说很明显,我先向您道歉。我已经研究并阅读了有关网络编码的信息,但我仍然不清楚如何做到这一点。

假设我想编写一个TCP代理(进行中),其中包含一些TCP客户端和某些TCP服务器之间的连接。像这样的东西:

首先,假设这些连接是半永久性的(很长一段时间后将关闭),我需要按顺序到达数据。

我要实现的想法如下:每当我从客户端收到请求时,我都希望将该请求转发到后端服务器,然后等待(不执行任何操作),直到后端服务器响应我(代理),然后将响应转发给客户端(假定在通常情况下两个TCP连接都将保持不变)。

有一个主要问题,我不确定如何解决。当我将请求从代理转发到服务器并获得响应时,如果我事先不知道从服务器发送到的数据格式,我如何知道服务器何时向我发送了我需要的所有信息。代理(即,我不知道来自服务器的响应是否为type-length-value scheme形式,也不知道`\r\n\是否表示来自服务器的消息结尾)。有人告诉我应该假设,只要我从tcp连接读取的大小为零或小于预期的读取大小,就将从服务器连接中获取所有数据。但是,这对我来说似乎不正确。通常,它可能不正确的原因如下:

假设由于某种原因,服务器一次只向其套接字写入一个字节,但是对“真实”客户端的响应的总长度要长得多。因此,当代理读取连接到服务器的tcp套接字时,代理是否仅读取一个字节,并且如果其循环足够快(在接收更多数据之前进行读取),然后零读取且错误地读取,这是不可能的。得出的结论是,它已收到客户端打算接收的所有消息?

解决此问题的一种方法可能是在每次从套接字读取后等待,以使代理不会比获取字节更快地循环。我担心的原因是,假设有一个网络分区,我再也无法与服务器通信了。但是,它与我的连接断开的时间还不够长,无法使TCP连接超时。因此,是否有可能我尝试再次从tcp套接字读取到服务器(比获取数据快)并读取零并错误地得出结论,即所有数据然后将其打包发送给客户端? (请记住,我要保留的 promise 是,我仅在写入客户端连接时才将整个消息发送给客户端。因此,如果代理出现故障,则考虑正确的行为是非法的,在此之后的稍后时间再次读取该连接已写入客户端,并在以后的某个时间(可能在其他请求的响应期间)发送丢失的数据块)。

我写的代码在go-playground.

我喜欢用来解释为什么我认为这种方法不起作用的类比如下:

假设我们有一个杯子,代理每次从服务器读取内容时,代理服务器就会喝掉一半的杯子,但是服务器一次只能放入1茶匙。因此,如果代理喝得快于得到茶匙,它可能会太早达到零,并得出结论说它的 socket 是空的,可以继续前进了!如果我们要保证每次都发送完整的消息,那是错误的。要么,这种类比是错误的,并且TCP的某些“魔术”使其起作用,或者假定直到套接字为空的算法是完全错误的。

此处处理类似问题的question建议阅读直到EOF为止。但是,我不确定为什么这是正确的。阅读EOF是否意味着我收到了缩进的消息?每当有人向tcp套接字写入大块字节时是否发送EOF(即,我担心如果服务器一次写入一个字节,则每字节发送1个EOF)吗?但是,EOF可能是TCP连接实际工作方式的“魔术”?发送EOF是否关闭连接?如果它不是我要使用的方法。另外,我无法控制服务器的工作方式(即,我不知道它要多久写入一次套接字以将数据发送到代理,但是可以合理地假设它以某种“标准”写入套接字/socket的正常写入算法”)。我只是不相信从服务器的套接字读取直到EOF是正确的。为什么会这样呢?我什么时候甚至可以阅读EOFEOF是数据的一部分还是它们在TCP报头中?

另外,我写的关于将等待放在epsilon超时以下的想法,在最坏的情况下还是只能在平均情况下起作用?我也在想,我意识到,如果Wait()调用的时间长于超时时间,那么如果您返回到tcp连接并且它没有任何内容,则可以继续进行。但是,如果它什么都没有并且我们不知道服务器发生了什么,那么我们将超时。因此可以安全地关闭连接(因为超时无论如何都会这样做)。因此,我认为如果Wait调用至少与超时时间一样长,则此过程确实有效!人们怎么看?

我也对一个可以证明为什么这种算法在某些情况下可行的答案很感兴趣。例如,我在想,即使服务器一次只写一个字节,如果部署的环境是一个紧凑的数据中心,那么平均而言,因为延迟非常小并且等待调用几乎可以肯定就够了,这种算法不好吗?

此外,我编写的代码陷入“僵局”是否有任何风险?

package main

import (
    "fmt"
    "net"
)

type Proxy struct {
    ServerConnection *net.TCPConn
    ClientConnection *net.TCPConn
}

func (p *Proxy) Proxy() {
    fmt.Println("Running proxy...")
    for {
        request := p.receiveRequestClient()
        p.sendClientRequestToServer(request)
        response := p.receiveResponseFromServer() //<--worried about this one.
        p.sendServerResponseToClient(response)
    }
}

func (p *Proxy) receiveRequestClient() (request []byte) {
    //assume this function is a black box and that it works.
    //maybe we know that the messages from the client always end in \r\n or they
    //they are length prefixed.
    return
}

func (p *Proxy) sendClientRequestToServer(request []byte) {
    //do
    bytesSent := 0
    bytesToSend := len(request)
    for bytesSent < bytesToSend {
        n, _ := p.ServerConnection.Write(request)
        bytesSent += n
    }
    return
}

// Intended behaviour: waits until ALL of the response from backend server is obtained.
// What it does though, assumes that if it reads zero, that the server has not yet
// written to the proxy and therefore waits. However, once the first byte has been read,
// keeps writting until it extracts all the data from the server and the socket is "empty".
// (Signaled by reading zero from the second loop)
func (p *Proxy) receiveResponseFromServer() (response []byte) {
    bytesRead, _ := p.ServerConnection.Read(response)
    for bytesRead == 0 {
        bytesRead, _ = p.ServerConnection.Read(response)
    }
    for bytesRead != 0 {
        n, _ := p.ServerConnection.Read(response)
        bytesRead += n
        //Wait(n) could solve it here?
    }
    return
}

func (p *Proxy) sendServerResponseToClient(response []byte) {
    bytesSent := 0
    bytesToSend := len(request)
    for bytesSent < bytesToSend {
        n, _ := p.ServerConnection.Write(request)
        bytesSent += n
    }
    return
}

func main() {
    proxy := &Proxy{}
    proxy.Proxy()
}

最佳答案

除非您使用特定的更高级别的协议(protocol),否则没有从客户端读取以中继到服务器的“消息”。 TCP是一种流协议(protocol),您所能做的就是来回穿梭字节。

好消息是,这样做非常容易,此代理的核心部分将是:

go io.Copy(server, client)
io.Copy(client, server)

这显然缺少错误处理,并且不能完全关闭,但是清楚地显示了如何处理核心数据传输。

关于sockets - 如何使用TCP连接在Go(golang)中编写代理,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25090690/

相关文章:

java - 在同一台机器上调试多个Socket IP地址

sockets - 当所有 IP 地址都相同时,指定用于 UDP 写入的以太网端口

c - Berkeley Sockets,打破别名规则?

python - 发送 CAN J1939 消息

python - 如何通过互联网在两台计算机之间建立 UDP 连接

c - 在c中通过tcp套接字发送结构

java - Netty - 如何获得所有客户 channel ?

linux - 有没有办法在 RISC-V 中运行网络(套接字)程序?

c# - 异步线程 tcp 服务器

java - Spring 集成 : individual timeouts settings for connections in the same pool