WebSocket на Rust: создание real-time приложений

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

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

Начать курс

Введение в WebSocket на Rust

Real-time приложения стали неотъемлемой частью современного веба. Чат-системы, онлайн-игры, биржевые котировки и уведомления в реальном времени — всё это требует постоянного двустороннего обмена данными между клиентом и сервером. Протокол WebSocket предоставляет именно такую возможность, а Rust благодаря своей производительности и безопасности является идеальным языком для создания таких систем.

В этой статье мы подробно разберём, как создавать real-time приложения на Rust с использованием WebSocket. Вы узнаете о популярных крейтах, архитектурных подходах и получите готовые примеры кода для быстрого старта.

Основы WebSocket и выбор библиотек

WebSocket — это протокол, обеспечивающий полнодуплексную связь через одно TCP-соединение. В отличие от HTTP, где клиент инициирует запрос, а сервер отвечает, WebSocket позволяет обеим сторонам отправлять данные в любой момент.

Популярные крейты для WebSocket в Rust

  • tokio-tungstenite — асинхронный WebSocket-клиент и сервер на базе Tokio. Лёгкий и быстрый.
  • actix-web — мощный веб-фреймворк со встроенной поддержкой WebSocket.
  • tungstenite — синхронная версия, удобная для простых сценариев.
  • warp — ещё один асинхронный фреймворк с поддержкой WebSocket.

Для большинства проектов мы рекомендуем tokio-tungstenite или actix-web. Первый — для лёгких серверов и клиентов, второй — для полноценных веб-приложений.

Создание WebSocket-сервера на Rust

Рассмотрим создание простого echo-сервера, который принимает сообщения и отправляет их обратно клиенту. Используем tokio-tungstenite.

Настройка проекта

Создайте новый проект Cargo и добавьте зависимости в Cargo.toml:

[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.21"
futures-util = "0.3"

Базовый echo-сервер

use futures_util::{SinkExt, StreamExt};
use tokio::net::TcpListener;
use tokio_tungstenite::accept_async;

#[tokio::main]
async fn main() -> Result<(), Box> {
    let listener = TcpListener::bind("127.0.0.1:8080").await?;
    println!("Сервер запущен на ws://127.0.0.1:8080");

    while let Ok((stream, addr)) = listener.accept().await {
        println!("Новое подключение: {}", addr);
        tokio::spawn(handle_connection(stream));
    }

    Ok(())
}

async fn handle_connection(stream: tokio::net::TcpStream) {
    let ws_stream = accept_async(stream).await.unwrap();
    let (mut write, mut read) = ws_stream.split();

    while let Some(msg) = read.next().await {
        let msg = msg.unwrap();
        // Отправляем сообщение обратно
        write.send(msg).await.unwrap();
    }
}

Этот код создаёт TCP-сервер на порту 8080, принимает WebSocket-соединения и для каждого клиента запускает асинхронную задачу, которая эхо-повторяет все входящие сообщения.

Создание WebSocket-клиента на Rust

Теперь напишем клиент, который подключается к нашему серверу, отправляет сообщение и получает ответ.

use futures_util::StreamExt;
use tokio_tungstenite::connect_async;

#[tokio::main]
async fn main() -> Result<(), Box> {
    let url = "ws://127.0.0.1:8080";
    let (ws_stream, _) = connect_async(url).await?;

    println!("Подключено к серверу");

    let (mut write, mut read) = ws_stream.split();

    // Отправляем сообщение
    write.send("Привет, WebSocket!".into()).await?;

    // Получаем ответ
    if let Some(msg) = read.next().await {
        let msg = msg?;
        println!("Получено: {}", msg.to_text().unwrap());
    }

    Ok(())
}

Real-time чат с использованием actix-web

Теперь создадим полноценный многопользовательский чат с помощью actix-web. Это более сложный пример, демонстрирующий broadcast-рассылку сообщений всем подключённым клиентам.

Добавление зависимостей

[dependencies]
actix-web = "4"
actix-ws = "0.2"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"

Реализация чата

use actix_web::{web, App, HttpServer, HttpRequest, HttpResponse};
use actix_ws::Message;
use serde::{Deserialize, Serialize};
use tokio::sync::broadcast;

#[derive(Serialize, Deserialize, Clone)]
struct ChatMessage {
    user: String,
    text: String,
}

async fn chat_route(
    req: HttpRequest,
    stream: web::Payload,
    broadcaster: web::Data>,
) -> Result {
    let (response, mut session, mut msg_stream) = actix_ws::handle(&req, stream)?;

    let mut rx = broadcaster.subscribe();

    actix_web::rt::spawn(async move {
        loop {
            tokio::select! {
                Some(msg) = msg_stream.next() => {
                    match msg {
                        Ok(Message::Text(text)) => {
                            let chat_msg = ChatMessage {
                                user: "User".to_string(),
                                text: text.to_string(),
                            };
                            let _ = broadcaster.send(chat_msg);
                        }
                        Ok(Message::Ping(bytes)) => {
                            let _ = session.pong(&bytes).await;
                        }
                        _ => break,
                    }
                }
                Ok(msg) = rx.recv() => {
                    let json = serde_json::to_string(&msg).unwrap();
                    let _ = session.text(json).await;
                }
            }
        }
    });

    Ok(response)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    let (tx, _) = broadcast::channel::(100);

    HttpServer::new(move || {
        App::new()
            .app_data(web::Data::new(tx.clone()))
            .route("/chat", web::get().to(chat_route))
    })
    .bind("127.0.0.1:8081")?
    .run()
    .await
}

В этом примере мы используем broadcast::channel для рассылки сообщений всем подключённым клиентам. Каждое новое сообщение получает каждый подписчик канала.

Блоги

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