.NET 8.0 使用 WebSocket

使用 WebSocket

WebSocket 是一项关键技术,它支持客户端和服务器之间的全双工实时通信,从而促进联网应用中更动态的数据交换。与遵循请求-响应模式的传统 HTTP 请求不同,WebSocket 提供持久连接,数据可以在两个方向上自由流动。这使得 WebSocket 对于需要低延迟通信的应用程序(例如在线游戏、实时聊天和实时财务数据流)尤为有用。借助 .NET 8 和 C# 12 及更高版本的全面支持,开发人员可以构建高度交互和响应迅速的应用程序,并受益于 WebSocket 的优势。

在 .NET 中使用 WebSocket 涉及服务器端和客户端组件。在服务器上,ASP.NET Core 提供对 WebSocket 的内置支持,允许服务器处理传入的 WebSocket 连接、管理数据流并响应客户端请求。这是通过将 WebSocket 中间件集成到服务器应用程序中实现的。在客户端,开发人员可以使用 System.Net.WebSockets 命名空间建立 WebSocket 连接、发送和接收消息以及管理 WebSocket 连接的生命周期。

本章将全面指导您如何使用 .NET 和 C# 在服务器和客户端场景中实现 WebSocket 通信。它涵盖了从 WebSocket 连接的建立到消息处理、错误管理以及该协议在创建实时交互式应用程序方面的优势等所有内容。读完本章后,您将能够充分理解并准备好在 .NET 应用程序中利用 WebSocket 进行高性能网络编程。

WebSocket协议概述

WebSocket 协议支持通过单个 TCP 连接进行全双工双向通信。与基于请求-响应模型的传统 HTTP 不同,WebSocket 允许客户端和服务器之间持续通信,而无需为每条消息建立新连接的开销。这使得 WebSocket 非常适合需要实时数据传输的场景,例如聊天应用程序、直播和互动游戏,在这些场景中,低延迟和持续的数据流至关重要。

WebSocket 连接始于客户端发起的 HTTP 握手,请求升级到 WebSocket 协议。服务器确认并接受请求后,协议将切换到 WebSocket,连接将变为持久连接。从此时起,客户端和服务器可以独立发送和接收消息,从而使通信模型异步且高效。这种维持持久连接的能力消除了对多个 HTTP 请求的需要,并显著降低了传统轮询机制的开销。

WebSocket 协议基于帧,这意味着数据以离散帧的形式传输。这些帧可以承载文本或二进制数据,从而根据应用程序的需求实现灵活的通信。每个帧都包含控制信息,例如消息分段或连接终止。这种轻量级结构使 WebSocket 能够以最小的性能影响处理高流量,使其成为需要高效高速通信的应用程序的热门选择。

.NET 集成了对 WebSocket 协议的支持,为开发人员提供了强大的工具,用于在服务器端和客户端实现基于 WebSocket 的通信。使用 System.Net.WebSockets 命名空间,开发人员可以轻松管理 WebSocket 连接、处理消息,并确保其应用程序实现高效、实时的通信。随着本章的深入,我们将探讨在 .NET 中设置和管理 WebSocket 连接的技术细节,并展示该协议如何增强现代网络应用程序的性能和交互性。

用例、优势以及与传统 HTTP 的比较

WebSocket 以其独特的优势脱颖而出,尤其是在需要客户端和服务器之间实时、低延迟通信的应用中。与基于请求-响应模型且每次交互都需要重新建立连接的 HTTP 不同,WebSocket 能够维持持久的全双工连接。这种开放的连接允许持续的数据流,从而带来更加无缝且响应迅速的用户体验。WebSocket 在聊天应用、实时数据反馈、在线多人游戏和物联网系统等场景中表现出色,因为及时的数据传输和更低的延迟至关重要。客户端和服务器能够随时发送数据,而无需不断重新建立连接,这正是 WebSocket 在这些用例中脱颖而出的关键所在。

与适用于获取网页或提交表单等简单请求的传统 HTTP 相比,WebSocket 为需要频繁或持续数据交换的应用程序提供了显著的效率提升。在传统 HTTP 中,轮询会导致不必要的网络流量和延迟,因为客户端必须反复检查更新。相比之下,WebSocket 通过维护直接的数据传输管道消除了这种需求,使服务器能够即时推送更新。这种高效性带来了更快的数据传输速度、更低的延迟和更低的服务器负载,使 WebSocket 成为动态实时联网应用程序的理想选择。

