[英]Can C# connect to a WebSocket using Socket(Not WebSocket) class
[英]Connecting to websocket using C# (I can connect using JavaScript, but C# gives Status code 200 error)
我是 websocket 领域的新手。
我可以使用以下代码使用 JavaScript 连接到 websocket 服务器:
var webSocket = new WebSocket(url);
但是对于我的应用程序,我需要使用 c# 连接到同一台服务器。 我正在使用的代码是:
ClientWebSocket webSocket = null;
webSocket = new ClientWebSocket();
await webSocket.ConnectAsync(new Uri(url), CancellationToken.None);
代码的第 3 行导致以下错误:
“当需要状态代码 101 时,服务器返回状态代码 200”
经过一点调查,我意识到在连接过程中服务器无法将http协议切换为websocket协议。
我在我的 C# 代码中做了什么愚蠢的事情,或者服务器出了什么问题。 我没有任何访问服务器的权限,因为我使用的 url 是第三方的。
你能就这个问题给我任何建议吗?
使用WebSocketSharp库并轻松连接:
WebSocket客户端
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);
}
}
}
}
步骤1
必需命名空间
using WebSocketSharp;
WebSocket类存在于WebSocketSharp命名空间中。
第2步
使用要连接的WebSocket URL创建WebSocket类的新实例。
using (var ws = new WebSocket ("ws://example.com")) {
...
}
WebSocket类继承System.IDisposable接口,因此您可以使用using语句。 当控件离开使用块时,WebSocket连接将以关闭状态1001(离开)关闭。
第3步
设置WebSocket事件。
WebSocket.OnOpen
事件 建立WebSocket连接时发生WebSocket.OnOpen
事件。
ws.OnOpen += (sender, e) => {
...
};
e已作为System.EventArgs.Empty
传递,因此您不需要使用它。
WebSocket.OnMessage
事件 WebSocket接收消息时发生WebSocket.OnMessage
事件。
ws.OnMessage += (sender, e) => {
...
};
e已作为WebSocketSharp.MessageEventArgs传递。
e.Type property
返回表示消息类型的WebSocketSharp.Opcode.Text或WebSocketSharp.Opcode.Binary。 因此,通过检查,您可以确定应该使用哪个项目。
如果它返回Opcode.Text,您应该使用返回字符串的e.Data属性(表示Text消息)。
或者,如果它返回Opcode.Binary
,则应使用返回byte [](表示二进制消息)的e.RawData属性。
if (e.Type == Opcode.Text) {
// Do something with e.Data.
...
return;
}
if (e.Type == Opcode.Binary) {
// Do something with e.RawData.
...
return;
}
WebSocket.OnError
事件 WebSocket收到错误时发生WebSocket.OnError
事件。
ws.OnError += (sender, e) => {
...
};
e已作为WebSocketSharp.ErrorEventArgs
传递。
e.Message属性返回表示错误消息的字符串。
如果错误是由异常引起的,则e.Exception属性返回导致错误的System.Exception
实例。
WebSocket.OnClose
事件 WebSocket连接关闭时发生WebSocket.OnClose事件。
ws.OnClose += (sender, e) => {
...
};
e已作为WebSocketSharp.CloseEventArgs
传递。
e.Code属性返回一个ushort,表示状态代码,表示关闭的原因,e.Reason属性返回一个字符串,表示关闭的原因。
第4步
连接到WebSocket服务器。
ws.Connect ();
如果要以异步方式连接到服务器,则应使用WebSocket.ConnectAsync()方法。
第5步
将数据发送到WebSocket服务器。
ws.Send (data);
WebSocket.Send方法已重载。
您可以使用WebSocket.Send (string), WebSocket.Send (byte[])
或WebSocket.Send (System.IO.FileInfo)
方法来发送数据。
如果要异步发送数据,则应使用WebSocket.SendAsync方法。
ws.SendAsync (data, completed);
如果您希望在发送完成后执行某些Action<bool>
,则应将完成设置为任何Action<bool>
委托。
第6步
关闭WebSocket连接。
ws.Close (code, reason);
如果要显式关闭连接,则应使用WebSocket.Close
方法。
WebSocket.Close方法已重载。
您可以使用WebSocket.Close(),WebSocket.Close(ushort),WebSocket.Close(WebSocketSharp.CloseStatusCode),WebSocket.Close(ushort,string)或WebSocket.Close(WebSocketSharp.CloseStatusCode,string)方法来关闭连接。
如果要异步关闭连接,则应使用WebSocket.CloseAsync
方法。
TL; 博士:
在循环中使用ReceiveAsync()
直到收到Close
帧或CancellationToken
。 这就是您获取消息的方式。 发送是直接的,只是SendAsync()
。 不要在CloseOutputAsync()
CloseAsync()
之前使用CloseAsync()
- 因为您想先停止接收循环。 否则 - CloseAsync()
将挂起,或者如果您使用CancellationToken
退出ReceiveAsync()
- CloseAsync()
将抛出。
我从https://mcguirev10.com/2019/08/17/how-to-close-websocket-correctly.html学到了很多东西。
完整答案:
使用 Dotnet 客户端,这里有一个从我的真实代码中剪下来的例子,它说明了握手是如何进行的。 大多数人不了解事物如何运作的最重要的一点是,接收消息时没有魔法事件。 你自己创造。 如何?
当接收到特殊的Close
帧时,您只需在结束的循环中执行ReceiveAsync()
。 所以当你想断开连接时,你必须告诉服务器你用CloseOutputAsync
关闭,这样它就会用一个类似的Cloce
帧回复你的客户端,这样它就可以结束接收。
我的代码示例仅说明了最基本的外部传输机制。 所以你发送和接收原始二进制消息。 此时,您无法判断特定服务器响应与您发送的特定请求相关。 您必须在编码/解码消息后自己匹配它们。 为此使用任何序列化工具,但许多加密货币市场使用 Google 的协议缓冲区。 这个名字说明了一切 ;)
为了匹配可以使用任何唯一的随机数据。 您需要令牌,在 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 以外的其他库? 除了 Microsoft 课程的糟糕文档之外,我找不到任何其他原因。 也许 - 如果出于某种非常奇怪的原因,您希望将现代 WebSocket 传输与古老的 .NET 框架结合使用;)
哦,我还没有测试过这个例子。 它取自测试代码,但删除了所有内部协议部分,只留下传输部分。
由于 WebsocketSharp 与 .NET Core 不兼容,我建议使用这个库。 这是一些示例代码
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 作为响应,则意味着您可能连接到错误的位置(主机、路径和/或端口)。
基本上,您正在连接到一个不理解您的 WebSocket 要求的普通 HTTP 端点,它只是返回“OK”响应(HTTP 200)。 可能 WebSocket 服务器在同一服务器的另一个端口或路径中运行。
检查您的网址。
不太确定 WebSocketSharp nuget 包发生了什么,但是我注意到现在WebSocket#在 nuget 存储库中显示为最相关的结果。 我花了一些时间才意识到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);
}
}
}
Websocket URL 应该以ws://
或wss://
开头,后者是安全的 websocket。
上面提到的所有库都是 Wrappers。 .Net Frameworks 类是System.Net.WebSockets.ClientWebSocket
试试这个代码:
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();
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.