Перечисление Shutdown

#![allow(unused)]
fn main() {
pub enum Shutdown {
    Read,
    Write,
    Both,
}
}

Флаги для отключения частей соединения.

Shutdown используется в методе shutdown для указания, какая часть соединения должна быть отключена.

Варианты

Read

#![allow(unused)]
fn main() {
Read
}

Запрещает дальнейшие операции чтения.

После вызова shutdown(Shutdown::Read) все последующие вызовы read будут возвращать Ok(0), указывая на конец потока.

Примеры

#![allow(unused)]
fn main() {
use std::net::{TcpStream, Shutdown};
use std::io::Read;

let stream = TcpStream::connect("127.0.0.1:8080").unwrap();

// Отключаем чтение
stream.shutdown(Shutdown::Read).unwrap();

let mut buf = [0; 10];
let n = stream.read(&mut buf).unwrap();
assert_eq!(n, 0); // Больше данных для чтения нет
}

Write

#![allow(unused)]
fn main() {
Write
}

Запрещает дальнейшие операции записи.

После вызова shutdown(Shutdown::Write) все последующие вызовы write будут возвращать ошибку.

Примеры

#![allow(unused)]
fn main() {
use std::net::{TcpStream, Shutdown};
use std::io::Write;

let stream = TcpStream::connect("127.0.0.1:8080").unwrap();

// Отключаем запись
stream.shutdown(Shutdown::Write).unwrap();

// Попытка записи после отключения
match stream.write(b"hello") {
    Ok(_) => println!("Запись успешна"),
    Err(e) => println!("Ошибка записи: {}", e), // Будет ошибка
}
}

Both

#![allow(unused)]
fn main() {
Both
}

Запрещает как чтение, так и запись.

Эквивалентно вызову shutdown(Shutdown::Read) и shutdown(Shutdown::Write).

Примеры

#![allow(unused)]
fn main() {
use std::net::{TcpStream, Shutdown};
use std::io::{Read, Write};

let stream = TcpStream::connect("127.0.0.1:8080").unwrap();

// Полностью отключаем соединение
stream.shutdown(Shutdown::Both).unwrap();

// И чтение, и запись теперь будут возвращать ошибки
let mut buf = [0; 10];
let read_result = stream.read(&mut buf);
let write_result = stream.write(b"hello");

println!("Чтение после отключения: {:?}", read_result);
println!("Запись после отключения: {:?}", write_result);
}

Трайт-реализации

Clone, Copy

#![allow(unused)]
fn main() {
impl Clone for Shutdown
impl Copy for Shutdown
}

Реализует клонирование и копирование для Shutdown.

Примеры

#![allow(unused)]
fn main() {
use std::net::Shutdown;

let shutdown_read = Shutdown::Read;
let shutdown_read_clone = shutdown_read.clone(); // Клонирование
let shutdown_read_copy = shutdown_read; // Копирование

assert_eq!(shutdown_read, shutdown_read_clone);
assert_eq!(shutdown_read, shutdown_read_copy);
}

Debug

#![allow(unused)]
fn main() {
impl Debug for Shutdown
}

Реализует отладочное отображение для Shutdown.

Примеры

#![allow(unused)]
fn main() {
use std::net::Shutdown;

println!("Отладочная информация: {:?}", Shutdown::Read);   // "Read"
println!("Отладочная информация: {:?}", Shutdown::Write);  // "Write"
println!("Отладочная информация: {:?}", Shutdown::Both);   // "Both"
}

PartialEq, Eq

#![allow(unused)]
fn main() {
impl PartialEq for Shutdown
impl Eq for Shutdown
}

Реализует сравнение для Shutdown.

Примеры

#![allow(unused)]
fn main() {
use std::net::Shutdown;

assert_eq!(Shutdown::Read, Shutdown::Read);
assert_ne!(Shutdown::Read, Shutdown::Write);
assert_eq!(Shutdown::Both, Shutdown::Both);
}

Примеры использования

Базовое использование с TCP соединениями

use std::net::{TcpStream, Shutdown};
use std::io::{Read, Write};
use std::thread;
use std::time::Duration;

