如何在没有 ASP.NET 的情况下使用 System.Net.WebSockets?
How to work with System.Net.WebSockets without ASP.NET?
我想在 .NET 4.5 及更高版本(在 Windows 8.1 上)中使用新的 System.Net.WebSockets
classes 实现一个简单的聊天服务器。但是,我只找到在 ASP.NET 环境中使用那些 classes 的例子(尤其是这里的例子:http://www.codemag.com/Article/1210051)
我没有这样的协议,并且希望尽可能将 websocket 服务器实现为 "raw",但不必重新实现所有 websocket 协议,因为 Microsoft 希望已经在 .NET 4.5 中实现了这一点。
我想简单地实例化一个新的 WebSocket
class 就像我对普通 Socket
所做的那样,但是构造函数是受保护的。所以我去创建一个继承自它的 class,但后来我注意到我必须实现如此多的抽象方法和属性,看起来我正在重写整个逻辑(特别是因为我必须实现 State
或 SendAsync
).
恐怕 MSDN 文档对我没有帮助。那里的文档处于预发布状态,许多评论只是说 "TBD" 或 "when its implemented".
我刚刚偶然发现 this link,它展示了如何仅使用 System.Net.WebSockets
实现来实现 IHttpHandler
。需要处理程序,因为 .NET WebSocket 实现依赖于 IIS 8+。
using System;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
namespace AspNetWebSocketEcho
{
public class EchoHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
context.AcceptWebSocketRequest(HandleWebSocket);
else
context.Response.StatusCode = 400;
}
private async Task HandleWebSocket(WebSocketContext wsContext)
{
const int maxMessageSize = 1024;
byte[] receiveBuffer = new byte[maxMessageSize];
WebSocket socket = wsContext.WebSocket;
while (socket.State == WebSocketState.Open)
{
WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else if (receiveResult.MessageType == WebSocketMessageType.Binary)
{
await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept binary frame", CancellationToken.None);
}
else
{
int count = receiveResult.Count;
while (receiveResult.EndOfMessage == false)
{
if (count >= maxMessageSize)
{
string closeMessage = string.Format("Maximum message size: {0} bytes.", maxMessageSize);
await socket.CloseAsync(WebSocketCloseStatus.MessageTooLarge, closeMessage, CancellationToken.None);
return;
}
receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer, count, maxMessageSize - count), CancellationToken.None);
count += receiveResult.Count;
}
var receivedString = Encoding.UTF8.GetString(receiveBuffer, 0, count);
var echoString = "You said " + receivedString;
ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(echoString));
await socket.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
public bool IsReusable
{
get { return true; }
}
}
}
希望对您有所帮助!
是的。
最简单的方法是使用 HTTPListener。如果您搜索 HTTPListener WebSocket,您会找到大量示例。
一言以蔽之(伪代码)
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add("http://localhost/");
httpListener.Start();
HttpListenerContext context = await httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync( ... );
}
}
需要 .NET 4.5 和 Windows8 或更高版本。
伊恩的回答肯定是好的,但我需要一个循环过程。互斥锁对我来说很关键。这是一个基于他的工作 .net core 2 示例。我不能说这个循环的可扩展性。
using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace WebSocketServerConsole
{
public class Program
{
static HttpListener httpListener = new HttpListener();
private static Mutex signal = new Mutex();
public static void Main(string[] args)
{
httpListener.Prefixes.Add("http://localhost:8080/");
httpListener.Start();
while (signal.WaitOne())
{
ReceiveConnection();
}
}
public static async System.Threading.Tasks.Task ReceiveConnection()
{
HttpListenerContext context = await
httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello world")),
WebSocketMessageType.Text, true, CancellationToken.None);
}
}
signal.ReleaseMutex();
}
}
}
和一个测试 html 页面。
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:8080/";
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
}
function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
}
function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>
这是我完整的工作示例...
- 启动主机
namespace ConsoleApp1;
public static class Program
{
public static async Task Main(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<Server>();
services.AddHostedService<Server>();
});
IHost host = hostBuilder.Build();
await host.RunAsync();
}
}
- 创建一个服务器来接受客户并与他们交谈
using ConsoleApp15.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.WebSockets;
using System.Text;
namespace ConsoleApp15;
public class Server : IHostedService
{
private readonly ILogger<Server> Logger;
private readonly HttpListener HttpListener = new();
public Server(ILogger<Server> logger)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
HttpListener.Prefixes.Add("http://localhost:8080/");
}
public async Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Started");
HttpListener.Start();
while (!cancellationToken.IsCancellationRequested)
{
HttpListenerContext? context = await HttpListener.GetContextAsync().WithCancellationToken(cancellationToken);
if (context is null)
return;
if (!context.Request.IsWebSocketRequest)
context.Response.Abort();
else
{
HttpListenerWebSocketContext? webSocketContext =
await context.AcceptWebSocketAsync(subProtocol: null).WithCancellationToken(cancellationToken);
if (webSocketContext is null)
return;
string clientId = Guid.NewGuid().ToString();
WebSocket webSocket = webSocketContext.WebSocket;
_ = Task.Run(async() =>
{
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
await webSocket.SendAsync(
Encoding.ASCII.GetBytes($"Hello {clientId}\r\n"),
WebSocketMessageType.Text,
endOfMessage: true,
cancellationToken);
}
});
_ = Task.Run(async() =>
{
byte[] buffer = new byte[1024];
var stringBuilder = new StringBuilder(2048);
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
WebSocketReceiveResult receiveResult =
await webSocket.ReceiveAsync(buffer, cancellationToken);
if (receiveResult.Count == 0)
return;
stringBuilder.Append(Encoding.ASCII.GetString(buffer, 0, receiveResult.Count));
if (receiveResult.EndOfMessage)
{
Console.WriteLine($"{clientId}: {stringBuilder}");
stringBuilder = new StringBuilder();
}
}
});
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Stopping...");
HttpListener.Stop();
Logger.LogInformation("Stopped");
return Task.CompletedTask;
}
}
- 为不接受
CancellationToken
参数的异步方法创建 WithCancellationToken
。这是为了让服务器在被告知时正常关闭。
namespace ConsoleApp15.Extensions;
public static class TaskExtensions
{
public static async Task<T?> WithCancellationToken<T>(this Task<T> source, CancellationToken cancellationToken)
{
var cancellationTask = new TaskCompletionSource<bool>();
cancellationToken.Register(() => cancellationTask.SetCanceled());
_ = await Task.WhenAny(source, cancellationTask.Task);
if (cancellationToken.IsCancellationRequested)
return default;
return source.Result;
}
}
- 启动 Postman 应用程序
- 文件 => 新建
- Select“WebSocket 请求”
- 输入以下内容作为 url
ws://localhost:8080/
- 点击[连接]
我想在 .NET 4.5 及更高版本(在 Windows 8.1 上)中使用新的 System.Net.WebSockets
classes 实现一个简单的聊天服务器。但是,我只找到在 ASP.NET 环境中使用那些 classes 的例子(尤其是这里的例子:http://www.codemag.com/Article/1210051)
我没有这样的协议,并且希望尽可能将 websocket 服务器实现为 "raw",但不必重新实现所有 websocket 协议,因为 Microsoft 希望已经在 .NET 4.5 中实现了这一点。
我想简单地实例化一个新的 WebSocket
class 就像我对普通 Socket
所做的那样,但是构造函数是受保护的。所以我去创建一个继承自它的 class,但后来我注意到我必须实现如此多的抽象方法和属性,看起来我正在重写整个逻辑(特别是因为我必须实现 State
或 SendAsync
).
恐怕 MSDN 文档对我没有帮助。那里的文档处于预发布状态,许多评论只是说 "TBD" 或 "when its implemented".
我刚刚偶然发现 this link,它展示了如何仅使用 System.Net.WebSockets
实现来实现 IHttpHandler
。需要处理程序,因为 .NET WebSocket 实现依赖于 IIS 8+。
using System;
using System.Web;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Net.WebSockets;
namespace AspNetWebSocketEcho
{
public class EchoHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
if (context.IsWebSocketRequest)
context.AcceptWebSocketRequest(HandleWebSocket);
else
context.Response.StatusCode = 400;
}
private async Task HandleWebSocket(WebSocketContext wsContext)
{
const int maxMessageSize = 1024;
byte[] receiveBuffer = new byte[maxMessageSize];
WebSocket socket = wsContext.WebSocket;
while (socket.State == WebSocketState.Open)
{
WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);
if (receiveResult.MessageType == WebSocketMessageType.Close)
{
await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
}
else if (receiveResult.MessageType == WebSocketMessageType.Binary)
{
await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "Cannot accept binary frame", CancellationToken.None);
}
else
{
int count = receiveResult.Count;
while (receiveResult.EndOfMessage == false)
{
if (count >= maxMessageSize)
{
string closeMessage = string.Format("Maximum message size: {0} bytes.", maxMessageSize);
await socket.CloseAsync(WebSocketCloseStatus.MessageTooLarge, closeMessage, CancellationToken.None);
return;
}
receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer, count, maxMessageSize - count), CancellationToken.None);
count += receiveResult.Count;
}
var receivedString = Encoding.UTF8.GetString(receiveBuffer, 0, count);
var echoString = "You said " + receivedString;
ArraySegment<byte> outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(echoString));
await socket.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None);
}
}
}
public bool IsReusable
{
get { return true; }
}
}
}
希望对您有所帮助!
是的。
最简单的方法是使用 HTTPListener。如果您搜索 HTTPListener WebSocket,您会找到大量示例。
一言以蔽之(伪代码)
HttpListener httpListener = new HttpListener();
httpListener.Prefixes.Add("http://localhost/");
httpListener.Start();
HttpListenerContext context = await httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync( ... );
}
}
需要 .NET 4.5 和 Windows8 或更高版本。
伊恩的回答肯定是好的,但我需要一个循环过程。互斥锁对我来说很关键。这是一个基于他的工作 .net core 2 示例。我不能说这个循环的可扩展性。
using System;
using System.Net;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
namespace WebSocketServerConsole
{
public class Program
{
static HttpListener httpListener = new HttpListener();
private static Mutex signal = new Mutex();
public static void Main(string[] args)
{
httpListener.Prefixes.Add("http://localhost:8080/");
httpListener.Start();
while (signal.WaitOne())
{
ReceiveConnection();
}
}
public static async System.Threading.Tasks.Task ReceiveConnection()
{
HttpListenerContext context = await
httpListener.GetContextAsync();
if (context.Request.IsWebSocketRequest)
{
HttpListenerWebSocketContext webSocketContext = await context.AcceptWebSocketAsync(null);
WebSocket webSocket = webSocketContext.WebSocket;
while (webSocket.State == WebSocketState.Open)
{
await webSocket.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("Hello world")),
WebSocketMessageType.Text, true, CancellationToken.None);
}
}
signal.ReleaseMutex();
}
}
}
和一个测试 html 页面。
<!DOCTYPE html>
<meta charset="utf-8" />
<title>WebSocket Test</title>
<script language="javascript" type="text/javascript">
var wsUri = "ws://localhost:8080/";
var output;
function init()
{
output = document.getElementById("output");
testWebSocket();
}
function testWebSocket()
{
websocket = new WebSocket(wsUri);
websocket.onopen = function(evt) { onOpen(evt) };
websocket.onclose = function(evt) { onClose(evt) };
websocket.onmessage = function(evt) { onMessage(evt) };
websocket.onerror = function(evt) { onError(evt) };
}
function onOpen(evt)
{
writeToScreen("CONNECTED");
doSend("WebSocket rocks");
}
function onClose(evt)
{
writeToScreen("DISCONNECTED");
}
function onMessage(evt)
{
writeToScreen('<span style="color: blue;">RESPONSE: ' + evt.data+'</span>');
}
function onError(evt)
{
writeToScreen('<span style="color: red;">ERROR:</span> ' + evt.data);
}
function doSend(message)
{
writeToScreen("SENT: " + message);
websocket.send(message);
}
function writeToScreen(message)
{
var pre = document.createElement("p");
pre.style.wordWrap = "break-word";
pre.innerHTML = message;
output.appendChild(pre);
}
window.addEventListener("load", init, false);
</script>
<h2>WebSocket Test</h2>
<div id="output"></div>
这是我完整的工作示例...
- 启动主机
namespace ConsoleApp1;
public static class Program
{
public static async Task Main(string[] args)
{
IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args)
.ConfigureServices(services =>
{
services.AddSingleton<Server>();
services.AddHostedService<Server>();
});
IHost host = hostBuilder.Build();
await host.RunAsync();
}
}
- 创建一个服务器来接受客户并与他们交谈
using ConsoleApp15.Extensions;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Net;
using System.Net.WebSockets;
using System.Text;
namespace ConsoleApp15;
public class Server : IHostedService
{
private readonly ILogger<Server> Logger;
private readonly HttpListener HttpListener = new();
public Server(ILogger<Server> logger)
{
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
HttpListener.Prefixes.Add("http://localhost:8080/");
}
public async Task StartAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Started");
HttpListener.Start();
while (!cancellationToken.IsCancellationRequested)
{
HttpListenerContext? context = await HttpListener.GetContextAsync().WithCancellationToken(cancellationToken);
if (context is null)
return;
if (!context.Request.IsWebSocketRequest)
context.Response.Abort();
else
{
HttpListenerWebSocketContext? webSocketContext =
await context.AcceptWebSocketAsync(subProtocol: null).WithCancellationToken(cancellationToken);
if (webSocketContext is null)
return;
string clientId = Guid.NewGuid().ToString();
WebSocket webSocket = webSocketContext.WebSocket;
_ = Task.Run(async() =>
{
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
await Task.Delay(1000);
await webSocket.SendAsync(
Encoding.ASCII.GetBytes($"Hello {clientId}\r\n"),
WebSocketMessageType.Text,
endOfMessage: true,
cancellationToken);
}
});
_ = Task.Run(async() =>
{
byte[] buffer = new byte[1024];
var stringBuilder = new StringBuilder(2048);
while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested)
{
WebSocketReceiveResult receiveResult =
await webSocket.ReceiveAsync(buffer, cancellationToken);
if (receiveResult.Count == 0)
return;
stringBuilder.Append(Encoding.ASCII.GetString(buffer, 0, receiveResult.Count));
if (receiveResult.EndOfMessage)
{
Console.WriteLine($"{clientId}: {stringBuilder}");
stringBuilder = new StringBuilder();
}
}
});
}
}
}
public Task StopAsync(CancellationToken cancellationToken)
{
Logger.LogInformation("Stopping...");
HttpListener.Stop();
Logger.LogInformation("Stopped");
return Task.CompletedTask;
}
}
- 为不接受
CancellationToken
参数的异步方法创建WithCancellationToken
。这是为了让服务器在被告知时正常关闭。
namespace ConsoleApp15.Extensions;
public static class TaskExtensions
{
public static async Task<T?> WithCancellationToken<T>(this Task<T> source, CancellationToken cancellationToken)
{
var cancellationTask = new TaskCompletionSource<bool>();
cancellationToken.Register(() => cancellationTask.SetCanceled());
_ = await Task.WhenAny(source, cancellationTask.Task);
if (cancellationToken.IsCancellationRequested)
return default;
return source.Result;
}
}
- 启动 Postman 应用程序
- 文件 => 新建
- Select“WebSocket 请求”
- 输入以下内容作为 url
ws://localhost:8080/
- 点击[连接]