我是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是第三方的。
关于这个问题你能给我一些建议吗?;博士:
使用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