如何使用tcp连接在go(golang)中编写代理

时间:2014-08-02 01:11:36

标签: sockets networking tcp proxy go

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

假设我想用某个TCP客户端和某个TCP服务器之间的连接编写一个tcp代理(在go中)。像这样:

enter image description here

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

我想要实现的想法如下:每当我从客户端收到请求时,我想将该请求转发到后端服务器并等待(并且什么都不做),直到后端服务器响应我(代理)然后将该响应转发给客户端(假设在常见情况下将保持两个TCP连接)。

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

假设服务器出于某种原因,一次只写一个字节,但是响应"真实"客户要长得多。因此,当代理读取连接到服务器的tcp套接字时,代理只读取一个字节并且如果它足够快地循环(在接收更多数据之前进行读取),则不可能读取零并错误地断定它收到了客户打算收到的所有消息?

解决这个问题的一种方法可能是在每次从套接字读取后等待,这样代理的循环速度就不会超过字节。我担心的原因是,假设有一个网络分区,我不能再与服务器通信了。但是,它并没有与我断开足够长的时间来超时TCP连接。因此,我是否有可能再次尝试从tcp套接字读取服务器(比我获取数据更快)并读取零并错误地断定其所有数据然后将其发送到客户端? (记住,我要保留的承诺是,当我写入客户端连接时,我只向客户端发送整个消息。因此,如果代理服务器的话,考虑正确行为是非法的,在它之后再次读取连接已写入客户端,并在以后发送丢失的块,可能是在不同请求的响应期间)。

我写的代码在go-playground.

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

假设我们有一个杯子并且代理每次从服务器读取时都喝了一半杯子,但服务器一次只能放入1茶匙。因此,如果代理饮料的速度超过茶匙,​​它可能会很快达到零,并得出结论,它的插座是空的,可以继续前进!如果我们想保证每次都发送完整的消息,那就错了。要么,这个比喻是错误的,有些"魔术"来自TCP使其工作或假定直到套接字为空的算法是完全错误的。

此处处理类似问题的question建议阅读EOF。但是,我不确定为什么这是正确的。读EOF是否意味着我得到了缩进的信息?每次有人将一大块字节写入tcp套接字时发送EOF(即我担心如果服务器一次写入一个字节,它每个字节发送1 EOF个?但是,EOF可能是某些"魔法" TCP连接如何真正起作用?发送EOF是否会关闭连接?如果它不是我想要使用的方法。此外,我无法控制服务器可能正在做什么(即我不知道它多久要写入套接字以将数据发送到代理,但是,假设它用一些&#写入套接字是合理的34;标准/正常写入算法到套接字")。我只是不相信从服务器的套接字读取EOF是正确的。为什么会这样?我何时可以阅读EOFEOF是数据的一部分还是在TCP标题中?

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

我也对一个答案很感兴趣,这个答案可能证明为什么这个算法可以解决某些情况。例如,我在想,即使服务器一次只写一个字节,如果部署的情况是一个紧张的数据中心,那么平均来说,因为延迟非常小而等待呼叫几乎肯定足够,那么就不会#39;这个算法没问题?

此外,我写的代码是否有任何风险进入"死锁"?

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()
}

1 个答案:

答案 0 :(得分:11)

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

好消息是,这非常容易,而且这个代理的核心部分将是:

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

这显然缺少错误处理,并且没有干净地关闭,但清楚地显示了如何处理核心数据传输。