本文介绍了使用 C# 连接到 websocket(我可以使用 JavaScript 连接,但 C# 给出状态代码 200 错误)的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我是 websocket 领域的新手.

I am new in the area of websocket.

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

I can connect to websocket server using JavaScript using this code:

var webSocket = new WebSocket(url);

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

But for my application, I need to connect to the same server using c#. The code I am using is:

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

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

3rd line of the code results following error:

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

经过一番调查,我发现在连接过程中,不知何故服务器无法将http协议切换为websocket协议.

After little bit of survey, I realised that somehow server can't switch http protocol to websocket protocol during connection process.

我在我的 C# 代码中做了什么愚蠢的事情,或者服务器出了什么问题.我无法访问服务器,因为我使用的 url 是第三方的.

Am I doing anything stupid in my C# code or there is something going wrong with the server. I don't have any access to the server, as the url I am using is a third party one .

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

Could you please give me any suggestion regarding the issue?

推荐答案

TL;博士:

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

Use ReceiveAsync() in loop until Close frame is received or CancellationToken is canceled. That's how you get your messages. Sending is straightworward, just SendAsync(). Do not use CloseAsync() before CloseOutputAsync() - because you want to stop your receiving loop first. Otherwise - either the CloseAsync() would hang, or if you use CancellationToken to quit ReceiveAsync() - the CloseAsync() would throw.

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

完整答案:

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

Use Dotnet client, here, have an example cut out from my real life code, that illustrate how the handshaking is made. The most important thing most people don't understand about how the thing operates is that there is no magic event when a message is received. You create it yourself. How?

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

You just perform ReceiveAsync() in a loop that ends, when a special Close frame is received. So when you want to disconnect you have to tell the server you close with CloseOutputAsync, so it would reply with a similar Cloce frame to your client, so it would be able to end receiving.

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

My code example illustrates only the most basic, outer transmission mechanism. So you send and receive raw binary messages. At this point you cannot tell the specific server response is related to the specific request you've sent. You have to match them yourself after coding / decoding messages. Use any serialization tool for that, but many crypto currency markets use Protocol Buffers from Google. The name says it all ;)

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

For matching any unique random data can be used. You need tokens, in C# I use Guid class for that.

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

Then I use request / response matching to make request work without dependency on events. The SendRequest() methods awaits until matching response arrives, or... the connection is closed. Very handy and allows to make way more readable code than in event-based approach. Of course you can still invoke events on messages received, just make sure they are not matched to any requests that require response.

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

Oh, and for waiting in my async method I use SemaphoreSlim. Each request puts its own semaphore in a special dictionary, when I get the response, I find the entry by the response token, release the semaphore, dispose it, remove from the dictionary. Seems complicated, but it's actually pretty simple.

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 框架结合使用;)

BTW, why use other libraries than the .NET built in? I can't find any reason other than maybe poor documentation of the Microsoft's classes. Maybe - if for some really weird reason you would want to use modern WebSocket transport with an ancient .NET Framework ;)

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

Oh, and I haven't tested the example. It's taken from the tested code, but all inner protocol parts were removed to leave only the transport part.

这篇关于使用 C# 连接到 websocket(我可以使用 JavaScript 连接,但 C# 给出状态代码 200 错误)的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-24 09:50