Трайт ToSocketAddrs

#![allow(unused)]
fn main() {
pub trait ToSocketAddrs {
    type Iter: Iterator<Item = SocketAddr>;
    
    fn to_socket_addrs(&self) -> Result<Self::Iter>;
}
}

Трайт для объектов, которые могут быть преобразованы в SocketAddr.

Этот трайт используется для обобщенного разрешения адресов в объекты SocketAddr. По умолчанию он реализован для следующих типов:

  • SocketAddr - to_socket_addrs является тождественной функцией
  • SocketAddrV4, SocketAddrV6 - преобразуются в SocketAddr
  • (IpAddr, u16) - создает адрес сокета из IP-адреса и порта
  • (Ipv4Addr, u16), (Ipv6Addr, u16) - аналогично предыдущему
  • &str - строка в формате "host:port" или "host" (с портом по умолчанию 0)
  • &String - то же самое, что и &str
  • &[SocketAddr] - итератор по уже разрешенным адресам

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

Iter

#![allow(unused)]
fn main() {
type Iter: Iterator<Item = SocketAddr>
}

Возвращаемый итератор по адресам сокетов, который может создавать несколько значений SocketAddr из одного входного значения.

Обязательные методы

to_socket_addrs

#![allow(unused)]
fn main() {
fn to_socket_addrs(&self) -> Result<Self::Iter>
}

Преобразует объект в итератор по разрешенным SocketAddr.

Возвращаемый итератор может не возвращать адреса в определенном порядке. Порт по умолчанию - 0.

Реализации по умолчанию

Для SocketAddr

#![allow(unused)]
fn main() {
impl ToSocketAddrs for SocketAddr {
    type Iter = option::IntoIter<SocketAddr>;
    
    fn to_socket_addrs(&self) -> Result<option::IntoIter<SocketAddr>> {
        Ok(Some(*self).into_iter())
    }
}
}

Примеры

#![allow(unused)]
fn main() {
use std::net::{SocketAddr, ToSocketAddrs};

let addr: SocketAddr = "127.0.0.1:8080".parse().unwrap();
for socket_addr in addr.to_socket_addrs().unwrap() {
    println!("Адрес: {}", socket_addr);
}
}

Для &[SocketAddr]

#![allow(unused)]
fn main() {
impl<'a> ToSocketAddrs for &'a [SocketAddr] {
    type Iter = iter::Cloned<slice::Iter<'a, SocketAddr>>;
    
    fn to_socket_addrs(&self) -> Result<iter::Cloned<slice::Iter<'a, SocketAddr>>> {
        Ok(self.iter().cloned())
    }
}
}

Примеры

#![allow(unused)]
fn main() {
use std::net::{SocketAddr, ToSocketAddrs};

let addrs = [
    "127.0.0.1:8080".parse().unwrap(),
    "127.0.0.1:8081".parse().unwrap(),
];

for socket_addr in addrs.to_socket_addrs().unwrap() {
    println!("Адрес: {}", socket_addr);
}
}

Для кортежа (IpAddr, u16)

#![allow(unused)]
fn main() {
impl ToSocketAddrs for (IpAddr, u16) {
    type Iter = option::IntoIter<SocketAddr>;
    
    fn to_socket_addrs(&self) -> Result<option::IntoIter<SocketAddr>> {
        let (ip, port) = *self;
        Ok(Some(SocketAddr::new(ip, port)).into_iter())
    }
}
}

Примеры

#![allow(unused)]
fn main() {
use std::net::{IpAddr, Ipv4Addr, ToSocketAddrs};

let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let port = 8080;

for socket_addr in (ip, port).to_socket_addrs().unwrap() {
    println!("Адрес: {}", socket_addr);
}
}

Для строк &str и String

#![allow(unused)]
fn main() {
impl ToSocketAddrs for &str {
    type Iter = vec::IntoIter<SocketAddr>;
    
    fn to_socket_addrs(&self) -> Result<vec::IntoIter<SocketAddr>> {
        // Реализация разрешения имен хостов
    }
}

impl ToSocketAddrs for String {
    type Iter = vec::IntoIter<SocketAddr>;
    
    fn to_socket_addrs(&self) -> Result<vec::IntoIter<SocketAddr>> {
        (&**self).to_socket_addrs()
    }
}
}

Примеры

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

// Разрешение IPv4 адреса
for socket_addr in "127.0.0.1:8080".to_socket_addrs().unwrap() {
    println!("IPv4 адрес: {}", socket_addr);
}

// Разрешение IPv6 адреса  
for socket_addr in "[::1]:8080".to_socket_addrs().unwrap() {
    println!("IPv6 адрес: {}", socket_addr);
}

// Разрешение имени хоста
if let Ok(addrs) = "example.com:80".to_socket_addrs() {
    for socket_addr in addrs {
        println!("Адрес хоста: {}", socket_addr);
    }
}

