为啥火币服务器收不到 C# WebSocket Ping?

Posted

技术标签:

【中文标题】为啥火币服务器收不到 C# WebSocket Ping?【英文标题】:Why is C# WebSocket Ping not received by Huobi server?为什么火币服务器收不到 C# WebSocket Ping? 【发布时间】:2021-10-01 00:07:29 【问题描述】:

我正在尝试从火币 USDT 掉期传输比特币价格,但我的 ping/pong 机制无法正常工作,因此火币总是在 30 秒后关闭连接。

这就是文档所说的https://huobiapi.github.io/docs/usdt_swap/v1/en/#market-heartbeat:

WebSocket API 支持双向心跳。 Server 和 Client 都可以发送 ping 消息,对方可以用 pong 消息返回。

WebSocket Server 发送心跳: “平”:18212558000

WebSocket 客户端应该响应: “乒乓”:18212558000

注意:一旦 WebSocket 客户端和 WebSocket 服务器建立连接,服务器将每 5 秒发送一次心跳(频率可能会改变)。如果 WebSocket 客户端 5 次忽略心跳消息,连接将自动断开。如果 WebSocket 客户端在最近的 2 个心跳消息中响应一个“ping”值,服务器将保持连接。

using System;
using System.IO;
using System.IO.Compression;
using System.Net.WebSockets;
using System.Text;
using System.Threading;

namespace HuobiMarketWebSocketTest

    class HuobiMarketWebSocketTest
    
        public static void Main(string[] args)
        
            int pings = 0;
            ClientWebSocket ClientWebSocket = new ClientWebSocket();
            string symbol = "btcusdt";
            ClientWebSocket.ConnectAsync(new Uri("wss://api.hbdm.com/linear-swap-ws"), CancellationToken.None).Wait();

            try
            
                // Subscribe to BTC
                string subscribeTrade = $" \"sub\": \"market.symbol.trade.detail\", \"id\": \"symbol trade\"";
                byte[] subscribeTradeAsBytes = Encoding.UTF8.GetBytes(subscribeTrade);
                ClientWebSocket.SendAsync(subscribeTradeAsBytes, WebSocketMessageType.Text, true, CancellationToken.None).Wait();
                Console.WriteLine($"subscribeTrade sent.");

                string subscribeQuote = $" \"sub\": \"market.symbol.bbo\", \"id\": \"symbol bbo\"";
                byte[] subscribeQuoteAsBytes = Encoding.UTF8.GetBytes(subscribeQuote);
                ClientWebSocket.SendAsync(subscribeQuoteAsBytes, WebSocketMessageType.Text, true, CancellationToken.None).Wait();
                Console.WriteLine($"subscribeQuote sent.");

                ArraySegment<byte> bytesReceived = new ArraySegment<byte>(new byte[819200]);

                //Read from WebSocket
                while (ClientWebSocket.State == WebSocketState.Open)
                
                    WebSocketReceiveResult receiveResult = ClientWebSocket.ReceiveAsync(bytesReceived, CancellationToken.None).Result;
                    byte[] compressedBytes = bytesReceived.Array[..receiveResult.Count];
                    byte[] decompressedBytes;

                    //Decompress json
                    using (MemoryStream compressedStream = new MemoryStream(compressedBytes))
                    
                        using (GZipStream decompressionStream = new GZipStream(compressedStream, CompressionMode.Decompress))
                        
                            using (MemoryStream decompressedStream = new MemoryStream())
                            
                                decompressionStream.CopyTo(decompressedStream);
                                decompressedBytes = decompressedStream.ToArray();
                            
                        
                    
                    string json = Encoding.UTF8.GetString(decompressedBytes);

                    // Handle ping
                    if (json.Contains("ping"))
                    
                        pings++;
                        string pong = json.Replace("ping", "pong");
                        Console.WriteLine($"json received");
                        byte[] pongAsBytes = Encoding.UTF8.GetBytes(pong);
                        ClientWebSocket.SendAsync(pongAsBytes, WebSocketMessageType.Text, true, CancellationToken.None).Wait();
                        Console.WriteLine($"pong sent");
                        Console.WriteLine($"pings pings received");
                    
                    else
                    
                        // Uncomment to see that subscription worked
                        //Console.WriteLine(json);
                    
                
            
            catch (Exception exception)
            
                Console.WriteLine(exception);

                Console.WriteLine($"Huobi closed WebSocket after pings pings.");
            
        
    

这是我的程序的输出:

 "sub": "market.btcusdt.trade.detail", "id": "btcusdt trade" sent.
 "sub": "market.btcusdt.bbo", "id": "btcusdt bbo" sent.
"ping":1627090272242 received
"pong":1627090272242 sent
1 pings received
"ping":1627090277234 received
"pong":1627090277234 sent
2 pings received
"ping":1627090282238 received
"pong":1627090282238 sent
3 pings received
"ping":1627090287239 received
"pong":1627090287239 sent
4 pings received
"ping":1627090292238 received
"pong":1627090292238 sent
5 pings received
"ping":1627090297241 received
"pong":1627090297241 sent
6 pings received
System.AggregateException: One or more errors occurred. (The remote party closed the WebSocket connection without completing the close handshake.)
 ---> System.Net.WebSockets.WebSocketException (0x80004005): The remote party closed the WebSocket connection without completing the close handshake.
   at System.Net.WebSockets.ManagedWebSocket.ThrowIfEOFUnexpected(Boolean throwOnPrematureClosure)
   at System.Net.WebSockets.ManagedWebSocket.EnsureBufferContainsAsync(Int32 minimumRequiredBytes, CancellationToken cancellationToken, Boolean throwOnPrematureClosure)
   at System.Net.WebSockets.ManagedWebSocket.ReceiveAsyncPrivate[TWebSocketReceiveResultGetter,TWebSocketReceiveResult](Memory`1 payloadBuffer, CancellationToken cancellationToken, TWebSocketReceiveResultGetter resultGetter)
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
   at System.Threading.Tasks.Task`1.get_Result()
   at HuobiMarketWebSocketTest.HuobiMarketWebSocketTest.Main(String[] args) in C:\home\source\csharp\prog\HuobiMarketWebSocketTest - pong not recieved\HuobiMarketWebSocketTest\HuobiMarketWebSocketTest\HuobiMarketWebSocketTest.cs:line 156
Huobi closed WebSocket after 6 pings.

【问题讨论】:

【参考方案1】:

发生断开连接是因为 .NET ClientWebsocket 在内部使用 ManagedWebSocket,它会发送一个未经请求的 pong 帧作为其保持活动处理的一部分。根据 websocket 标准,这是正确的行为。但是,它似乎会导致火币的期货和掉期服务器出现问题(但不是现货)。如果将 ClientwebSocket.Options.KeepAliveInterval 设置为 TimeSpan.Zero,则可以避免 ManagedWebSocket 创建保活计时器,从而停止设置未经请求的 pong 帧。这样做的缺点是会话从 2 向心跳变为 1 向心跳,但由于在应用程序级别存在心跳,因此这里没有真正的缺点。

【讨论】:

以上是关于为啥火币服务器收不到 C# WebSocket Ping?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的OUTLOOK收不到邮件了呢?

为啥对方收不到我的邮件而显示邮件发送成功?

websocket前端发送一个消息 后端还没执行完 收不到第二条消息

JAVA socket 收不到数据 求指点

python websocket 获取火币 eth 历史行情

为啥注册有的软件收不到短信验证码?