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

kmbjn2e3  于 2023-02-11  发布在  Java
关注(0)|答案(6)|浏览(299)

我是新的领域的WebSocket。
我可以使用JavaScript连接到WebSocket服务器,使用以下代码:

var webSocket = new WebSocket(url);

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

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

代码的第3行导致以下错误:
“服务器返回状态代码200,而预期状态代码101”
经过一点调查,我意识到不知何故,服务器不能切换http协议到WebSocket协议在连接过程中。
我在我的C#代码中做了什么愚蠢的事情或者服务器出了问题。我没有任何访问服务器的权限,因为我使用的URL是第三方的。
关于这个问题,你能给我提些建议吗?

iyr7buue

iyr7buue1#

靶病变; DR:
在循环中使用ReceiveAsync(),直到收到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帧给你的客户端,这样它就可以结束接收。
我的代码示例只说明了最基本的外部传输机制。因此,您发送和接收原始的二进制消息。此时,您无法分辨特定的服务器响应是否与您发送的特定请求相关。您必须在对消息进行编码/解码后自己匹配它们。可以使用任何序列化工具来实现这一点,但许多加密货币市场使用Google的Protocol Buffers。名称说明了一切;)
匹配任何唯一的随机数据都可以使用。你需要令牌,在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框架;)
哦,我还没有测试过这个例子。它取自测试过的代码,但是所有内部协议部分都被删除了,只留下了传输部分。

rlcwz9us

rlcwz9us2#

由于WebsocketSharp不兼容. NETCore,我建议使用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。否则它将不起作用。

bogh5gae

bogh5gae3#

如果您使用WebSocket客户端连接,并收到HTTP 200作为响应,则意味着您可能连接到了错误的位置(主机、路径和/或端口)。
基本上,您连接到的是一个普通的HTTP端点,它不理解您的WebSocket需求,并且它只是返回“OK”响应(HTTP 200)。可能WebSocket服务器运行在同一服务器的另一个端口或路径中。
检查您的URL。

raogr8fs

raogr8fs4#

不太清楚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);
        }
    }
}
8cdiaqws

8cdiaqws5#

上面提到的所有库都是Wrappers,执行此操作的. NetFrameworks类是System.Net.WebSockets.ClientWebSocket

sy5wg1nm

sy5wg1nm6#

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

相关问题