// Только порт (localhost с портом 0)
for socket_addr in ":0".to_socket_addrs().unwrap() {
    println!("Адрес: {}", socket_addr);
}
}

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

Использование в сетевых функциях

use std::net::{TcpStream, TcpListener, UdpSocket};
use std::io;

fn connect_to_server<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> {
    TcpStream::connect(addr)
}

fn create_listener<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener> {
    TcpListener::bind(addr)
}

fn create_udp_socket<A: ToSocketAddrs>(addr: A) -> io::Result<UdpSocket> {
    UdpSocket::bind(addr)
}

fn main() -> io::Result<()> {
    // Различные способы указания адресов
    let _stream1 = connect_to_server("127.0.0.1:8080")?;
    let _stream2 = connect_to_server(("127.0.0.1".parse().unwrap(), 8080))?;
    let _stream3 = connect_to_server("[::1]:8080")?;
    
    let _listener = create_listener("0.0.0.0:8080")?;
    let _udp_socket = create_udp_socket("localhost:0")?;
    
    Ok(())
}

Обработка множественных адресов

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

fn try_connect<A: ToSocketAddrs>(addr: A) -> io::Result<TcpStream> {
    let mut last_error = None;
    
    for socket_addr in addr.to_socket_addrs()? {
        match TcpStream::connect(socket_addr) {
            Ok(stream) => return Ok(stream),
            Err(e) => last_error = Some(e),
        }
    }
    
    Err(last_error.unwrap_or_else(|| {
        io::Error::new(io::ErrorKind::Other, "не удалось разрешить адрес")
    }))
}

fn main() -> io::Result<()> {
    // Попытка подключения к нескольким адресам
    let stream = try_connect("example.com:80")?;
    println!("Успешно подключились!");
    Ok(())
}

Создание пользовательских типов, реализующих ToSocketAddrs

use std::net::{SocketAddr, ToSocketAddrs};
use std::vec;

#[derive(Debug)]
struct ServerConfig {
    host: String,
    port: u16,
    backup_ports: Vec<u16>,
}

impl ToSocketAddrs for ServerConfig {
    type Iter = vec::IntoIter<SocketAddr>;
    
    fn to_socket_addrs(&self) -> std::io::Result<vec::IntoIter<SocketAddr>> {
        let mut addrs = Vec::new();
        
        // Основной порт
        let main_addr = format!("{}:{}", self.host, self.port);
        addrs.extend(main_addr.to_socket_addrs()?);
        
        // Резервные порты
        for &port in &self.backup_ports {
            let backup_addr = format!("{}:{}", self.host, port);
            addrs.extend(backup_addr.to_socket_addrs()?);
        }
        
        Ok(addrs.into_iter())
    }
}

fn main() -> std::io::Result<()> {
    let config = ServerConfig {
        host: "localhost".to_string(),
        port: 8080,
        backup_ports: vec![8081, 8082],
    };
    
    println!("Доступные адреса сервера:");
    for addr in config.to_socket_addrs()? {
        println!("  {}", addr);
    }
    
    Ok(())
}

Использование с UDP сокетами

use std::net::{UdpSocket, ToSocketAddrs};
use std::io;

fn send_message<A: ToSocketAddrs>(socket: &UdpSocket, msg: &[u8], addr: A) -> io::Result<usize> {
    let mut last_error = None;
    
    for target_addr in addr.to_socket_addrs()? {
        match socket.send_to(msg, target_addr) {
            Ok(size) => return Ok(size),
            Err(e) => last_error = Some(e),
        }
    }
    
    Err(last_error.unwrap_or_else(|| {
        io::Error::new(io::ErrorKind::Other, "не удалось отправить на любой адрес")
    }))
}

fn main() -> io::Result<()> {
    let socket = UdpSocket::bind("0.0.0.0:0")?;
    
    // Отправка на различные типы адресов
    send_message(&socket, b"Hello", "127.0.0.1:8080")?;
    send_message(&socket, b"Hello", ("::1".parse().unwrap(), 8080))?;
    send_message(&socket, b"Hello", "localhost:8080")?;
    
    Ok(())
}

Особенности реализации

Разрешение имен хостов

При использовании строк для разрешения адресов:

  • Блокирующая операция: разрешение имен хостов может блокировать текущий поток
  • Множественные адреса: один хост может вернуть несколько IP-адресов
  • Порядок: адреса могут возвращаться в произвольном порядке
  • Таймауты: разрешение имен подвержено таймаутам сети

Порт по умолчанию

Когда порт не указан (например, "localhost"), используется порт 0.

Ошибки разрешения

Метод возвращает io::Result, который может содержать ошибки:

Рекомендации по использованию

  1. Всегда обрабатывайте ошибки - разрешение адресов может завершиться неудачно
  2. Итерируйтесь по результатам - один вызов может вернуть несколько адресов
  3. Используйте для гибкости - принимайте ToSocketAddrs в своих API
  4. Помните о блокировках - разрешение имен хостов блокирует поток