WebSocket 在需要实时通信时才真正发挥作用,例如实时聊天、游戏或实时分析,在这些情况下,客户端和服务器都必须立即对事件做出响应。WebSocket 的实时功能为开发者和系统架构师开辟了无限可能,使他们能够创建响应更快、更具吸引力的应用程序。了解每种协议的优势和局限性有助于根据应用程序的特定需求选择合适的解决方案。WebSocket 的实时功能不仅仅是一项功能,更是那些致力于构建实时通信应用程序的人们兴奋和着迷的理由。

WebSocket 协议机制和高级功能

WebSocket 协议的运作方式与传统 HTTP 不同,它首先通过初始 HTTP 握手来升级连接。一旦此握手完成且连接升级完成,通信就会切换到持久 WebSocket 连接,从而允许客户端和服务器之间持续的数据流。这种持久性使得 WebSocket 对于实时应用尤为有效,因为它消除了每次交互都建立新连接的开销。

WebSocket 使用基于帧的结构来处理数据传输,从而确保高效地将消息分解为可携带文本或二进制数据的帧。该结构还包含控制帧来管理连接生命周期事件,例如关闭连接或使用乒乓帧保持连接。客户端和服务器能够随时发起数据传输,而无需像 HTTP 那样依赖客户端请求,这使得 WebSocket 成为动态交互的理想选择,例如聊天系统和在线多人游戏,因为这些情况下即时响应至关重要。

WebSockets 提供高级功能,为实时通信系统增添更多灵活性和可扩展性。压缩、客户端组管理和空闲连接处理等功能确保 WebSockets 能够适应各种用例,并随着应用程序的增长而扩展。压缩有助于减小消息大小并提升性能,尤其是在带宽受限的环境中。客户端组管理允许开发者创建有针对性的交互,例如聊天室或游戏大厅,在这些情况下,消息仅向特定群组广播。此外,使用乒乓消息或超时机制来控制空闲连接,可确保高效管理服务器资源。总而言之,这些功能有助于创建可扩展且强大的实时通信系统,使 WebSockets 成为现代联网应用程序的首选解决方案。WebSockets 的强大性和可扩展性必定会引起技术决策者的共鸣,使他们在选择 WebSockets 作为其应用程序时充满信心。

实际考虑

使用 WebSocket 时,您需要牢记一些实际注意事项,以确保您的实现高效、可靠且能够充分满足应用程序的需求。虽然 WebSocket 提供了强大的实时通信功能,但由于其持久性以及对低延迟环境的需求,也需要谨慎管理。

首先要考虑的是可扩展性。与每个请求都是短暂的 HTTP 不同,WebSocket 连接是持久的。如果您的应用程序需要大量客户端,例如公共聊天或游戏平台,则需要确保您的服务器基础设施能够处理数千甚至数百万个同时打开的连接。这通常需要使用支持 WebSocket 的负载均衡器,以确保连接均匀分布,避免服务器不堪重负。此外,研究水平扩展解决方案也是一个好主意,随着需求的增长,可以启动额外的服务器实例来分担负载。

另一个关键因素是处理网络中断。WebSocket 连接依赖于持续的 TCP 连接,而网络问题、服务器重启或其他因素都可能导致连接中断。在客户端实现重新连接逻辑对于维持稳定的用户体验至关重要。例如,如果连接意外断开,客户端应在短暂延迟后尝试重新连接。此外,使用退避策略(逐渐增加重新连接尝试之间的延迟时间)也是一种很好的做法,可以防止在问题持续存在时服务器不堪重负。

使用 WebSocket 时,安全性也至关重要。确保您的 WebSocket 连接通过安全通道 (wss://) 运行,而不是未加密的通道 (ws://)。通过 TLS 运行可以加密数据,防止窃听和中间人攻击。此外,在建立 WebSocket 连接之前,应考虑身份验证和授权。通常,您可以使用初始 HTTP 握手来验证客户端的凭据,但一旦 WebSocket 连接打开,您还需要一种机制来确保只有授权用户才能继续使用它,例如实现基于令牌的检查。

错误处理和优雅关闭是维护 WebSocket 通信稳定性的关键。WebSocket 连接可能需要由于协议错误、服务器维护或空闲超时而关闭。您的应用程序应该优雅地处理这些关闭操作,并在必要时通知用户,并确保未发送的数据得到妥善管理。使用 WebSocket 控制帧(例如 ping 和 pong)也有助于保持连接活动,并确定何时应因不活动而关闭连接。

最后,请密切关注性能和内存使用情况。由于 WebSocket 连接始终保持打开状态,每个连接都会消耗服务器资源,例如内存和 CPU 时间。如果连接管理不当,可能会导致资源耗尽,尤其对于高流量应用程序而言。请务必实施策略来关闭空闲连接,并使用高效的数据序列化来最大程度地减少发送消息的大小。监控工具有助于发现 WebSocket 实现中的内存泄漏或性能瓶颈。

简而言之,虽然 WebSocket 提供了一种强大的方式来实现应用程序中的实时交互功能,但它们也带来了一些必须谨慎处理的复杂性。解决这些考虑因素(从可扩展性和错误处理到安全性和资源管理)将有助于您创建更可靠、更可扩展的解决方案。接下来,我们将探索如何使用 .NET 和 C# 在服务器和客户端实现中实现这些原则。

C# 中的 WebSocket 简介

在了解了 WebSocket 的基础知识、协议和机制之后,我们将深入探讨如何使用 C# 实现 WebSocket,将目前为止所讲的所有内容付诸实践。.NET 提供了一个强大的框架来利用 WebSocket,使得设置服务器端和客户端组件进行实时通信变得相对简单。通过使用 C#,您可以高效地处理连接生命周期、传输数据并响应事件,同时还能利用 .NET 丰富的功能简化流程。

在服务器端,ASP.NET Core 提供了用于处理 WebSocket 连接的集成中间件。这意味着您可以轻松地将传入的 HTTP 请求升级到 WebSocket 连接,管理持久通道,并开始在常用的 C# 代码结构中交换消息。内置支持确保您能够控制消息流、处理异常和管理连接状态,而无需重复工作。这对于聊天服务或实时数据馈送等服务器端处理必须具有响应能力和可扩展性的场景尤为有用。

该System.Net.WebSockets命名空间提供了发起连接以及发送或接收客户端 WebSocket 通信消息所需的所有工具。无论您是构建桌面应用程序、移动客户端,还是控制台工具,使用 C# 和此命名空间都能为您提供与 WebSocket 服务器交互的可靠方式。客户端库负责处理协议的复杂性,让您专注于功能构建,而无需担心底层通信细节。

接下来,我们将探索在 ASP.NET Core 中建立 WebSocket 服务器、在 C# 中创建客户端连接以及处理它们之间的消息流的示例。这些示例将展示如何构建可扩展且高效的网络应用程序,充分利用 WebSocket 的功能,并利用 .NET 实现低延迟、实时的交互。

在 C# 中设置 WebSocket

要开始在 C# 中使用 WebSocket,您必须设置一个 .NET 解决方案来处理客户端和服务器端的交互。在本节中,我们将设置一个可以接受 WebSocket 连接的 ASP.NET Core 8 服务器,以及一个主要的 C# 客户端来测试它。这样,您将拥有一个构建自己的实时应用程序的良好起点。

让我们从在 ASP.NET Core 中设置 WebSocket 服务器开始。首先在 Visual Studio 中创建一个新的 ASP.NET Core Web API 项目。您需要添加中间件来处理 WebSocket 连接。打开Program.cs并修改它以添加对 WebSocket 的支持,如下例所示:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Enable WebSocket support

app.UseWebSockets();

app.Use(async (context, next) =>

{

if (context.Request.Path == "/ws")

{

if (context.WebSockets.IsWebSocketRequest)

{

var webSocket = await context.WebSockets.AcceptWebSocketAsync();

await EchoMessages(webSocket);

}

else

{

context.Response.StatusCode = 400;

}

}

else

{

await next();

}

});

app.Run();

// A simple echo function to handle messages

async Task EchoMessages(WebSocket webSocket)

{

var buffer = new byte[1024 * 4];

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

while (!result.CloseStatus.HasValue)

{

await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

}

await webSocket.CloseAsync(result.CloseStatus.Value, result.CloseStatusDescription, CancellationToken.None);

}

在此示例中,我们使用 添加 WebSocket 支持app.UseWebSockets()。然后,我们检查传入请求是否以/ws端点(WebSocket 连接的关键部分)为目标,并且确实是 WebSocket 请求。如果是,我们将接受 WebSocket 连接并使用EchoMessages函数进行处理,该函数只会回显收到的任何消息------这是了解服务器如何处理传入和传出消息的绝佳起点。

接下来,创建一个主客户端来连接到这个 WebSocket 服务器非常简单。你可以在控制台应用程序中使用命名空间ClientWebSocket中提供的类来执行此操作System.Net.WebSockets。这是一个简单的客户端示例:

using System.Net.WebSockets;

using System.Text;

using var client = new ClientWebSocket();

await client.ConnectAsync(new Uri("ws://localhost:5000/ws"), CancellationToken.None);

Console.WriteLine("Connected to the server");

var sendBuffer = Encoding.UTF8.GetBytes("Hello from client");

await client.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None);

Console.WriteLine("Message sent to the server");

var receiveBuffer = new byte[1024];

var result = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);

Console.WriteLine($"Message received from the server: {Encoding.UTF8.GetString(receiveBuffer, 0, result.Count)}");

await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);

Console.WriteLine("Connection closed");

在此代码中,ClientWebSocket连接到服务器ws://localhost:5000/ws。客户端向服务器发送一条消息("来自客户端的 Hello"),然后等待回显。您可以在服务器运行时运行此客户端来查看消息交换。这是一个简单但强大的示例,展示了使用 C# 在 .NET 中轻松设置 WebSocket 连接的两端是多么简单。

服务器和客户端都已安装完毕后,您可以尝试更高级的用例。在后续步骤中,您可以考虑如何增强此基本设置以处理更复杂的交互,例如有效地向多个客户端广播消息或管理连接生命周期。在接下来的部分中,我们将探索构建基于 WebSocket 的健壮应用程序的更高级模式和最佳实践。

实现 WebSocket 服务器

使用 ASP.NET Core 在 C# 中构建 WebSocket 服务器是为应用程序添加实时功能的绝佳方法。我们已经设置了一个用于回显消息的普通 WebSocket 服务器,现在我们将在此基础上进行扩展,创建一个强大的服务器,它可以处理多个客户端并有效地管理传入消息,让您对其功能充满信心。

要实现一个能够管理多个连接的 WebSocket 服务器,我们需要一种机制来跟踪每个已连接的客户端及其 WebSocket 实例。WebSocket 实例是服务器和客户端之间的唯一连接,允许双向通信。以下代码展示了如何扩展现有的服务器设置以管理多个客户端:

var builder = WebApplication.CreateBuilder(args);

var app = builder.Build();

// Enable WebSocket support

app.UseWebSockets();

// Dictionary to track connected clients

var clients = new Dictionary<string, WebSocket>();

app.Use(async (context, next) =>

{

if (context.Request.Path == "/ws")

{

if (context.WebSockets.IsWebSocketRequest)

{

var clientId = Guid.NewGuid().ToString();

var webSocket = await context.WebSockets.AcceptWebSocketAsync();

clients.Add(clientId, webSocket);

Console.WriteLine($"Client connected: {clientId}");

await HandleClientCommunication(clientId, webSocket);

}

else

{

context.Response.StatusCode = 400;

}

}

else

{

await next();

}

});

app.Run();

async Task HandleClientCommunication(string clientId, WebSocket webSocket)

{

var buffer = new byte[1024 * 4];

try

{

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

while (!result.CloseStatus.HasValue)

{

string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);

Console.WriteLine($"Message from {clientId}: {receivedMessage}");

// Echo the message back to all connected clients

foreach (var client in clients.Values)

{

if (client.State == WebSocketState.Open)

{

await client.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

}

}

result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

}

}

catch (Exception ex)

{

Console.WriteLine($"Error with client {clientId}: {ex.Message}");

}

finally

{

clients.Remove(clientId);

await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed", CancellationToken.None);

Console.WriteLine($"Client disconnected: {clientId}");

}

}

在此实现中,我们使用来跟踪每个连接的客户端,方法是为每个新的 WebSocket 连接Dictionary生成一个唯一的 ID ( )。该方法负责监听来自特定客户端的消息并进行相应的处理。从一个客户端收到的每条消息都会广播给所有连接的客户端,从而创建一个基本的聊天服务器。clientIdHandleClientCommunication

