【跟小嘉学 Rust 编程】二十一、网络编程

系列文章目录

【跟小嘉学 Rust 编程】一、Rust 编程基础
【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
【跟小嘉学 Rust 编程】六、枚举和模式匹配
【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
【跟小嘉学 Rust 编程】八、常见的集合
【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
【跟小嘉学 Rust 编程】十一、编写自动化测试
【跟小嘉学 Rust 编程】十二、构建一个命令行程序
【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
【跟小嘉学 Rust 编程】十五、智能指针(Smart Point)
【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
【跟小嘉学 Rust 编程】十七、面向对象语言特性
【跟小嘉学 Rust 编程】十八、模式匹配(Patterns and Matching)
【跟小嘉学 Rust 编程】十九、高级特性
【跟小嘉学 Rust 编程】二十、进阶扩展
【跟小嘉学 Rust 编程】二十一、网络编程

前言

本章节讲解 Rust 标准库(std::net 模块)操作 TCP 和 UDP 编程

主要教材参考 《The Rust Programming Language》
主要教材参考 《Rust For Rustaceans》
主要教材参考 《The Rustonomicon》
主要教材参考 《Rust 高级编程》


一、 TCP

1.1、std::net::TcpListener

1.1.1、bind

TCP 服务端使用 std::net::TcpListener::bind 方法来监听IP地址和端口。该方法定义如下

 pub fn bind<A: ToSocketAddrs>(addr: A) -> io::Result<TcpListener>

我们可以看到泛型参数 A 为 ToSocketAddrs 其实是一个 SocketAddr 的数组,我们可以使用 字符串方式来调用该方法 也可以使用数组的方式来使用。

use std::net::{
    
    SocketAddr, TcpListener};
fn main(){
    
    
    let listener  = TcpListener::bind("127.0.0.1:3000").unwrap();

    let addrs = [
	    SocketAddr::from(([127, 0, 0, 1], 80)),
	    SocketAddr::from(([127, 0, 0, 1], 443)),
    ];
	let listener = TcpListener::bind(&addrs[..]).unwrap();
}

两种方式调用都可以,第二种就是绑定 80端口失败,绑定端口443;

1.1.2、try_clone 方法

为 socket 创建一个新的句柄 ,两个句柄都可以用于接受传入的链接。

1.1.3、accept 方法

pub fn accept(&self) -> Result<(TcpStream, SocketAddr)>

从 监听器上接收一个新的连接,该方法返回 Result 。

use std::net::TcpListener;
#![allow(unused)]
fn main() {
    
    
	let listener = TcpListener::bind("127.0.0.1:8080").unwrap();
	match listener.accept() {
    
    
	    Ok((_socket, addr)) => println!("new client: {addr:?}"),
	    Err(e) => eprintln!("couldn't get client: {e:?}"),
	}
}

1.1.4、incoming 方法

返回监听器上正在接收连接的迭代器,迭代器永远不会返回None,也不会产生一个 SokcetAddr 结构,相当于循环调用 accept 。

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

fn handle_connection(stream: TcpStream) {
    
    
   //...
}

fn main() -> std::io::Result<()> {
    
    
    let listener = TcpListener::bind("127.0.0.1:80")?;

    for stream in listener.incoming() {
    
    
        match stream {
    
    
            Ok(stream) => {
    
    
                handle_connection(stream);
            }
            Err(e) => {
    
     /* connection failed */ }
        }
    }
    Ok(())
}

1.1.5、into_incoming 方法

这是一个 仅限 nightly 使用的实验性 API,参考 https://github.com/rust-lang/rust/pull/88339

#![feature(tcplistener_into_incoming)]
use std::net::{
    
    TcpListener, TcpStream};

fn listen_on(port: u16) -> impl Iterator<Item = TcpStream> {
    
    
    let listener = TcpListener::bind(("127.0.0.1", port)).unwrap();
    listener.into_incoming()
        .filter_map(Result::ok) /* Ignore failed connections */
}

fn main() -> std::io::Result<()> {
    
    
    for stream in listen_on(80) {
    
    
        /* handle the connection here */
    }
    Ok(())
}

1.1.6、ttl 相关方法

pub fn set_ttl(&self, ttl: u32) -> Result<()>
pub fn ttl(&self) -> Result<u32>

socket 发送 internet 协议数据包的生存时间值。

1.1.7、set_nonblocking

将 TCP 流设置成阻塞或非阻塞方式。

use std::io;
use std::net::TcpListener;

let listener = TcpListener::bind("127.0.0.1:7878").unwrap();
listener.set_nonblocking(true).expect("Cannot set non-blocking");

for stream in listener.incoming() {
    
    
    match stream {
    
    
        Ok(s) => {
    
    
            // do something with the TcpStream
            handle_connection(s);
        }
        Err(ref e) if e.kind() == io::ErrorKind::WouldBlock => {
    
    
            // wait until network socket is ready, typically implemented
            // via platform-specific APIs such as epoll or IOCP
            wait_for_fd();
            continue;
        }
        Err(e) => panic!("encountered IO error: {e}"),
    }
}

1.2、地址相关类

1.2.1、Ipv4Addr/IpV6Addr/IpAddr

IpAddr 枚举

pub enum IpAddr {
    
    
    V4(Ipv4Addr),
    V6(Ipv6Addr),
}

使用方式

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

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


    let localhost_v4 = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
    let localhost_v6 = IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1));
    
    assert_eq!("127.0.0.1".parse(), Ok(localhost_v4));
    assert_eq!("::1".parse(), Ok(localhost_v6));
    
    assert_eq!(localhost_v4.is_ipv6(), false);
    assert_eq!(localhost_v4.is_ipv4(), true);
}

1.2.2、SocketAddr/SocketAddrV4/SocketAddrV6

同样 SocketAddr 是一个枚举

pub enum SocketAddr {
    
    
    V4(SocketAddrV4),
    V6(SocketAddrV6),
}

使用用例:

use std::net::{
    
    Ipv4Addr, SocketAddrV4};
use std::net::{
    
    Ipv6Addr, SocketAddrV6};

fn main(){
    
    
    let socket = SocketAddrV4::new(Ipv4Addr::new(127, 0, 0, 1), 8080);

    assert_eq!("127.0.0.1:8080".parse(), Ok(socket));
    assert_eq!(socket.ip(), &Ipv4Addr::new(127, 0, 0, 1));
    assert_eq!(socket.port(), 8080);

    let socket = SocketAddrV6::new(Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1), 8080, 0, 0);

    assert_eq!("[2001:db8::1]:8080".parse(), Ok(socket));
    assert_eq!(socket.ip(), &Ipv6Addr::new(0x2001, 0xdb8, 0, 0, 0, 0, 0, 1));
    assert_eq!(socket.port(), 8080);
}

1.3、std::net::TCPStream

使用 std::net::TCPStream 可以连接服务器、进行数据读取、写入数据;

1.3.1、connect 方法连接服务器

打开一个 Tcp 连接到远程主机。

pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<TcpStream>

使用

use std::net::TcpStream;

if let Ok(stream) = TcpStream::connect("127.0.0.1:8080") {
    
    
    println!("Connected to the server!");
} else {
    
    
    println!("Couldn't connect to server...");
}

1.3.2、connect_timeout

pub fn connect_timeout(
    addr: &SocketAddr,
    timeout: Duration
) -> Result<TcpStream>

在超时时间内打开一个远程主机的TCP连接。

1.3.3、shutdown

pub fn shutdown(&self, how: Shutdown) -> Result<()>

关闭连接读、写或都关闭。该函数将导致指定部分上的所有挂起和未来的I/O 立即返回一个适当的值。

特定平台的行为

  • Linux 第二次调用将返回· OK(())·
  • macOS;将返回 ErrorKind::NotConnected, 将来可能会改变

1.3.4、try_clone

pub fn try_clone(&self) -> Result<TcpStream>

1.3.5、set_read_timeout 和 set_write_timeout

pub fn set_read_timeout(&self, dur: Option<Duration>) -> Result<()>
pub fn set_write_timeout(&self, dur: Option<Duration>) -> Result<()>

如果指定为 None 读取调用将无限期阻塞,如果传入 0 Duration 。则返回Err。

特定平台行为:每当设置此项导致读取超时,平台可能返回不同的错误代码

  • Unix:返回一个类型为 WouldBlock 错误
  • Windows:可能返回 timeout;

1.3.6、peek

从连接上读取数据,不从队列中删除数据,如果成功返回已peek的字节数,连续调用返回相同的数据。

这是通过将MSG_PEEK作为一个标志传递给底层的recv系统调用来实现的。

pub fn peek(&self, buf: &mut [u8]) -> Result<usize>

1.3.7、set_linger

这是一个 nightly 版本的 实验性 API。

pub fn set_linger(&self, linger: Option<Duration>) -> Result<()>

如果设置了 SO_LINGER,系统尝试发送挂起数据时 ,sokect 将在指定时间内保持打开状态,否则系统会立即关闭套接字 或等待默认超时。

