Перечисление 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() с неблокирующими сокетами такое же, как и с блокирующими.