如果这些问题对于专业的网络程序员来说显而易见,我在此提前道歉。我已经研究过网络编程方面的代码,并且仍然不清楚如何做到这一点。
假设我想用go编写一个tcp代理,用于连接某个TCP客户端和某个TCP服务器。就像这样:
我想要实现的想法是:每当我从客户端收到请求时,我希望将该请求转发到后端服务器,并等待(并不执行任何操作)直到后端服务器响应给我(代理),然后将该响应转发给客户端(假设在通常情况下,两个TCP连接都将保持)。
有一个主要的问题我不确定如何解决。当我将代理请求转发到服务器并获得响应时,如果我不知道从服务器发送到代理的数据格式(即我不知道服务器的响应是否为type-length-value scheme的形式,也不知道“\r\n”是否表示来自服务器的消息的结束),那么我该如何知道服务器何时已经向我发送了所有所需的信息。有人告诉我,当我的TCP连接读取大小为零或小于期望读取大小时,我应该假设我从服务器连接获取了所有数据。然而,这对我来说似乎不正确。一般情况下可能不正确的原因如下:
假设服务器由于某种原因只以每次写入一个字节的方式写入其套接字,但是响应“真实”客户端的总长度要长得多得多。因此,当代理读取与服务器连接的TCP套接字时,代理只读取一个字节,如果它循环得足够快(在接收更多数据之前进行读取),那么就会读取零并错误地得出结论:它已经获得了客户端想要接收的所有消息。
解决这个问题的一种方法是在每次从套接字读取后等待一段时间,以便代理程序不会比它接收到的字节更快地循环。我担心的原因是,假设存在网络分区,我无法再与服务器通信。然而,与我断开连接的时间并不足以超时TCP连接。因此,难道不可能我尝试再次从TCP套接字读取服务器(比我获取数据更快),并且读取零并错误地得出结论其为全部数据,然后将其发送回客户端吗?(请记住,我想要保持的承诺是:只有当我写入客户端连接时,我才向客户端发送完整的消息。因此,如果代理程序在已经向客户端写入之后的较晚时间再次读取连接,并在稍后的时间(可能在响应其他请求期间)发送丢失的部分,那么这种行为是非法的。)
我编写的代码在go-playground.中。
我喜欢使用以下类比来解释为什么我认为这种方法不起作用:
假设我们有一杯水,代理每次从服务器读取数据时都会喝掉杯中一半的水,但服务器每次只放入一茶匙水。因此,如果代理喝得比获取茶匙的速度快,它可能会很快就把杯子喝空,并得出结论说它的套接字是空的,可以继续移动!如果我们想保证每次发送完整的消息,则这是错误的。要么,这个类比是错误的,TCP的某些“魔法”使其工作,要么假定直到套接字为空的算法就是错的。
我还对能够解释为什么这个算法在某些情况下有效的答案感兴趣。例如,我想,即使服务器每次只写一个字节,如果部署场景是一个紧凑的数据中心,那么平均而言,由于延迟非常小而等待调用几乎肯定足够,那么这个算法不是很好吗?
此外,我写的代码有陷入“死锁”的风险吗?
假设我想用go编写一个tcp代理,用于连接某个TCP客户端和某个TCP服务器。就像这样:
我想要实现的想法是:每当我从客户端收到请求时,我希望将该请求转发到后端服务器,并等待(并不执行任何操作)直到后端服务器响应给我(代理),然后将该响应转发给客户端(假设在通常情况下,两个TCP连接都将保持)。
有一个主要的问题我不确定如何解决。当我将代理请求转发到服务器并获得响应时,如果我不知道从服务器发送到代理的数据格式(即我不知道服务器的响应是否为type-length-value scheme的形式,也不知道“\r\n”是否表示来自服务器的消息的结束),那么我该如何知道服务器何时已经向我发送了所有所需的信息。有人告诉我,当我的TCP连接读取大小为零或小于期望读取大小时,我应该假设我从服务器连接获取了所有数据。然而,这对我来说似乎不正确。一般情况下可能不正确的原因如下:
假设服务器由于某种原因只以每次写入一个字节的方式写入其套接字,但是响应“真实”客户端的总长度要长得多得多。因此,当代理读取与服务器连接的TCP套接字时,代理只读取一个字节,如果它循环得足够快(在接收更多数据之前进行读取),那么就会读取零并错误地得出结论:它已经获得了客户端想要接收的所有消息。
解决这个问题的一种方法是在每次从套接字读取后等待一段时间,以便代理程序不会比它接收到的字节更快地循环。我担心的原因是,假设存在网络分区,我无法再与服务器通信。然而,与我断开连接的时间并不足以超时TCP连接。因此,难道不可能我尝试再次从TCP套接字读取服务器(比我获取数据更快),并且读取零并错误地得出结论其为全部数据,然后将其发送回客户端吗?(请记住,我想要保持的承诺是:只有当我写入客户端连接时,我才向客户端发送完整的消息。因此,如果代理程序在已经向客户端写入之后的较晚时间再次读取连接,并在稍后的时间(可能在响应其他请求期间)发送丢失的部分,那么这种行为是非法的。)
我编写的代码在go-playground.中。
我喜欢使用以下类比来解释为什么我认为这种方法不起作用:
假设我们有一杯水,代理每次从服务器读取数据时都会喝掉杯中一半的水,但服务器每次只放入一茶匙水。因此,如果代理喝得比获取茶匙的速度快,它可能会很快就把杯子喝空,并得出结论说它的套接字是空的,可以继续移动!如果我们想保证每次发送完整的消息,则这是错误的。要么,这个类比是错误的,TCP的某些“魔法”使其工作,要么假定直到套接字为空的算法就是错的。
这里有一个处理类似问题的链接建议读取直到EOF
。然而,我不确定为什么这样做是正确的。读取EOF
是否意味着我得到了预期的消息?每次有人向TCP套接字写入一块字节时,都会发送一个EOF
吗(例如,我担心如果服务器一次只写一个字节,那么它会发送1个EOF
)?然而,EOF
可能是TCP连接如何实际工作的“魔法”之一?发送EOF
是否会关闭连接?如果是的话,这不是我想使用的方法。此外,我无法控制服务器可能正在做什么(即我不知道它想要多频繁地写入套接字以向代理发送数据,但可以合理地假设它使用某种“标准/正常的写入算法”来写入套接字)。我只是不确定从服务器的套接字读取到EOF
是否正确。为什么会这样?我甚至什么时候才能读取到EOF
?EOF
是数据的一部分还是在TCP头中?
我还对能够解释为什么这个算法在某些情况下有效的答案感兴趣。例如,我想,即使服务器每次只写一个字节,如果部署场景是一个紧凑的数据中心,那么平均而言,由于延迟非常小而等待调用几乎肯定足够,那么这个算法不是很好吗?
此外,我写的代码有陷入“死锁”的风险吗?
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()
}