繁体   English   中英

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

[英]Connecting to websocket using C# (I can connect using JavaScript, but C# gives Status code 200 error)

我是 websocket 领域的新手。

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

var webSocket = new WebSocket(url);

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

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

代码的第 3 行导致以下错误:

“当需要状态代码 101 时,服务器返回状态代码 200”

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

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

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

使用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);
      }
    }
  }
}

步骤1

必需命名空间

using WebSocketSharp;

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

第2步

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

using (var ws = new WebSocket ("ws://example.com")) {
  ...
}

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

第3步

设置WebSocket事件。

WebSocket.OnOpen事件

建立WebSocket连接时发生WebSocket.OnOpen事件。

ws.OnOpen += (sender, e) => {
  ...
};

e已作为System.EventArgs.Empty传递,因此您不需要使用它。

WebSocket.OnMessage事件

WebSocket接收消息时发生WebSocket.OnMessage事件。

ws.OnMessage += (sender, e) => {
  ...
};

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;
}

WebSocket.OnError事件

WebSocket收到错误时发生WebSocket.OnError事件。

ws.OnError += (sender, e) => {
  ...
};

e已作为WebSocketSharp.ErrorEventArgs传递。

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

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

WebSocket.OnClose事件

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

ws.OnClose += (sender, e) => {
  ...
};

e已作为WebSocketSharp.CloseEventArgs传递。

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

第4步

连接到WebSocket服务器。

ws.Connect ();

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

第5步

将数据发送到WebSocket服务器。

ws.Send (data);

WebSocket.Send方法已重载。

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

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

ws.SendAsync (data, completed);

如果您希望在发送完成后执行某些Action<bool> ,则应将完成设置为任何Action<bool>委托。

第6步

关闭WebSocket连接。

ws.Close (code, reason);

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

WebSocket.Close方法已重载。

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

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

TL; 博士:

在循环中使用ReceiveAsync()直到收到Close帧或CancellationToken 这就是您获取消息的方式。 发送是直接的,只是SendAsync() 不要在CloseOutputAsync() CloseAsync()之前使用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 以外的其他库? 除了 Microsoft 课程的糟糕文档之外,我找不到任何其他原因。 也许 - 如果出于某种非常奇怪的原因,您希望将现代 WebSocket 传输与古老的 .NET 框架结合使用;)

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

由于 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();
}

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

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

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

检查您的网址。

不太确定 WebSocketSharp nuget 包发生了什么,但是我注意到现在WebSocket#在 nuget 存储库中显示为最相关的结果。 我花了一些时间才意识到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);
        }
    }
}

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

上面提到的所有库都是 Wrappers。 .Net Frameworks 类是System.Net.WebSockets.ClientWebSocket

试试这个代码:

public class MyWebSocket : WsClient
{
    public override async void ResponseReceived(Stream inputStream)
    {
        throw new NotImplementedException();
    }

    public override Task<RequestType> SendMessageAsync<RequestType>(RequestType message)
    {
        throw new NotImplementedException();
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM