Введение в WebSocket на C# для real-time приложений
Современные веб-приложения всё чаще требуют мгновенной передачи данных между клиентом и сервером. Традиционный HTTP-протокол, работающий по схеме «запрос-ответ», неэффективен для задач, где важна низкая задержка и постоянное обновление информации: онлайн-чаты, биржевые котировки, многопользовательские игры, системы мониторинга. На помощь приходит WebSocket — протокол, обеспечивающий постоянное двустороннее соединение.
В экосистеме C# (.NET) реализация WebSocket доступна как через встроенные классы пространства имён System.Net.WebSockets, так и через высокоуровневую библиотеку SignalR. В этой статье мы разберём оба подхода, напишем рабочие примеры и обсудим архитектурные особенности real-time приложений. Материал будет полезен как начинающим, так и опытным .NET-разработчикам.
Основы WebSocket: как это работает в C#
WebSocket — это протокол, который после начального HTTP-рукопожатия (upgrade request) переводит соединение в постоянный двунаправленный режим. В C# работа с WebSocket строится на асинхронном программировании с использованием ключевых слов async и await.
Создание простого WebSocket сервера на C#
Рассмотрим базовый пример сервера, который принимает WebSocket-соединения и эхо-отвечает клиенту. Для этого используем встроенный класс HttpListener или Kestrel (ASP.NET Core). Ниже приведён пример на основе ASP.NET Core.
// Startup.cs или Program.cs (минимальный API)
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWebSockets();
app.Map("/ws", async (HttpContext context) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await Echo(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
});
async Task Echo(WebSocket webSocket)
{
var buffer = new byte[1024 * 4];
while (webSocket.State == WebSocketState.Open)
{
var result = await webSocket.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Close)
{
await webSocket.CloseAsync(
WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
}
else
{
// Отправляем данные обратно (эхо)
await webSocket.SendAsync(
new ArraySegment<byte>(buffer, 0, result.Count),
result.MessageType,
result.EndOfMessage,
CancellationToken.None);
}
}
}
app.Run();
Код демонстрирует базовую механику: AcceptWebSocketAsync переводит HTTP-запрос в WebSocket, а методы ReceiveAsync и SendAsync обеспечивают асинхронный обмен данными. Важно обрабатывать состояние соединения и корректно закрывать его.
WebSocket клиент на C#
Клиентская часть также проста. Используем класс ClientWebSocket:
using System.Net.WebSockets;
async Task ConnectAndSend()
{
using var client = new ClientWebSocket();
await client.ConnectAsync(new Uri("ws://localhost:5000/ws"), CancellationToken.None);
var message = "Привет, WebSocket!";
var bytes = Encoding.UTF8.GetBytes(message);
await client.SendAsync(new ArraySegment<byte>(bytes),
WebSocketMessageType.Text, true, CancellationToken.None);
var buffer = new byte[1024];
var result = await client.ReceiveAsync(
new ArraySegment<byte>(buffer), CancellationToken.None);
var response = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Ответ сервера: {response}");
await client.CloseAsync(WebSocketCloseStatus.NormalClosure, "Done", CancellationToken.None);
}
Этот код подключается к серверу, отправляет текстовое сообщение и получает ответ. Обратите внимание на кодировку UTF-8 — WebSocket работает с бинарными данными, поэтому текст нужно преобразовывать.
SignalR: высокоуровневый WebSocket в .NET
Хотя нативные WebSocket дают полный контроль, для сложных приложений удобнее использовать SignalR — библиотеку от Microsoft, которая абстрагирует транспорт (WebSocket, Server-Sent Events, Long Polling) и предоставляет модель хабов (Hubs).
Архитектура SignalR
SignalR автоматически выбирает наилучший транспорт. Если WebSocket недоступен, он переключается на альтернативы. Основные компоненты:
- Hub — класс на сервере, который обрабатывает вызовы от клиентов.
- Clients — объект для отправки сообщений одному, группе или всем подключённым клиентам.
- HubConnection — клиентский объект для подключения и вызова методов сервера.
Пример чата на SignalR (сервер)
// ChatHub.cs
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
{
// Отправляем сообщение всем подключённым клиентам
await Clients.All.SendAsync("ReceiveMessage", user, message);
}
public override async Task OnConnectedAsync()
{
await Clients.All.SendAsync("UserConnected", Context.ConnectionId);
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception? exception)
{
await Clients.All.SendAsync("UserDisconnected", Context.ConnectionId);
await base.OnDisconnectedAsync(exception);
}
}
// Program.cs
builder.Services.AddSignalR();
app.MapHub<ChatHub>("/chat");
Клиент SignalR на C# (консольное приложение)
using Microsoft.AspNetCore.SignalR.Client;
var connection = new HubConnectionBuilder()
.WithUrl("https://localhost:5001/chat")
.Build();
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
Console.WriteLine($"{user}: {message}");
});
await connection.StartAsync();
await connection.InvokeAsync("SendMessage", "C# Client", "Привет из консоли!");
Console.ReadLine();
SignalR значительно упрощает код: вам не нужно вручную управлять буферами, парсить сообщения и обрабатывать переподключение. Библиотека также поддерживает группы — например, для отправки сообщений только определённым пользователям.
Лучшие практики и производительность
При разработке real-time приложений на C# важно учитывать несколько аспектов: