简体   繁体   中英

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

I am new in the area of websocket.

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

var webSocket = new WebSocket(url);

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

3rd line of the code results following error:

"Server returned status code 200 when status code 101 was expected"

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

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?

Use the WebSocketSharp library and connect easily:

WebSocket Client

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

Step 1

Required namespace.

using WebSocketSharp;

The WebSocket class exists in the WebSocketSharp namespace.

Step 2

Creating a new instance of the WebSocket class with the WebSocket URL to connect.

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

The WebSocket class inherits the System.IDisposable interface, so you can use the using statement. And the WebSocket connection will be closed with close status 1001 (going away) when the control leaves the using block.

Step 3

Setting the WebSocket events.

WebSocket.OnOpen Event

A WebSocket.OnOpen event occurs when the WebSocket connection has been established.

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

e has passed as the System.EventArgs.Empty , so you don't need to use it.

WebSocket.OnMessage Event

A WebSocket.OnMessage event occurs when the WebSocket receives a message.

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

e has passed as a WebSocketSharp.MessageEventArgs.

e.Type property returns either WebSocketSharp.Opcode.Text or WebSocketSharp.Opcode.Binary that represents the type of the message. So by checking it, you can determine which item you should use.

If it returns Opcode.Text, you should use e.Data property that returns a string (represents the Text message).

Or if it returns Opcode.Binary , you should use e.RawData property that returns a byte[] (represents the Binary message).

if (e.Type == Opcode.Text) {
  // Do something with e.Data.
  ...

  return;
}

if (e.Type == Opcode.Binary) {
  // Do something with e.RawData.
  ...

  return;
}

WebSocket.OnError Event

A WebSocket.OnError event occurs when the WebSocket gets an error.

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

e has passed as a WebSocketSharp.ErrorEventArgs .

e.Message property returns a string that represents the error message.

If the error is due to an exception, e.Exception property returns a System.Exception instance that caused the error.

WebSocket.OnClose Event

A WebSocket.OnClose event occurs when the WebSocket connection has been closed.

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

e has passed as a WebSocketSharp.CloseEventArgs .

e.Code property returns a ushort that represents the status code indicating the reason for the close, and e.Reason property returns a string that represents the reason for the close.

Step 4

Connecting to the WebSocket server.

ws.Connect ();

If you would like to connect to the server asynchronously, you should use the WebSocket.ConnectAsync () method.

Step 5

Sending data to the WebSocket server.

ws.Send (data);

The WebSocket.Send method is overloaded.

You can use the WebSocket.Send (string), WebSocket.Send (byte[]) , or WebSocket.Send (System.IO.FileInfo) method to send the data.

If you would like to send the data asynchronously, you should use the WebSocket.SendAsync method.

ws.SendAsync (data, completed);

And also if you would like to do something when the send is complete, you should set completed to any Action<bool> delegate.

Step 6

Closing the WebSocket connection.

ws.Close (code, reason);

If you would like to close the connection explicitly, you should use the WebSocket.Close method.

The WebSocket.Close method is overloaded.

You can use the WebSocket.Close (), WebSocket.Close (ushort), WebSocket.Close (WebSocketSharp.CloseStatusCode), WebSocket.Close (ushort, string), or WebSocket.Close (WebSocketSharp.CloseStatusCode, string) method to close the connection.

If you would like to close the connection asynchronously, you should use the WebSocket.CloseAsync method.

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

}

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.

Since WebsocketSharp is not .NET Core compatible I suggest using this library . Here's some sample code

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

Be sure to use ManualResetEvent . Otherwise it doesn't work.

If you connect with a WebSocket client and you get an HTTP 200 as response, means that probably you are connecting to the wrong place (host, path and/or port).

Basically, you are connecting to a normal HTTP endpoint that is not understanding your WebSocket requirement, and it is just returning the "OK" response (HTTP 200). Probably the WebSocket server runs in another port or path in the same server.

Check your URL.

Not quite sure what happened to WebSocketSharp nuget package, however I noticed that now WebSocket# is showing up as most relevant result in nuget repo. It took me some time before I realized that Connect() is now returning Task , hopefully this example will be useful to someone:

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。

All the libraries mentioned above are Wrappers. The .Net Frameworks class doing this is System.Net.WebSockets.ClientWebSocket

Try this code:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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