WebSocket на C#: создание real-time приложений с .NET

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Введение в 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# важно учитывать несколько аспектов:

Блоги

Рекомендаций по книгам