Глава 18: Обзор стандартной библиотеки

Раздел 8: Сетевые возможности

Содержание: 1. std::net: Поддержка TCP, UDP и IP Практические советы Пример: Эхо-сервер с выбором протокола Упражнение Заключение

В этом разделе мы познакомимся с модулем std::net — частью стандартной библиотеки Rust, которая предоставляет инструменты для работы с сетью: поддержку протоколов TCP, UDP и IP. Этот модуль позволяет создавать серверы, клиенты и взаимодействовать с сетью на низком уровне. Лекция подойдёт как новичкам, только начинающим изучать сетевые аспекты Rust, так и опытным разработчикам, желающим углубить свои знания. Мы разберём основные компоненты с примерами, нюансами и практическими советами.


1. std::net: Поддержка TCP, UDP и IP

Модуль std::net предоставляет низкоуровневый доступ к сетевым примитивам. Он не зависит от внешних библиотек и работает на всех платформах, поддерживаемых Rust. Основное внимание уделено трём ключевым областям: TCP (надёжная передача данных), UDP (быстрая передача без гарантий) и IP (адресация). Все операции в std::net возвращают std::io::Result, что требует обработки ошибок.

Основные типы:

1.1 TCP: Надёжная передача данных

TCP (Transmission Control Protocol) обеспечивает надёжную, упорядоченную передачу данных между двумя точками. В std::net это реализовано через TcpListener (сервер) и TcpStream (соединение).

Пример: Простой TCP-сервер и клиент

Сервер:

use std::net::TcpListener;
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("127.0.0.1:8080")?;
    println!("Сервер запущен на 127.0.0.1:8080");
    
    for stream in listener.incoming() {
        let mut stream = stream?;
        println!("Новое соединение: {:?}", stream.peer_addr()?);
        
        let mut buffer = [0; 1024];
        stream.read(&mut buffer)?;
        println!("Получено: {}", String::from_utf8_lossy(&buffer));
        
        stream.write_all(b"Hello from server!")?;
    }
    Ok(())
}
    

Клиент:

use std::net::TcpStream;
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    println!("Подключено к серверу");
    
    stream.write_all(b"Hello from client!")?;
    
    let mut buffer = [0; 1024];
    stream.read(&mut buffer)?;
    println!("Ответ сервера: {}", String::from_utf8_lossy(&buffer));
    Ok(())
}
    

Комментарии:

Заметка: Запустите сервер в одном терминале, а клиент — в другом, чтобы увидеть взаимодействие.

1.2 UDP: Быстрая передача без гарантий

UDP (User Datagram Protocol) отправляет данные без установления соединения, что быстрее, но не гарантирует доставку. Используется UdpSocket.

Пример: UDP-сервер и клиент

Сервер:

use std::net::UdpSocket;

fn main() -> std::io::Result<()> {
    let socket = UdpSocket::bind("127.0.0.1:8081")?;
    println!("UDP-сервер запущен на 127.0.0.1:8081");
    
    let mut buffer = [0; 1024];
    let (size, src) = socket.recv_from(&mut buffer)?;
    println!("Получено {} байт от {:?}: {}", 
        size, src, String::from_utf8_lossy(&buffer[..size]));
    
    socket.send_to(b"Echo from UDP server", src)?;
    Ok(())
}
    

Клиент:

use std::net::UdpSocket;

fn main() -> std::io::Result<()> {
    let socket = UdpSocket::bind("0.0.0.0:0")?; // Любой свободный порт
    socket.connect("127.0.0.1:8081")?;
    
    socket.send(b"Hello from UDP client")?;
    
    let mut buffer = [0; 1024];
    let (size, src) = socket.recv_from(&mut buffer)?;
    println!("Ответ от {:?}: {}", src, String::from_utf8_lossy(&buffer[..size]));
    Ok(())
}
    

Комментарии:

1.3 IP: Адресация

std::net предоставляет типы для работы с IP-адресами: IpAddr (общий тип), Ipv4Addr и Ipv6Addr.

Пример: работа с IP-адресами:

use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};

fn main() {
    let ipv4 = Ipv4Addr::new(127, 0, 0, 1);
    let ipv6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
    
    let ip: IpAddr = Ipv4Addr::LOCALHOST.into();
    println!("IPv4? {}, IPv6? {}", ip.is_ipv4(), ip.is_ipv6());
    
    let socket_addr = SocketAddr::new(ip, 8080);
    println!("Сокет: {}", socket_addr);
}
    

Комментарии:

Предупреждение: std::net блокирует поток при ожидании данных. Для асинхронных операций используйте крейты вроде tokio.


Практические советы


Пример: Эхо-сервер с выбором протокола

Программа, которая запускает TCP или UDP сервер в зависимости от аргумента:

use std::net::{TcpListener, UdpSocket, SocketAddr};
use std::io::{Read, Write};
use std::env;

fn tcp_server(addr: SocketAddr) -> std::io::Result<()> {
    let listener = TcpListener::bind(addr)?;
    println!("TCP сервер на {}", addr);
    for stream in listener.incoming() {
        let mut stream = stream?;
        let mut buffer = [0; 1024];
        let size = stream.read(&mut buffer)?;
        stream.write_all(&buffer[..size])?;
    }
    Ok(())
}

fn udp_server(addr: SocketAddr) -> std::io::Result<()> {
    let socket = UdpSocket::bind(addr)?;
    println!("UDP сервер на {}", addr);
    let mut buffer = [0; 1024];
    let (size, src) = socket.recv_from(&mut buffer)?;
    socket.send_to(&buffer[..size], src)?;
    Ok(())
}

fn main() -> std::io::Result<()> {
    let args: Vec = env::args().collect();
    if args.len() < 2 {
        println!("Укажите протокол: tcp или udp");
        return Ok(());
    }
    
    let addr = "127.0.0.1:8080".parse()?;
    match args[1].as_str() {
        "tcp" => tcp_server(addr),
        "udp" => udp_server(addr),
        _ => {
            println!("Неизвестный протокол");
            Ok(())
        }
    }
}
    

Этот пример демонстрирует TCP и UDP эхо-серверы с общей логикой обработки.


Упражнение

Напишите программу, которая:

  1. Принимает аргумент командной строки: server или client.
  2. Если server, запускает TCP-сервер, принимающий сообщения и отправляющий их в верхнем регистре.
  3. Если client, подключается к серверу, отправляет строку и выводит ответ.
  4. Обрабатывает ошибки с помощью Result.

Подсказка: Используйте to_uppercase() для строки.


Заключение

Модуль std::net предоставляет базовые средства для работы с сетью в Rust. Он прост, надёжен и кроссплатформенен, но ограничен блокирующими операциями. Освоив TCP, UDP и IP-адресацию, вы сможете создавать сетевые приложения, от простых чатов до серверов. Для более сложных задач рассмотрите асинхронные библиотеки, о которых мы поговорим позже.

Выполните упражнение или переходите к следующему разделу!