#![allow(unused)]
#![feature(tcp_linger)]
fn main() {
    
    
use std::net::TcpStream;
use std::time::Duration;

let stream = TcpStream::connect("127.0.0.1:8080")
                       .expect("Couldn't connect to the server...");
stream.set_linger(Some(Duration::from_secs(0))).expect("set_linger call failed");
}

1.3.8、set_nodelay

Rust 标准库中的 TCP 流默认启用了 Nagle 的算法,它会避免高频发送小量的数据包,所以数据不会总是立即发送,这会对一些场合的应用产生影响(Rust TCP/Websocket 连接延迟波动)。

设置为 true 之后,会禁用 Nagle 算法。

pub fn set_nodelay(&self, nodelay: bool) -> Result<()>

示例

use std::net::TcpStream;

#![allow(unused)]
fn main() {
    
    
	let stream = TcpStream::connect("127.0.0.1:8080")
	                       .expect("Couldn't connect to the server...");
	stream.set_nodelay(true).expect("set_nodelay call failed");
}

二、 std::net::UdpSocket

可以用于 UDP 协议通信,通常用于低延迟比保证传输更重要的场景,例如:音频/视频流、网络发现等。

2.1.1、bind 方法

pub fn bind<A: ToSocketAddrs>(addr: A) -> Result<UdpSocket>

从给定地址创建一个 UdpSocket 对象。使用方法和 TCPListener 差不多

2.1.2、recv_from 方法

pub fn recv_from(&self, buf: &mut [u8]) -> Result<(usize, SocketAddr)>

接受单个数据包信息,如果成功,返回读取的字节数和原始字节。函数必须使用有效字节数组调用,但必须有足够大小来保存消息字节,如果消息太长而无法转入所提供的缓存区则会丢弃多余的字节。

use std::net::UdpSocket;
#![allow(unused)]
fn main() {
    
    
	let socket = UdpSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
	let mut buf = [0; 10];
	let (number_of_bytes, src_addr) = socket.recv_from(&mut buf)
	                                        .expect("Didn't receive data");
	let filled_buf = &mut buf[..number_of_bytes];
	}

2.1.3、peek_from 方法

2.1.4、send_to 方法

发送数据到服务端

pub fn send_to<A: ToSocketAddrs>(&self, buf: &[u8], addr: A) -> Result<usize>

返回成功写入的字节数。

use std::net::UdpSocket;
#![allow(unused)]
fn main() {
    
    
	let socket = UdpSocket::bind("127.0.0.1:34254").expect("couldn't bind to address");
	socket.send_to(&[0; 10], "127.0.0.1:4242").expect("couldn't send data");
}

2.1.5、try_clone 方法

2.1.6、set_read_timeout 和 set_write_timeout

2.1.7、UDP 广播

2.1.7.2、广播

广播是一种一对多的通信,即目的是将数据报发送到网络中的所有节点。对于点对点通信的情况不同,我们不必知道目标主机的IP地址,而是使用广播地址。

广播地址是一个逻辑地址,连接到网络的设备可以在该地址上接收数据包。

2.1.7.1、set_broadcast

pub fn set_broadcast(&self, broadcast: bool) -> Result<()>
pub fn broadcast(&self) -> Result<bool>

默认值为false,设置为true之后,会开启广播

2.1.8、UDP 组播

2.1.8.1、组播

广播效率低下,因为数据包被发现倒网络中的所有节点,而不管他们是否有兴趣接收通信,这可能是一种资源浪费。多播解决了这个问题,并且只向感兴趣的消费者发送数据包

其中多播地址代表每个组。在IPv4 中,224.0.0.0 到 239.255.255.255 之间的任何地址都可以用作多播地址。只有订阅组的那些节点才能接收传送到该组的数据包。

2.1.8.2、set_multicast_loop_v4 和 set_multicast_loop_v6

获取此套接字的 IP_MULTICAST_LOOP 选项的值。

2.1.8.3、set_multicast_ttl_v4 和 set_multicast_ttl_v6

设置组播 ttl 超时时间

2.1.8.4、leave_multicast_v4 和 leave_multicast_v6

接收端离开组播

2.1.8.5、join_multicast_v4 和 join_multicast_v6

接收端加入组播地址才能接收数据。

2.1.9、set_ttl

设置超时时间

2.1.10、set_nonblocking

pub fn set_nonblocking(&self, nonblocking: bool) -> Result<()>

总结

以上就是今天要讲的内容

猜你喜欢

转载自blog.csdn.net/fj_Author/article/details/132584937
今日推荐