我正在尝试建立一种在运行Go的中央服务器与一系列IoT设备(也运行Go)的中央服务器之间进行通信的方法。
对于每个设备,它都通过持久的TCPConn连接到中央服务器。这些设备在路由器后面。中央服务器保存该连接并通过该连接发送/接收消息。现在,它已经可以正常工作了。
但是,现在消息传递变得非常复杂,以至于有必要使用HTTP提供的实用程序而不是纯TCP提供的实用程序。
我试图编写一个返回上述连接的http.Transport版本。但是,我无法从Dial / DialContext函数提供并返回有效的连接。
IoT设备
func main() {
http.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})
tcpAddr, err := net.ResolveTCPAddr("tcp", "###.###.###.###:8533")
if err != nil {
panic(err)
}
conn, err := net.DialTCP("tcp", nil, tcpAddr)
if err != nil {
panic(err)
}
err = conn.SetKeepAlive(true)
if err != nil {
panic(err)
}
err = conn.SetKeepAlivePeriod(time.Second * 10)
if err != nil {
panic(err)
}
fmt.Println("Listening")
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
中央服务器
func main() {
tcpAddr, err := net.ResolveTCPAddr("tcp", port)
if err != nil {
panic(err)
}
listener, err := net.ListenTCP("tcp", tcpAddr)
if err != nil {
panic(err)
}
conn, err := listener.AcceptTCP()
if err != nil {
panic(err)
}
fmt.Println("Received conn, attempting to send HTTP through connection")
dialFunc := func(network, addr string) (net.Conn, error) {
return conn, nil
}
t := http.Transport{
Dial: dialFunc,
}
client := http.Client{
Transport: &t,
}
fmt.Println("Making request")
res, err := client.Get("http://www.shouldNotMatter.com:8080/foo") // HANGS HERE
if err != nil {
fmt.Println(err)
}
fmt.Println("Received response")
defer res.Body.Close()
if res.StatusCode == http.StatusOK {
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
panic(err)
}
bodyString := string(bodyBytes)
fmt.Println(bodyString)
} else {
fmt.Println(res)
}
}
使用调试器查看它的挂起位置后,似乎在pconn往返过程中卡在了select语句中。 https://golang.org/src/net/http/transport.go?s=3397:10477
中的第2420行答案 0 :(得分:3)
创建一个通过拨号方法返回现有连接的类型:
type connDialer struct {
c net.Conn
}
func (cd connDialer) Dial(network, addr string) (net.Conn, error) {
return cd.c, nil
}
在运输工具中使用拨盘method value:
client := http.Client{Transport: &http.Transport{Dial: connDialer{c}.Dial}}
其中c
是现有的net.Conn
。
在the playground上尝试一下(它仅在一个请求下起作用。当客户端拨打第二个连接时,它将失败。)
总体方法是脆弱的。考虑使用WebSocket,gRPC或旨在支持双向通信的其他协议。
答案 1 :(得分:0)
您错过了客户端代码上的代码。客户端与服务器建立了空闲连接,并且不执行任何操作,因此连接肯定会挂断。您需要将连接传递到HTTP服务器。这可以通过使用net/http.Serve
并将net.Listener
传递给它来实现。
type connListener struct {
conn net.Conn
ch chan struct{}
}
func (c connListener) Accept() (Conn, error) {
if c.conn != nil {
conn := c.conn
c.conn = nil
return conn, nil
}
<-c.ch
return nil, errors.New("listener closed")
}
func (c connListener) Close() error {
close(c.ch)
return nil
}
func (c connListener) Addr() net.Addr {
return c.conn.LocalAddr()
}
// call it like this
http.Serve(connListener{conn, make(chan struct{})}, nil)
顺便说一句,您是否让客户端连接到服务器,然后反向连接,从而使客户端的行为类似于HTTP服务器,而服务器的行为类似于HTTP客户端?您可能想在Google上“反向http”以获取有关此信息。