fn demonstrate_shutdown() -> std::io::Result<()> {
    // Предположим, что у нас есть TCP соединение
    let stream = TcpStream::connect("127.0.0.1:8080")?;
    
    println!("Демонстрация Shutdown::Read:");
    stream.shutdown(Shutdown::Read)?;
    
    let mut buf = [0; 1024];
    match stream.read(&mut buf) {
        Ok(0) => println!("  ✓ Чтение возвращает 0 (конец потока)"),
        Ok(n) => println!("  ✗ Неожиданно прочитано {} байт", n),
        Err(e) => println!("  ✗ Ошибка чтения: {}", e),
    }
    
    // Создаем новое соединение для следующего теста
    let stream2 = TcpStream::connect("127.0.0.1:8080")?;
    
    println!("\nДемонстрация Shutdown::Write:");
    stream2.shutdown(Shutdown::Write)?;
    
    match stream2.write(b"Hello, server!") {
        Ok(_) => println!("  ✗ Запись неожиданно удалась"),
        Err(e) => println!("  ✓ Запись возвращает ошибку: {}", e),
    }
    
    // Создаем новое соединение для следующего теста
    let stream3 = TcpStream::connect("127.0.0.1:8080")?;
    
    println!("\nДемонстрация Shutdown::Both:");
    stream3.shutdown(Shutdown::Both)?;
    
    let read_result = stream3.read(&mut buf);
    let write_result = stream3.write(b"Hello");
    
    println!("  Чтение после Both: {:?}", read_result);
    println!("  Запись после Both: {:?}", write_result);
    
    Ok(())
}

fn main() -> std::io::Result<()> {
    demonstrate_shutdown()
}

Грациозное завершение сервера

use std::net::{TcpListener, TcpStream, Shutdown};
use std::io::{Read, Write};
use std::thread;
use std::sync::{Arc, Mutex};
use std::time::Duration;

struct ConnectionManager {
    active_connections: Mutex<Vec<TcpStream>>,
}

impl ConnectionManager {
    fn new() -> Self {
        Self {
            active_connections: Mutex::new(Vec::new()),
        }
    }
    
    fn add_connection(&self, stream: TcpStream) {
        let mut connections = self.active_connections.lock().unwrap();
        connections.push(stream);
    }
    
    fn graceful_shutdown(&self) -> std::io::Result<()> {
        let mut connections = self.active_connections.lock().unwrap();
        
        println!("Начинаем грациозное завершение {} соединений...", connections.len());
        
        for (i, stream) in connections.iter_mut().enumerate() {
            println!("  Завершаем соединение {}", i + 1);
            
            // Сначала отключаем запись - уведомляем клиент о завершении
            if let Err(e) = stream.shutdown(Shutdown::Write) {
                println!("    Предупреждение: не удалось отключить запись: {}", e);
            }
            
            // Даем клиенту время завершить свою работу
            thread::sleep(Duration::from_millis(100));
            
            // Затем полностью закрываем соединение
            if let Err(e) = stream.shutdown(Shutdown::Both) {
                println!("    Предупреждение: не удалось полностью отключить: {}", e);
            }
        }
        
        connections.clear();
        println!("Все соединения завершены грациозно.");
        
        Ok(())
    }
}

fn handle_client(stream: TcpStream, manager: Arc<ConnectionManager>) {
    manager.add_connection(stream);
    
    // Имитация обработки клиента
    println!("Обработка нового клиента...");
    thread::sleep(Duration::from_secs(5));
    println!("Обработка клиента завершена.");
}