请注意,我们处理异常是为了增强服务器的健壮性。WebSocket 连接可能由于各种原因中断------客户端可能关闭浏览器、遇到网络问题或遇到应用程序级错误。我们通过使用 try-catch 块并确保在断开连接时将每个客户端从列表中移除,来维护干净且容错的服务器状态。

该服务器的一个关键组件是广播消息。每次收到消息时,都会将其回显给所有连接的客户端,以便每个人都能实时查看该消息。内部循环HandleClientCommunication会检查每个 WebSocket 的状态,以确保在尝试发送消息之前连接仍然处于打开状态。

通过此设置,您已拥有一个基础的 WebSocket 服务器,它可以管理多个客户端并实现它们之间的实时通信。这可以作为更复杂应用程序的基础,例如协作工具、多人游戏或实时流媒体更新。在接下来的部分中,我们将深入探讨如何处理特定事件、管理客户端组以及如何在 WebSocket 服务器扩展时优化性能。

实现 WebSocket 客户端

现在我们已经建立了一个能够管理多个 WebSocket 连接的服务器,接下来了解如何用 C# 实现 WebSocket 客户端至关重要。客户端是一个关键组件,它允许我们连接服务器并与之交互,发送和接收消息。我们将使用命名空间ClientWebSocket中的类System.Net.WebSockets来创建一个简单的客户端,用于连接、发送消息和监听响应。

首先,让我们设置一个基本的控制台应用程序作为客户端。该过程非常简单:实例化ClientWebSocket,连接到 WebSocket 服务器,然后发送一条简单消息。以下是一个基本示例:

using System.Net.WebSockets;

using System.Text;

using var client = new ClientWebSocket();

try

{

// Connect to the WebSocket server

await client.ConnectAsync(new Uri("ws://localhost:5000/ws"), CancellationToken.None);

Console.WriteLine("Connected to the WebSocket server.");

// Sending a message to the server

var sendBuffer = Encoding.UTF8.GetBytes("Hello, Server! This is the client.");

await client.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None);

Console.WriteLine("Message sent to the server.");

// Listen for messages from the server

var receiveBuffer = new byte[1024];

while (client.State == WebSocketState.Open)

{

var result = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);

if (result.MessageType == WebSocketMessageType.Close)

{

Console.WriteLine("Server closed the connection.");

await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);

}

else

{

string receivedMessage = Encoding.UTF8.GetString(receiveBuffer, 0, result.Count);

Console.WriteLine($"Message received from server: {receivedMessage}");

}

}

}

catch (Exception ex)

{

Console.WriteLine($"Exception: {ex.Message}");

}

在此示例中,客户端连接到在端点 5000 端口上本地运行的 WebSocket 服务器/ws。然后,客户端发送一条问候消息,服务器可能会将该消息广播给所有连接的客户端(包括我们自己的客户端)。之后,客户端进入循环监听来自服务器的消息。如果服务器决定关闭连接,客户端会检测到该Close消息并正常关闭。

该ClientWebSocketAPI 简单易用,允许您处理所有典型的 WebSocket 活动,例如发送、接收和关闭连接。我们代码中的循环旨在保持客户端连接,直到服务器结束会话。这种行为对于像聊天室这样需要客户端持续参与的场景非常有用。

您可以扩展客户端,使其根据用户输入(而不是硬编码的文本)发送消息。例如,您可以将发送逻辑包装在一个从控制台获取用户输入的方法中,让客户端表现得像一个聊天参与者:

while (client.State == WebSocketState.Open)

{

Console.Write("Enter message: ");

string message = Console.ReadLine();

if (string.IsNullOrEmpty(message)) break;

var buffer = Encoding.UTF8.GetBytes(message);

await client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);

}

这一简单的新增功能使客户端能够向服务器发送多条消息,从而增强了交互的动态性。此功能开启了运行客户端应用程序多个实例的可能性,让您能够模拟用户与服务器的各种交互。随着您不断进行实验,请考虑扩展服务器和客户端的功能。这可能涉及实现特定命令、管理不同类型的消息,甚至引入身份验证层,以构建更健壮、更易于生产的应用程序。

调试和测试

调试和测试 WebSocket 连接与传统的 HTTP 请求不同,因为 WebSocket 是持久且双向的。了解如何在 C# 中排除故障并验证 WebSocket 实现,可确保流畅高效的用户体验。在本节中,我们将探讨如何使用内置 .NET 工具、日志记录和一些实用技巧来调试和测试服务器端和客户端。

