WebSocket на C#: real-time приложения от А до Я

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

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

Начать курс

Введение: Почему WebSocket — стандарт для real-time приложений?

Современные веб-приложения требуют мгновенной реакции на действия пользователя. Будь то онлайн-чат, биржевой терминал, многопользовательская игра или система мониторинга — задержки в передаче данных неприемлемы. Традиционный протокол HTTP, работающий по принципу "запрос-ответ", неэффективен для таких сценариев: клиенту приходится постоянно опрашивать сервер (polling), создавая избыточную нагрузку.

На помощь приходит WebSocket — протокол, обеспечивающий постоянное двустороннее соединение между клиентом и сервером. В этой статье мы разберем, как создавать real-time приложения на C# (.NET) с использованием WebSocket, от простого эхо-сервера до полноценного чата. Вы узнаете, как работает протокол, какие инструменты предоставляет платформа .NET, и как избежать типичных ошибок.

Основы WebSocket в .NET: Серверная часть

Платформа .NET предлагает встроенную поддержку WebSocket через пространство имен System.Net.WebSockets. Рассмотрим создание сервера на базе ASP.NET Core.

1. Настройка Middleware для WebSocket

Для начала необходимо добавить поддержку WebSocket в конвейер обработки запросов. В файле 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 Echo(webSocket);
    }
    else
    {
        context.Response.StatusCode = 400;
    }
});

app.Run("http://localhost:5000");

async Task Echo(WebSocket webSocket)
{
    var buffer = new byte[1024 * 4];
    var receiveResult = await webSocket.ReceiveAsync(
        new ArraySegment<byte>(buffer), CancellationToken.None);

    while (!receiveResult.CloseStatus.HasValue)
    {
        // Отправляем полученные данные обратно клиенту (эхо)
        await webSocket.SendAsync(
            new ArraySegment<byte>(buffer, 0, receiveResult.Count),
            receiveResult.MessageType,
            receiveResult.EndOfMessage,
            CancellationToken.None);

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

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

Этот код создает эхо-сервер: все, что отправляет клиент, возвращается обратно. Обратите внимание на использование async/await — работа с WebSocket всегда асинхронна.

2. Управление несколькими подключениями

В реальном приложении нужно управлять множеством соединений. Создадим простой менеджер подключений:

public class WebSocketConnectionManager
{
    private readonly ConcurrentDictionary<string, WebSocket> _connections = new();

    public string AddConnection(WebSocket socket)
    {
        var id = Guid.NewGuid().ToString();
        _connections.TryAdd(id, socket);
        return id;
    }

    public async Task BroadcastMessageAsync(string message)
    {
        var tasks = _connections.Values.Select(async socket =>
        {
            if (socket.State == WebSocketState.Open)
            {
                var bytes = Encoding.UTF8.GetBytes(message);
                await socket.SendAsync(
                    new ArraySegment<byte>(bytes),
                    WebSocketMessageType.Text,
                    true,
                    CancellationToken.None);
            }
        });
        await Task.WhenAll(tasks);
    }
}

Этот класс позволяет хранить все активные соединения и рассылать сообщения всем подключенным клиентам.

Создание клиента WebSocket на C#

Хотя чаще всего клиентом выступает браузер (JavaScript), иногда необходимо реализовать клиент на C# — например, для десктопного приложения или серверного агрегатора данных.

Пример клиента на .NET

using System.Net.WebSockets;
using System.Text;

class WebSocketClient
{
    public static async Task Main()
    {
        using var ws = new ClientWebSocket();
        await ws.ConnectAsync(new Uri("ws://localhost:5000/ws"), CancellationToken.None);

        Console.WriteLine("Подключено к серверу. Введите сообщение:");

        var sendTask = Task.Run(async () =>
        {
            while (true)
            {
                var message = Console.ReadLine();
                if (string.IsNullOrEmpty(message)) break;

                var bytes = Encoding.UTF8.GetBytes(message);
                await ws.SendAsync(
                    new ArraySegment<byte>(bytes),
                    WebSocketMessageType.Text,
                    true,
                    CancellationToken.None);
            }
        });

        var receiveTask = Task.Run(async () =>
        {
            var buffer = new byte[1024 * 4];
            while (ws.State == WebSocketState.Open)
            {
                var result = await ws.ReceiveAsync(
                    new ArraySegment<byte>(buffer), CancellationToken.None);
                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await ws.CloseAsync(
                        WebSocketCloseStatus.NormalClosure, "Closing", CancellationToken.None);
                    break;
                }
                var text = Encoding.UTF8.GetString(buffer, 0, result.Count);
                Console.WriteLine($"Сервер: {text}");
            }
        });

        await Task.WhenAll(sendTask, receiveTask);
    }
}

Клиент одновременно отправляет сообщения с консоли и получает ответы от сервера. Это реализует полноценный дуплексный обмен данными.

Real-time чат: Собираем всё вместе

Объединим полученные знания и создадим простой чат. Сервер будет хранить имена пользователей и транслировать сообщения всем подключенным клиентам.

Серверная часть чата

public class ChatWebSocketHandler
{
    private static readonly ConcurrentDictionary<string, (WebSocket Socket, string Name)> _users = new();

    public static async Task HandleConnection(WebSocket socket)
    {
        var userId = Guid.NewGuid().ToString();
        _users.TryAdd(userId, (socket, "User_" + userId[..5]));

        // Отправляем приветственное сообщение
        await SendMessage(socket, new { type = "system", message = "Добро пожаловать в чат!" });

        var buffer = new byte[1024 * 4];
        var result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

        while (!result.CloseStatus.HasValue)
        {
            var json = Encoding.UTF8.GetString(buffer, 0, result.Count);
            var data = JsonSerializer.Deserialize<ChatMessage>(json);

            if (data?.type == "message")
            {
                var broadcast = new
                {
                    type = "message",
                    user = _users[userId].Name,
                    text = data.text,
                    time = DateTime.Now.ToShortTimeString()
                };
                await Broadcast(JsonSerializer.Serialize(broadcast));
            }

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

        // Удаляем пользователя                

Блоги

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