币安的WebSocket不响应ping请求。

huangapple go评论161阅读模式
英文:

binance websocket not responding to ping

问题

使用gorilla/websocket库拨号到Binance的WebSocket端点,成功连接没有错误。在连接上设置pong处理程序后,我写了一个ping控制消息,并等待pong到达pong处理程序,但似乎从未发生。我使用了一个通道、一个带有超时的上下文和一个select块来检查pong是否到达。

代码如下:

package main

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/gorilla/websocket"
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	conn, resp, err := websocket.DefaultDialer.DialContext(ctx, "wss://stream.binance.com:9443/ws", nil)
	if resp != nil {
		log.Printf("status: %s", resp.Status)
	}

	if err != nil {
		panic(err)
	}

	pong := make(chan struct{})
	conn.SetPongHandler(func(appData string) error {
		log.Println(appData)

		pong <- struct{}{}
		return nil
	})

	if err := conn.WriteControl(websocket.PingMessage, []byte("Hello, world!"), time.Now().Add(5*time.Second)); err != nil {
		panic(err)
	}

	select {
	case <-ctx.Done():
		panic(fmt.Errorf("pong wait: %w", ctx.Err()))
	case <-pong:
	}
}

输出结果:

$ go run ./cmd/ping
2022/02/07 20:01:23 status: 101 Switching Protocols
panic: pong wait: context deadline exceeded

goroutine 1 [running]:
main.main()
        /workspaces/yatgo/cmd/ping/ping.go:39 +0x2ba
exit status 2

根据rfc6455的第5.5.2节

> 5.5.2. Ping
>
> Ping帧的操作码为0x9。
>
> 一个Ping帧可以包含"应用数据"。
>
> 收到Ping帧后,终端必须发送一个Pong帧作为响应,除非它已经收到了一个Close帧。它应该尽快用Pong帧作出响应。Pong帧在第5.5.3节中讨论。
>
> 在连接建立之后、连接关闭之前,终端可以随时发送Ping帧。
>
> 注意:Ping帧既可以用作保持连接的信号,也可以用作验证远程终端是否仍然响应的手段。

我原本期望这个代码能够工作。Binance WebSocket API的限制文档确实提到了ping消息:

> WebSocket连接每秒最多接收5条消息。消息被视为:
> * 一个PING帧

所以我想知道:

  1. 我的代码有什么问题吗?
  2. 还是Binance没有遵守RFC6455?
英文:

Using gorilla/websocket I dial the binance websocket endpoint, which succeeds without error. After setting the pong handler on the connection, I write a ping control message and wait for a pong to arrive at the pong handler, which never seems to happen. I use a channel, a context with timeout and a select block to check if the pong arrived.

The code:

package main

import (
	&quot;context&quot;
	&quot;fmt&quot;
	&quot;log&quot;
	&quot;time&quot;

	&quot;github.com/gorilla/websocket&quot;
)

func main() {
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	conn, resp, err := websocket.DefaultDialer.DialContext(ctx, &quot;wss://stream.binance.com:9443/ws&quot;, nil)
	if resp != nil {
		log.Printf(&quot;status: %s&quot;, resp.Status)
	}

	if err != nil {
		panic(err)
	}

	pong := make(chan struct{})
	conn.SetPongHandler(func(appData string) error {
		log.Println(appData)

		pong &lt;- struct{}{}
		return nil
	})

	if err := conn.WriteControl(websocket.PingMessage, []byte(&quot;Hello, world!&quot;), time.Now().Add(5*time.Second)); err != nil {
		panic(err)
	}

	select {
	case &lt;-ctx.Done():
		panic(fmt.Errorf(&quot;pong wait: %w&quot;, ctx.Err()))
	case &lt;-pong:
	}
}

Output:

$ go run ./cmd/ping
2022/02/07 20:01:23 status: 101 Switching Protocols
panic: pong wait: context deadline exceeded

goroutine 1 [running]:
main.main()
        /workspaces/yatgo/cmd/ping/ping.go:39 +0x2ba
exit status 2

As per rfc6455, section-5.5.2:

> 5.5.2. Ping
>
> The Ping frame contains an opcode of 0x9.
>
> A Ping frame MAY include "Application data".
>
> Upon receipt of a Ping frame, an endpoint MUST send a Pong frame in
> response, unless it already received a Close frame. It SHOULD
> respond with Pong frame as soon as is practical. Pong frames are
> discussed in Section 5.5.3.
>
> An endpoint MAY send a Ping frame any time after the connection is
> established and before the connection is closed.
>
> NOTE: A Ping frame may serve either as a keepalive or as a means to
> verify that the remote endpoint is still responsive.

I kind off expected this to work. Binance websocket API limits doc does mentions ping messages:

> WebSocket connections have a limit of 5 incoming messages per second. A message is considered:
> * A PING frame

So I wonder:

  1. Is something wrong with my code?
  2. Or is binance not respecting RFC6455?

答案1

得分: 1

Gorilla Websocket文档中提到:

> 应用程序必须读取连接以处理来自对等方发送的关闭、ping和pong消息。如果应用程序对来自对等方的消息没有其他兴趣,那么应用程序应该启动一个goroutine来读取并丢弃来自对等方的消息。

通过在select语句之前启动一个goroutine来读取连接,修复应用程序:

go func() {
	defer cancel()
	for {
		if _, _, err := conn.NextReader(); err != nil {
			fmt.Println(err)
			return
		}
	}
}()

select {
⋮

这是对问题中所示应用程序的修复方法。如果你的实际应用程序在循环中从连接中读取数据,那么你不应该添加这里所示的goroutine。应用程序应该使用一个读取循环来处理控制和数据消息。

英文:

The Gorilla Websocket documentation says:

> The application must read the connection to process close, ping and pong messages sent from the peer. If the application is not otherwise interested in messages from the peer, then the application should start a goroutine to read and discard messages from the peer.

Fix the application by starting a goroutine to read the connection before the select statement:

go func() {
	defer cancel()
	for {
		if _, _, err := conn.NextReader(); err != nil {
			fmt.Println(err)
			return
		}
	}
}()

select {
⋮

This is a fix for the application shown in the question. If your actual application reads data from the connection in a loop, then you should not add the goroutine shown here. The application should use one read loop to handle control and data messages.

huangapple
  • 本文由 发表于 2022年2月8日 04:07:24
  • 转载请务必保留本文链接:https://go.coder-hub.com/71024741.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定