WebSocket на C#: создание real-time приложений в .NET
Современные веб-приложения всё чаще требуют мгновенной передачи данных между клиентом и сервером. Традиционный HTTP-протокол, работающий по схеме «запрос-ответ», не всегда эффективен для задач, где важна частота и скорость обновлений — чаты, онлайн-игры, биржевые котировки, системы мониторинга. Здесь на помощь приходит протокол WebSocket, который устанавливает постоянное двунаправленное соединение. В этой статье мы подробно разберём, как реализовать WebSocket на C# (.NET), создадим полноценный сервер и клиент, а также рассмотрим лучшие практики.
Что такое WebSocket и зачем он нужен в .NET?
WebSocket — это протокол, работающий поверх TCP, который обеспечивает полнодуплексную связь. После установки соединения через HTTP-рукопожатие (handshake), данные могут передаваться в обе стороны без дополнительных накладных расходов. В отличие от Long Polling или Server-Sent Events, WebSocket даёт минимальную задержку и низкий трафик.
В экосистеме .NET есть два основных подхода для работы с real-time:
- Нативный WebSocket (System.Net.WebSockets) — низкоуровневый API, доступный в ASP.NET Core. Даёт полный контроль над соединением, подходит для кастомных протоколов.
- SignalR — высокоуровневая библиотека от Microsoft, которая абстрагирует WebSocket и автоматически выбирает транспорт (WebSocket, SSE, Long Polling). Идеальна для типовых задач (чаты, уведомления).
Выбор зависит от задачи: если вам нужен максимальный контроль и производительность — используйте нативный WebSocket; если скорость разработки и простота — SignalR.
Создание WebSocket сервера на C# (ASP.NET Core)
Рассмотрим пошаговую реализацию сервера. Для начала создадим проект ASP.NET Core (пустой шаблон или Web API).
Шаг 1: Настройка Middleware
Добавьте поддержку WebSocket в методе Configure файла Program.cs:
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
app.UseWebSockets(); // Включаем поддержку WebSocket
app.Map("/ws", async (HttpContext context) =>
{
if (context.WebSockets.IsWebSocketRequest)
{
using var webSocket = await context.WebSockets.AcceptWebSocketAsync();
await HandleWebSocketConnection(webSocket);
}
else
{
context.Response.StatusCode = 400;
}
});
app.Run();
async Task HandleWebSocketConnection(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 if (result.MessageType == WebSocketMessageType.Text)
{
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
Console.WriteLine($"Получено: {message}");
// Эхо-ответ
var response = Encoding.UTF8.GetBytes($"Эхо: {message}");
await webSocket.SendAsync(
new ArraySegment<byte>(response),
WebSocketMessageType.Text,
true,
CancellationToken.None);
}
}
}
Этот код создаёт эхо-сервер: он принимает текстовые сообщения и отправляет их обратно с префиксом "Эхо:". Обратите внимание на управление состоянием соединения и обработку закрытия.
Шаг 2: Управление множественными подключениями
В реальном приложении нужно хранить список активных соединений. Используйте ConcurrentDictionary или ConcurrentBag:
public class WebSocketConnectionManager
{
private readonly ConcurrentDictionary<string, WebSocket> _sockets = new();
public string AddSocket(WebSocket socket)
{
var id = Guid.NewGuid().ToString();
_sockets.TryAdd(id, socket);
return id;
}
public async Task BroadcastMessageAsync(string message)
{
var tasks = _sockets.Values
.Where(s => s.State == WebSocketState.Open)
.Select(async socket =>
{
var data = Encoding.UTF8.GetBytes(message);
await socket.SendAsync(
new ArraySegment<byte>(data),
WebSocketMessageType.Text,
true,
CancellationToken.None);
});
await Task.WhenAll(tasks);
}
public void RemoveSocket(string id)
{
_sockets.TryRemove(id, out _);
}
}
WebSocket клиент на C# (.NET)
Клиентская часть может быть как консольным приложением, так и частью веб-интерфейса (JavaScript). Рассмотрим создание C#-клиента для тестирования сервера.
using System.Net.WebSockets;
using System.Text;
class WebSocketClient
{
static async Task Main(string[] args)
{
using var client = new ClientWebSocket();
var uri = new Uri("ws://localhost:5000/ws");
await client.ConnectAsync(uri, CancellationToken.None);
Console.WriteLine("Подключено к серверу");
// Отправляем сообщение
var sendBytes = Encoding.UTF8.GetBytes("Привет, WebSocket!");
await client.SendAsync(
new ArraySegment<byte>(sendBytes),
WebSocketMessageType.Text,
true,
CancellationToken.None);
// Получаем ответ
var buffer = new byte[1024 * 4];
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,
"Client closing",
CancellationToken.None);
}
}
Для работы с клиентом в вебе используйте стандартный JavaScript WebSocket API. Взаимодействие с C#-сервером будет прозрачным.
Лучшие практики и оптимизация real-time приложений
При разработке production-решений учитывайте следующие аспекты:
- Управление жизненным циклом соединений: всегда обрабатывайте закрытие WebSocket, освобождайте ресурсы, удаляйте мёртвые соединения из менеджера.
- Безопасность: используйте WSS (WebSocket Secure) через HTTPS, проверяйте Origin заголовки, реализуйте аутентификацию (JWT токены в query string).
- Масштабирование: для распределённых систем используйте бэкплейны (Redis, Azure SignalR Service) для синхронизации между экземплярами приложения.
- Обработка ошибок: обора