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

本文关键字:连接 状态 错误 JavaScript websocket 我可以 可以使 使用 | 更新日期: 2023-09-27 17:51:07

我是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#代码中做了什么愚蠢的事情,还是服务器出了问题?我没有任何访问服务器的权限,因为我使用的url是第三方的。

关于这个问题你能给我一些建议吗?

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

;博士:

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

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

完整的答案:

使用Dotnet客户机,在这里,有一个从我的现实生活代码中剪下来的例子,它说明了握手是如何进行的。大多数人不理解的最重要的事情是,当接收到消息时,没有神奇的事件。你自己创造它。如何?

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

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

用于匹配任何唯一的随机数据。你需要令牌,在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 ?除了微软类的糟糕文档之外,我找不到任何其他原因。也许——如果出于一些非常奇怪的原因,你想在一个古老的。net框架中使用现代的WebSocket传输;)

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

由于WebsocketSharp不是。net Core兼容,我建议使用websocket-client代替。下面是一些示例代码

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作为响应,这意味着你可能连接到错误的地方(主机,路径和/或端口)。

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

不太确定WebSocketSharp nuget包发生了什么,但是我注意到现在WebSocketSharp #在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);
        }
    }
}

上面提到的所有库都是包装器。做这个的。net Frameworks类是System.Net.WebSockets.ClientWebSocket

Websocket url应该以ws://wss://开头,其中后者是安全的Websocket