使用C#连接到websocket(我可以使用JavaScript连接,但C#给出状态代码200错误)

Jah*_*lam 14 javascript c# websocket

我是websocket领域的新手.

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

var webSocket = new WebSocket(url);
Run Code Online (Sandbox Code Playgroud)

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

ClientWebSocket webSocket = null;
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
Run Code Online (Sandbox Code Playgroud)

代码的第3行导致错误:

"当预期状态代码101时,服务器返回状态代码200"

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

我在C#代码中做了什么愚蠢的事情,或者服务器出了问题.我没有任何访问服务器的权限,因为我使用的网址是第三方网址.

你能否就这个问题给我任何建议?

Jag*_*raj 35

使用WebSocketSharp库并轻松连接:

WebSocket客户端

using System;
using WebSocketSharp;

namespace Example
{
  public class Program
  {
    public static void Main (string[] args)
    {
      using (var ws = new WebSocket ("ws://dragonsnest.far/Laputa")) {
        ws.OnMessage += (sender, e) =>
          Console.WriteLine ("Laputa says: " + e.Data);

        ws.Connect ();
        ws.Send ("BALUS");
        Console.ReadKey (true);
      }
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

步骤1

必需命名空间

using WebSocketSharp;
Run Code Online (Sandbox Code Playgroud)

WebSocket类存在于WebSocketSharp命名空间中.

第2步

使用要连接的WebSocket URL创建WebSocket类的新实例.

using (var ws = new WebSocket ("ws://example.com")) {
  ...
}
Run Code Online (Sandbox Code Playgroud)

WebSocket类继承System.IDisposable接口,因此您可以使用using语句.当控件离开使用块时,WebSocket连接将以关闭状态1001(离开)关闭.

第3步

设置WebSocket事件.

WebSocket.OnOpen 事件

一个WebSocket.OnOpen当WebSocket连接已经建立发生的事件.

ws.OnOpen += (sender, e) => {
  ...
};
Run Code Online (Sandbox Code Playgroud)

e已经过了System.EventArgs.Empty,所以你不需要使用它.

WebSocket.OnMessage 事件

WebSocket.OnMessage当网页套接字接收到消息事件发生.

ws.OnMessage += (sender, e) => {
  ...
};
Run Code Online (Sandbox Code Playgroud)

e已作为WebSocketSharp.MessageEventArgs传递.

e.Type property返回表示消息类型的WebSocketSharp.Opcode.Text或WebSocketSharp.Opcode.Binary.因此,通过检查,您可以确定应该使用哪个项目.

如果它返回Opcode.Text,您应该使用返回字符串的e.Data属性(表示Text消息).

或者,如果它返回Opcode.Binary,则应使用返回byte [](表示二进制消息)的e.RawData属性.

if (e.Type == Opcode.Text) {
  // Do something with e.Data.
  ...

  return;
}

if (e.Type == Opcode.Binary) {
  // Do something with e.RawData.
  ...

  return;
}
Run Code Online (Sandbox Code Playgroud)

WebSocket.OnError 事件

一个WebSocket.OnError当WebSocket的得到一个错误发生的事件.

ws.OnError += (sender, e) => {
  ...
};
Run Code Online (Sandbox Code Playgroud)

e已经过了WebSocketSharp.ErrorEventArgs.

e.Message属性返回表示错误消息的字符串.

如果错误是由异常System.Exception引起的,则e.Exception属性返回导致错误的实例.

WebSocket.OnClose 事件

WebSocket连接关闭时发生WebSocket.OnClose事件.

ws.OnClose += (sender, e) => {
  ...
};
Run Code Online (Sandbox Code Playgroud)

e已经过了WebSocketSharp.CloseEventArgs.

e.Code属性返回一个ushort,表示状态代码,表示关闭的原因,e.Reason属性返回一个字符串,表示关闭的原因.

第4步

连接到WebSocket服务器.

ws.Connect ();
Run Code Online (Sandbox Code Playgroud)

如果要以异步方式连接到服务器,则应使用WebSocket.ConnectAsync()方法.

第5步

将数据发送到WebSocket服务器.

ws.Send (data);
Run Code Online (Sandbox Code Playgroud)

WebSocket.Send方法已重载.

您可以使用WebSocket.Send (string), WebSocket.Send (byte[])WebSocket.Send (System.IO.FileInfo)方法发送数据.

如果要异步发送数据,则应使用WebSocket.SendAsync方法.

ws.SendAsync (data, completed);
Run Code Online (Sandbox Code Playgroud)

此外,如果您希望在发送完成后执行某些操作,则应将"已完成"设置为任何Action<bool>代理.

第6步

关闭WebSocket连接.

ws.Close (code, reason);
Run Code Online (Sandbox Code Playgroud)

如果要显式关闭连接,则应使用该WebSocket.Close方法.

WebSocket.Close方法已重载.

您可以使用WebSocket.Close(),WebSocket.Close(ushort),WebSocket.Close(WebSocketSharp.CloseStatusCode),WebSocket.Close(ushort,string)或WebSocket.Close(WebSocketSharp.CloseStatusCode,string)方法来关闭连接.

如果要异步关闭连接,则应使用该WebSocket.CloseAsync方法.


Har*_*rry 13

TL; 博士:

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

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

完整答案:

使用 Dotnet 客户端,这里有一个从我的真实代码中剪下来的例子,它说明了握手是如何进行的。大多数人不了解事物如何运作的最重要的一点是,接收消息时没有魔法事件。你自己创造。如何?

ReceiveAsync()Close接收到特殊帧时,您只需在结束的循环中执行。所以当你想断开连接时,你必须告诉你关闭的服务器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;
        
    }

}
Run Code Online (Sandbox Code Playgroud)

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

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


Boh*_*pak 9

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

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();
}
Run Code Online (Sandbox Code Playgroud)

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


vto*_*ola 5

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

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

检查您的网址。