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

后端 未结 6 1752
情书的邮戳
情书的邮戳 2021-01-30 07:38

I am new in the area of websocket.

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

var webSocket = new WebSocket(url);
6条回答
  •  礼貌的吻别
    2021-01-30 08:20

    TL; DR:

    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.

    I learned a lot from https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html .

    Full answer:

    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?

    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.

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

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

    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.

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

    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.

提交回复
热议问题