fn main() -> std::io::Result<()> {
    let manager = Arc::new(ConnectionManager::new());
    let listener = TcpListener::bind("127.0.0.1:0")?;
    let port = listener.local_addr()?.port();
    
    println!("Сервер запущен на порту {}", port);
    
    // Запускаем сервер в отдельном потоке
    let manager_clone = Arc::clone(&manager);
    let server_handle = thread::spawn(move || {
        for stream_result in listener.incoming().take(3) { // Принимаем только 3 соединения
            match stream_result {
                Ok(stream) => {
                    let manager = Arc::clone(&manager_clone);
                    thread::spawn(move || {
                        handle_client(stream, manager);
                    });
                }
                Err(e) => eprintln!("Ошибка принятия соединения: {}", e),
            }
        }
    });
    
    // Даем серверу время принять соединения
    thread::sleep(Duration::from_secs(2));
    
    // Грациозное завершение
    println!("\nИнициируем грациозное завершение...");
    manager.graceful_shutdown()?;
    
    server_handle.join().unwrap();
    println!("Сервер завершил работу.");
    
    Ok(())
}

Двусторонняя коммуникация с контролируемым завершением

use std::net::{TcpStream, Shutdown};
use std::io::{Read, Write};
use std::thread;
use std::time::Duration;

fn client_with_controlled_shutdown() -> std::io::Result<()> {
    let mut stream = TcpStream::connect("127.0.0.1:8080")?;
    
    // Фаза 1: Нормальная коммуникация
    println!("Фаза 1: Нормальная коммуникация");
    stream.write_all(b"Hello from client")?;
    
    let mut buffer = [0; 1024];
    let n = stream.read(&mut buffer)?;
    println!("Получено от сервера: {}", String::from_utf8_lossy(&buffer[..n]));
    
    // Фаза 2: Отключаем только запись (уведомляем сервер о завершении отправки)
    println!("\nФаза 2: Отключаем запись (Shutdown::Write)");
    stream.shutdown(Shutdown::Write)?;
    
    // Можем еще читать ответы от сервера
    match stream.read(&mut buffer) {
        Ok(0) => println!("Сервер закрыл соединение"),
        Ok(n) => println!("Финальное сообщение от сервера: {}", 
                         String::from_utf8_lossy(&buffer[..n])),
        Err(e) => println!("Ошибка чтения: {}", e),
    }
    
    // Фаза 3: Полное закрытие
    println!("\nФаза 3: Полное закрытие (Shutdown::Both)");
    stream.shutdown(Shutdown::Both)?;
    
    println!("Соединение полностью закрыто");
    
    Ok(())
}

fn simulate_server() -> std::io::Result<()> {
    let listener = std::net::TcpListener::bind("127.0.0.1:8080")?;
    
    thread::spawn(move || {
        for stream in listener.incoming() {
            match stream {
                Ok(mut stream) => {
                    thread::spawn(move || {
                        let mut buffer = [0; 1024];
                        
                        // Читаем запрос от клиента
                        match stream.read(&mut buffer) {
                            Ok(0) => {
                                println!("Клиент закрыл соединение");
                                return;
                            }
                            Ok(n) => {
                                println!("Сервер получил: {}", 
                                        String::from_utf8_lossy(&buffer[..n]));
                                
                                // Отправляем ответ
                                stream.write_all(b"Hello from server").unwrap();
                                
                                // Ждем, пока клиент отключит запись
                                thread::sleep(Duration::from_secs(1));
                                
                                // Отправляем финальное сообщение
                                stream.write_all(b"Final message from server").unwrap();
                                
                                // Грациозно закрываем соединение
                                stream.shutdown(Shutdown::Both).unwrap();
                            }
                            Err(e) => {
                                eprintln!("Ошибка чтения: {}", e);
                            }
                        }
                    });
                }
                Err(e) => eprintln!("Ошибка принятия соединения: {}", e),
            }
        }
    });
    
    Ok(())
}

fn main() -> std::io::Result<()> {
    // Запускаем имитацию сервера
    simulate_server()?;
    
    // Даем серверу время запуститься
    thread::sleep(Duration::from_millis(100));
    
    // Запускаем клиента
    client_with_controlled_shutdown()?;
    
    Ok(())
}

Обработка ошибок при отключении

use std::net::{TcpStream, Shutdown};
use std::io;

fn demonstrate_shutdown_errors() {
    let test_cases = [
        ("Нормальное соединение", || {
            TcpStream::connect("127.0.0.1:8080")
        }),
        ("Уже закрытое соединение", || {
            let stream = TcpStream::connect("127.0.0.1:8080")?;
            stream.shutdown(Shutdown::Both)?;
            Ok(stream)
        }),
    ];
    
    for (description, stream_creator) in test_cases {
        println!("Тест: {}", description);
        
        match stream_creator() {
            Ok(stream) => {
                // Пытаемся отключить разными способами
                let results = [
                    ("Shutdown::Read", stream.shutdown(Shutdown::Read)),
                    ("Shutdown::Write", stream.shutdown(Shutdown::Write)),
                    ("Shutdown::Both", stream.shutdown(Shutdown::Both)),
                ];
                
                for (shutdown_type, result) in results {
                    match result {
                        Ok(()) => println!("  ✓ {}: успешно", shutdown_type),
                        Err(e) => println!("  ✗ {}: ошибка - {}", shutdown_type, e),
                    }
                }
            }
            Err(e) => {
                println!("  ✗ Не удалось создать соединение: {}", e);
            }
        }
        println!();
    }
}

fn main() {
    demonstrate_shutdown_errors();
}

Использование в протоколе с подтверждением завершения

use std::net::{TcpStream, Shutdown};
use std::io::{Read, Write};
use std::time::{Duration, Instant};

struct GracefulProtocol;

impl GracefulProtocol {
    fn perform_graceful_shutdown(mut stream: TcpStream) -> std::io::Result<()> {
        println!("Начинаем грациозное завершение протокола...");
        
        // Шаг 1: Отправляем уведомление о завершении
        println!("Шаг 1: Отправляем FINISH команду");
        stream.write_all(b"FINISH")?;
        
        // Шаг 2: Ждем подтверждения от другой стороны
        println!("Шаг 2: Ждем подтверждения ACK");
        let mut buffer = [0; 3];
        let start = Instant::now();
        let timeout = Duration::from_secs(5);
        
        while start.elapsed() < timeout {
            match stream.read(&mut buffer) {
                Ok(0) => {
                    println!("  Соединение закрыто другой стороной");
                    break;
                }
                Ok(n) if &buffer[..n] == b"ACK" => {
                    println!("  ✓ Получено подтверждение ACK");
                    break;
                }
                Ok(n) => {
                    println!("  Получен неожиданный ответ: {}", 
                            String::from_utf8_lossy(&buffer[..n]));
                }
                Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
                    // Продолжаем ждать
                    continue;
                }
                Err(e) => {
                    println!("  Ошибка чтения: {}", e);
                    break;
                }
            }
        }
        
        // Шаг 3: Отключаем запись
        println!("Шаг 3: Отключаем запись (Shutdown::Write)");
        stream.shutdown(Shutdown::Write)?;
        
        // Шаг 4: Ждем, пока другая сторона тоже отключит запись
        println!("Шаг 4: Ждем завершения другой стороны");
        thread::sleep(Duration::from_secs(1));
        
        // Шаг 5: Полное закрытие
        println!("Шаг 5: Полное закрытие (Shutdown::Both)");
        stream.shutdown(Shutdown::Both)?;
        
        println!("Грациозное завершение завершено успешно");
        Ok(())
    }
}

fn main() -> std::io::Result<()> {
    // В реальном приложении здесь было бы установлено соединение
    println!("Демонстрация протокола грациозного завершения");
    println!("(В реальном приложении здесь было бы реальное соединение)");
    
    Ok(())
}

Особенности использования

Поведение на разных платформах

  • Unix-подобные системы: использует системный вызов shutdown()
  • Windows: использует shutdown() из Winsock API

Множественные вызовы

Многократные вызовы shutdown() с одинаковым или разными флагами допустимы, но могут возвращать ошибки если соединение уже закрыто.

Взаимодействие с закрытием

shutdown() отличается от простого закрытия (close()) сокета:

  • shutdown() уведомляет другую сторону о завершении определенной операции
  • close() освобождает ресурсы, но может не уведомлять другую сторону немедленно

Использование с неблокирующими сокетами

Поведение shutdown() с неблокирующими сокетами такое же, как и с блокирующими.