首先,使用详细的日志记录是调试 WebSocket 最有效的方法之一。通过记录关键事件(例如连接请求、收到的消息和断开连接状态),您可以追踪连接生命周期中发生的情况。例如,将日志记录添加到服务器的消息处理逻辑中,以了解客户端的连接方式、发送的数据以及断开连接的时间。以下是如何HandleClientCommunication从我们的服务器实现向方法添加日志记录的方法:

async Task HandleClientCommunication(string clientId, WebSocket webSocket)

{

var buffer = new byte[1024 * 4];

try

{

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

while (!result.CloseStatus.HasValue)

{

string receivedMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);

Console.WriteLine($"[Server] Received from {clientId}: {receivedMessage}");

// Echo the message back to all clients

foreach (var client in clients.Values)

{

if (client.State == WebSocketState.Open)

{

await client.SendAsync(new ArraySegment<byte>(buffer, 0, result.Count), result.MessageType, result.EndOfMessage, CancellationToken.None);

}

}

result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

}

}

catch (Exception ex)

{

Console.WriteLine($"[Server] Error with client {clientId}: {ex.Message}");

}

finally

{

clients.Remove(clientId);

await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed", CancellationToken.None);

Console.WriteLine($"[Server] Client disconnected: {clientId}");

}

}

有了此日志记录,您可以轻松跟踪数据流并观察事件序列,例如连接状态和消息交换。这使得更容易发现诸如消息未送达或连接意外关闭之类的问题。

在客户端,类似的日志记录可以帮助您了解连接是否成功建立,或者消息交换过程中是否发生错误。在ConnectAsync、SendAsync和ReceiveAsync调用周围添加详细的控制台输出有助于查明可能出错的位置。例如:

try

{

await client.ConnectAsync(new Uri("ws://localhost:5000/ws"), CancellationToken.None);

Console.WriteLine("[Client] Connected to the WebSocket server.");

var sendBuffer = Encoding.UTF8.GetBytes("Hello, Server!");

await client.SendAsync(new ArraySegment<byte>(sendBuffer), WebSocketMessageType.Text, true, CancellationToken.None);

Console.WriteLine("[Client] Message sent to the server.");

var receiveBuffer = new byte[1024];

var result = await client.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None);

Console.WriteLine($"[Client] Message received from server: {Encoding.UTF8.GetString(receiveBuffer, 0, result.Count)}");

}

catch (Exception ex)

{

Console.WriteLine($"[Client] Exception: {ex.Message}");

}

除了简单的日志记录之外,WebSocket 测试工具wscat(例如命令行 WebSocket 客户端)或 Postman)是开发人员进行初始测试的最佳助手。这些工具简化了流程,使您能够快速连接到 WebSocket 服务器、发送测试消息并查看响应,而无需编写客户端。这种高效性有助于在集成 C# 客户端代码之前确认服务器是否按预期运行。

WebSocket 测试不仅应涵盖基础知识,还应包含模拟实际使用场景的测试。例如,测试多个客户端同时连接的情况可以让您清楚地了解服务器如何处理并发连接。通过运行多个客户端代码实例,每个实例以不同的时间间隔发送和接收消息,并使用日志记录来监控服务器对这些交互的管理,您可以做好充分的准备。高消息吞吐量的压力测试可以发现服务器实现中的性能瓶颈或限制,确保您能够应对任何情况。

最后,不要低估单元测试和集成测试在验证 WebSocket 逻辑关键部分方面的作用。虽然单元测试可能无法直接测试实时 WebSocket 连接,但它们允许您模拟特定组件并测试消息处理、序列化或内部服务器逻辑。另一方面,集成测试对于 WebSocket 来说更实用,因为它们可以建立连接、发送消息并验证响应。例如,WebApplicationFactory在 ASP.NET Core 中使用 可以帮助您设置测试服务器,然后ClientWebSocket连接到该服务器,确保一切端到端正常工作。这些测试可以增强您对 WebSocket 实现的信心。

通过结合详细的日志记录、外部测试工具、压力测试以及单元/集成测试,您将能够很好地应对在 C# 中调试和测试 WebSocket 的独特挑战。这些实践将确保您的 WebSocket 服务器和客户端能够可靠地通信、有效地扩展,并提供用户期望的实时性能。

高级 WebSocket 功能

