【问题标题】:Connecting to websocket using C# (I can connect using JavaScript, but C# gives Status code 200 error)使用 C# 连接到 websocket(我可以使用 JavaScript 连接,但 C# 给出状态码 200 错误)
【发布时间】:2015-08-11 23:02:26
【问题描述】:

我是 websocket 领域的新手。

我可以使用以下代码使用 JavaScript 连接到 websocket 服务器:

var webSocket = new WebSocket(url);

但是对于我的应用程序,我需要使用 c# 连接到同一台服务器。我使用的代码是:

ClientWebSocket webSocket = null;
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);

第三行代码结果如下错误:

“当预期状态码 101 时,服务器返回状态码 200”

经过一番调查,我意识到服务器在连接过程中无法将http协议切换到websocket协议。

我是在我的 C# 代码中做任何愚蠢的事情还是服务器出了问题。我无权访问服务器,因为我使用的网址是第三方网址。

您能就这个问题给我任何建议吗?

【问题讨论】:

    标签: javascript c# websocket


    【解决方案1】:

    TL;博士:

    在循环中使用ReceiveAsync(),直到收到Close 帧或取消CancellationToken。这就是您获取消息的方式。发送很简单,只需SendAsync()。不要在CloseOutputAsync() 之前使用CloseAsync() - 因为您想先停止接收循环。否则 - CloseAsync() 会挂起,或者如果您使用 CancellationToken 退出 ReceiveAsync() - CloseAsync() 会抛出。

    我从https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html 学到了很多。

    完整答案:

    使用 Dotnet 客户端,这里有一个从我的真实代码中截取的示例,它说明了握手是如何进行的。大多数人不了解事物如何运作的最重要的事情是收到消息时没有魔术事件。你自己创造它。怎么样?

    当接收到一个特殊的Close 帧时,您只需在一个结束的循环中执行ReceiveAsync()。所以当你想断开连接时,你必须用CloseOutputAsync 告诉服务器你关闭了,这样它就会用类似的Cloce 帧回复你的客户端,这样它就可以结束接收了。

    我的代码示例仅说明了最基本的外部传输机制。所以你发送和接收原始二进制消息。此时,您无法判断特定服务器响应与您发送的特定请求相关。您必须在编码/解码消息后自己匹配它们。为此使用任何序列化工具,但许多加密货币市场使用 Google 的协议缓冲区。这个名字说明了一切;)

    可以使用任何唯一的随机数据进行匹配。你需要令牌,在 C# 中我使用 Guid 类。

    然后我使用请求/响应匹配来使请求工作而不依赖于事件。 SendRequest() 方法一直等待,直到匹配的响应到达,或者...连接关闭。非常方便,并且允许编写比基于事件的方法更具可读性的代码。当然,您仍然可以在收到的消息上调用事件,只需确保它们不匹配任何需要响应的请求。

    哦,为了等待我的async 方法,我使用SemaphoreSlim。每个请求都将自己的信号量放入一个特殊的字典中,当我得到响应时,我通过响应令牌找到条目,释放信号量,处理它,从字典中删除。看似复杂,其实很简单。

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using System.Net.WebSockets;
    using System.Threading;
    using System.Threading.Tasks;
    
    namespace Example {
    
        public class WsClient : IDisposable {
    
            public int ReceiveBufferSize { get; set; } = 8192;
    
            public async Task ConnectAsync(string url) {
                if (WS != null) {
                    if (WS.State == WebSocketState.Open) return;
                    else WS.Dispose();
                }
                WS = new ClientWebSocket();
                if (CTS != null) CTS.Dispose();
                CTS = new CancellationTokenSource();
                await WS.ConnectAsync(new Uri(url), CTS.Token);
                await Task.Factory.StartNew(ReceiveLoop, CTS.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
            }
    
            public async Task DisconnectAsync() {
                if (WS is null) return;
                // TODO: requests cleanup code, sub-protocol dependent.
                if (WS.State == WebSocketState.Open) {
                    CTS.CancelAfter(TimeSpan.FromSeconds(2));
                    await WS.CloseOutputAsync(WebSocketCloseStatus.Empty, "", CancellationToken.None);
                    await WS.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
                }
                WS.Dispose();
                WS = null;
                CTS.Dispose();
                CTS = null;
            }
    
            private async Task ReceiveLoop() {
                var loopToken = CTS.Token;
                MemoryStream outputStream = null;
                WebSocketReceiveResult receiveResult = null;
                var buffer = new byte[ReceiveBufferSize];
                try {
                    while (!loopToken.IsCancellationRequested) {
                        outputStream = new MemoryStream(ReceiveBufferSize);
                        do {
                            receiveResult = await WS.ReceiveAsync(buffer, CTS.Token);
                            if (receiveResult.MessageType != WebSocketMessageType.Close)
                                outputStream.Write(buffer, 0, receiveResult.Count);
                        }
                        while (!receiveResult.EndOfMessage);
                        if (receiveResult.MessageType == WebSocketMessageType.Close) break;
                        outputStream.Position = 0;
                        ResponseReceived(outputStream);
                    }
                }
                catch (TaskCanceledException) { }
                finally {
                    outputStream?.Dispose();
                }
            }
    
            private async Task<ResponseType> SendMessageAsync<RequestType>(RequestType message) {
                // TODO: handle serializing requests and deserializing responses, handle matching responses to the requests.
            }
    
            private void ResponseReceived(Stream inputStream) {
                // TODO: handle deserializing responses and matching them to the requests.
                // IMPORTANT: DON'T FORGET TO DISPOSE THE inputStream!
            }
    
            public void Dispose() => DisconnectAsync().Wait();
    
            private ClientWebSocket WS;
            private CancellationTokenSource CTS;
            
        }
    
    }
    

    顺便说一句,为什么要使用内置的 .NET 以外的其他库?除了微软课程的糟糕文档之外,我找不到任何原因。也许 - 如果出于某种非常奇怪的原因,您想要将现代 WebSocket 传输与古老的 .NET 框架一起使用;)

    哦,我还没有测试过这个例子。它取自测试代码,但所有内部协议部分都被删除,只留下传输部分。

    【讨论】:

    • 谢谢!使用 WebSocketCloseStatus.Empty 调用 CloseOutputAsync 时出现错误。用 WebSocketCloseStatus.NormalClosure 调用它似乎可以修复它们。
    • 您是否有任务 SendMessageAsync 的示例实现,具有示例请求和响应类型?这对我来说是新的,只是将此代码粘贴到 VS.NET 中,它抱怨“并非所有代码路径都返回一个值”,并且我要么删除异步修饰符,使其同步,要么删除成员/方法。我假设要做的另一件事是实际实现该功能,但我可以举个例子。
    • 我将 SendMessageAsync 更改为以下内容并为我完美工作... public async Task SendMessageAsync(string message) { ArraySegment bytesToSend = new ArraySegment(Encoding.UTF8.GetBytes(message ));等待 WS.SendAsync(bytesToSend, WebSocketMessageType.Text, true, CancellationToken.None); }
    • 我想我已经更改了库中的代码以修复一些边缘情况。我计划在下周发布完整的资源。我的实际用途是物联网,它必须优雅地处理 Wi-Fi 信号消失或断电等事件。在这样的条件下测试 WS 让我能够观察到很多边缘情况,并为每一种情况找到最佳解决方案。我的最终发现是 - 最终 - 它总是会抛出 ;) 如果你拔掉插头。但是好的代码只会在非常特殊的情况下抛出异常,比如突然的物理断开。让客户端重新连接全自动是下一个级别。
    【解决方案2】:

    由于 WebsocketSharp 不兼容 .NET Core,我建议使用 this library。 这是一些示例代码

    static async Task Main(string[] args)
    {
        var url = new Uri("wss://echo.websocket.org");
        var exitEvent = new ManualResetEvent(false);
    
        using (var client = new WebsocketClient(url))
        {
            client.MessageReceived.Subscribe(msg => Console.WriteLine($"Message: {msg}"));
            await client.Start();
    
            await client.Send("Echo");
    
            exitEvent.WaitOne();
        }
    
        Console.ReadLine();
    }
    

    请务必使用ManualResetEvent。否则它不起作用。

    【讨论】:

    • 有一个 WebsocketSharp.Core 端口(客户端)在 Core 2.2 和 3.0 中都适用于我。
    • 很高兴知道。我已经有一段时间没有看到这个端口的 repo 中的活动了,所以我怀疑它是否值得一试。
    • 即时消息发送使用 await client.SendInstant("Echo")
    【解决方案3】:

    如果您使用 WebSocket 客户端连接并收到 HTTP 200 作为响应,则意味着您可能连接到错误的位置(主机、路径和/或端口)。

    基本上,您正在连接到一个不了解您的 WebSocket 要求的普通 HTTP 端点,它只是返回“OK”响应 (HTTP 200)。可能 WebSocket 服务器在同一服务器的另一个端口或路径中运行。

    检查您的网址。

    【讨论】:

      【解决方案4】:

      不太清楚 WebSocketSharp nuget 包发生了什么,但是我注意到现在 WebSocket# 显示为 nuget repo 中最相关的结果。我花了一些时间才意识到Connect() 现在正在返回Task,希望这个示例对某人有用:

      using System;
      using System.Threading.Tasks;
      using WebSocketSharp;
      
      namespace Example
      {
          class Program
          {
              private static void Main(string[] args)
              {
                  using (var ws = new WebSocket(url: "ws://localhost:1337", onMessage: OnMessage, onError: OnError))
                  {
                      ws.Connect().Wait();
                      ws.Send("Hey, Server!").Wait();
                      Console.ReadKey(true);
                  }
              }
      
              private static Task OnError(ErrorEventArgs errorEventArgs)
              {
                  Console.Write("Error: {0}, Exception: {1}", errorEventArgs.Message, errorEventArgs.Exception);
                  return Task.FromResult(0);
              }
      
              private static Task OnMessage(MessageEventArgs messageEventArgs)
              {
                  Console.Write("Message received: {0}", messageEventArgs.Text.ReadToEnd());
                  return Task.FromResult(0);
              }
          }
      }
      

      【讨论】:

      • 不应将 ws 包含在 using 中,因为它过早关闭并且从未收到响应。但适用于 2019 年 7 月 2 日
      • @John the Console.ReadKey 不允许代码离开 using 块,因此在按键之前不会执行任何清理操作
      【解决方案5】:

      Websocket URL 应该以 ws://wss:// 开头,其中后者是安全 websocket。

      【讨论】:

        【解决方案6】:

        上面提到的所有库都是包装器。执行此操作的 .Net Frameworks 类是 System.Net.WebSockets.ClientWebSocket

        【讨论】:

          猜你喜欢
          • 2015-07-02
          • 2016-09-26
          • 2022-11-14
          • 2019-02-15
          • 1970-01-01
          • 2020-09-03
          • 2010-10-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多