掌握了使用 C# 设置 WebSocket 服务器和客户端的基础知识后,就可以探索一些更高级的功能,将您的实现提升到更高的水平。这些功能有助于提升性能、更好地管理资源,并提供更复杂的行为,例如处理不同类型的消息、管理客户端组以及使用压缩来提高数据传输效率。有效管理客户端组的能力将使您对系统拥有更强的掌控力。

WebSocket 的一项强大功能是能够处理不同类型的消息------文本、二进制和控制帧。例如,您可能希望在客户端之间发送二进制数据(例如图像或序列化对象)。处理这些数据需要识别消息类型并采取相应的措施。以下示例展示了如何扩展现有HandleClientCommunication方法来管理不同类型的消息:

async Task HandleClientCommunication(string clientId, WebSocket webSocket)

{

var buffer = new byte[1024 * 4];

WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

while (!result.CloseStatus.HasValue)

{

switch (result.MessageType)

{

case WebSocketMessageType.Text:

string textMessage = Encoding.UTF8.GetString(buffer, 0, result.Count);

Console.WriteLine($"[Server] Text message from {clientId}: {textMessage}");

break;

case WebSocketMessageType.Binary:

Console.WriteLine($"[Server] Binary message received from {clientId}, length: {result.Count} bytes.");

// Handle binary data here

break;

default:

Console.WriteLine($"[Server] Control message received from {clientId}.");

break;

}

result = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

}

}

这段代码以不同的方式处理文本和二进制消息,使您的服务器更加灵活。您可以进一步扩展它以处理控制帧,这有助于维护 WebSocket 连接,例如响应来自客户端的 ping 请求以保持连接有效。

另一个高级功能是管理客户端组。例如,您可能想为聊天应用程序创建"房间",让消息仅广播给特定的一组用户。为此,您可以维护一个包含 WebSocket 连接列表的客户端组字典。以下是基本实现:

var clientGroups = new Dictionary<string, List<WebSocket>>();

async Task AddClientToGroup(string groupId, WebSocket client)

{

if (!clientGroups.ContainsKey(groupId))

{

clientGroups[groupId] = new List<WebSocket>();

}

clientGroups[groupId].Add(client);

Console.WriteLine($"Client added to group {groupId}.");

}

async Task SendMessageToGroup(string groupId, string message)

{

if (clientGroups.ContainsKey(groupId))

{

var buffer = Encoding.UTF8.GetBytes(message);

foreach (var client in clientGroups[groupId])

{

if (client.State == WebSocketState.Open)

{

await client.SendAsync(new ArraySegment<byte>(buffer), WebSocketMessageType.Text, true, CancellationToken.None);

}

}

}

}

通过此设置,您可以将客户端添加到不同的组并向特定组广播消息,从而使您的服务器能够处理更复杂的通信模式,如聊天室或游戏大厅。

WebSocket 压缩是另一项高级功能,可以提高数据传输效率,尤其是在处理大量负载时。ASP.NET WebSocketDeflateOptionsCore 允许您启用每条消息的压缩,这是减少客户端和服务器之间交换的消息大小的关键步骤。这在带宽有限的环境中尤其有用。您可以在启用 WebSocket 时进行Program.cs如下配置:

app.UseWebSockets(new WebSocketOptions

{

KeepAliveInterval = TimeSpan.FromSeconds(120),

DangerousEnableCompression = true

});

虽然启用压缩可以节省带宽,但应考虑压缩和解压消息所需的处理开销。请务必测试您的具体用例,以确定压缩是否能带来净效益。

最后,请考虑智能地处理空闲连接以节省资源。WebSocket 连接本质上是持久性的,保持未使用的连接处于打开状态可能会导致资源耗尽。您可以使用定期的 ping-pong 消息来检查客户端是否仍然处于活动状态,如果不是,则关闭连接以释放服务器资源。简单的空闲超时机制可确保您的 WebSocket 服务器保持可扩展性和响应速度。

这些高级功能让您能够更好地控制 WebSocket 服务器和客户端的交互方式。它们允许您创建更复杂的实时应用程序,这些应用程序高效、可扩展,并且能够处理各种通信场景。随着这些功能的融入,您的 WebSocket 实现将变得更加强大,能够满足现代交互式应用程序的需求。

如果您喜欢此文章,请收藏、点赞、评论,谢谢,祝您快乐每一天。