码农知识堂 - 1000bd
  •   Python
  •   PHP
  •   JS/TS
  •   JAVA
  •   C/C++
  •   C#
  •   GO
  •   Kotlin
  •   Swift
  • 【跟小嘉学 Rust 编程】二十一、网络编程


    系列文章目录

    【跟小嘉学 Rust 编程】一、Rust 编程基础
    【跟小嘉学 Rust 编程】二、Rust 包管理工具使用
    【跟小嘉学 Rust 编程】三、Rust 的基本程序概念
    【跟小嘉学 Rust 编程】四、理解 Rust 的所有权概念
    【跟小嘉学 Rust 编程】五、使用结构体关联结构化数据
    【跟小嘉学 Rust 编程】六、枚举和模式匹配
    【跟小嘉学 Rust 编程】七、使用包(Packages)、单元包(Crates)和模块(Module)来管理项目
    【跟小嘉学 Rust 编程】八、常见的集合
    【跟小嘉学 Rust 编程】九、错误处理(Error Handling)
    【跟小嘉学 Rust 编程】十、泛型(Generic Type)、特征(Trait)和生命周期(Lifetimes)
    【跟小嘉学 Rust 编程】十一、编写自动化测试
    【跟小嘉学 Rust 编程】十二、构建一个命令行程序
    【跟小嘉学 Rust 编程】十三、函数式语言特性:迭代器和闭包
    【跟小嘉学 Rust 编程】十四、关于 Cargo 和 Crates.io
    【跟小嘉学 Rust 编程】十五、智能指针(Smart Point)
    【跟小嘉学 Rust 编程】十六、无畏并发(Fearless Concurrency)
    【跟小嘉学 Rust 编程】十七、面向对象语言特性
    【跟小嘉学 Rust 编程】十八、模式匹配(Patterns and Matching)
    【跟小嘉学 Rust 编程】十九、高级特性
    【跟小嘉学 Rust 编程】二十、进阶扩展
    【跟小嘉学 Rust 编程】二十一、网络编程
    【跟小嘉学 Rust 编程】二十二、常用 API
    【跟小嘉学 Rust 编程】二十三、Cargo 使用指南
    【跟小嘉学 Rust 编程】二十四、内联汇编(inline assembly)
    【跟小嘉学 Rust 编程】二十五、Rust命令行参数解析库(clap)
    【跟小嘉学 Rust 编程】二十六、Rust的序列化解决方案(Serde)
    【跟小嘉学 Rust 编程】二十七、Rust 异步编程(Asynchronous Programming)
    【跟小嘉学 Rust 编程】二十八、Rust中的日期与时间
    【跟小嘉学 Rust 编程】二十九、Rust 中的零拷贝序列化解决方案(rkyv)
    【跟小嘉学 Rust 编程】三十、Rust 使用 Slint UI
    【跟小嘉学 Rust 编程】三十一、Rust的日志与追踪
    【跟小嘉学 Rust 编程】三十二、Rust的设计模式(Design Patterns)
    【跟小嘉学 Rust 编程】三十三、Rust的Web开发框架之一: Actix-Web的基础
    【跟小嘉学 Rust 编程】三十四、Rust的Web开发框架之一: Actix-Web的进阶

    文章目录

    • 系列文章目录
      • @[TOC](文章目录)
    • 前言
    • 一、 TCP
      • 1.1、std::net::TcpListener
        • 1.1.1、bind
        • 1.1.2、try_clone 方法
        • 1.1.3、accept 方法
        • 1.1.4、incoming 方法
        • 1.1.5、into_incoming 方法
        • 1.1.6、ttl 相关方法
        • 1.1.7、set_nonblocking
      • 1.2、地址相关类
        • 1.2.1、Ipv4Addr/IpV6Addr/IpAddr
        • 1.2.2、SocketAddr/SocketAddrV4/SocketAddrV6
      • 1.3、std::net::TCPStream
        • 1.3.1、connect 方法连接服务器
        • 1.3.2、connect_timeout
        • 1.3.3、shutdown
        • 1.3.4、try_clone
        • 1.3.5、set_read_timeout 和 set_write_timeout
        • 1.3.6、peek
        • 1.3.7、set_linger
        • 1.3.8、set_nodelay
    • 二、 std::net::UdpSocket
      • 2.1.1、bind 方法
      • 2.1.2、recv_from 方法
      • 2.1.3、peek_from 方法
      • 2.1.4、send_to 方法
      • 2.1.5、try_clone 方法
      • 2.1.6、set_read_timeout 和 set_write_timeout
      • 2.1.7、UDP 广播
        • 2.1.7.2、广播
        • 2.1.7.1、set_broadcast
      • 2.1.8、UDP 组播
        • 2.1.8.1、组播
        • 2.1.8.2、set_multicast_loop_v4 和 set_multicast_loop_v6
        • 2.1.8.3、set_multicast_ttl_v4 和 set_multicast_ttl_v6
        • 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
    • 总结

    前言

    本章节讲解 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>
    
    • 1

    我们可以看到泛型参数 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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

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

    1.1.2、try_clone 方法

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

    1.1.3、accept 方法

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

    从 监听器上接收一个新的连接,该方法返回 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.1.6、ttl 相关方法

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

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    1.2、地址相关类

    1.2.1、Ipv4Addr/IpV6Addr/IpAddr

    IpAddr 枚举

    pub enum IpAddr {
        V4(Ipv4Addr),
        V6(Ipv6Addr),
    }
    
    • 1
    • 2
    • 3
    • 4

    使用方式

    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
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    1.2.2、SocketAddr/SocketAddrV4/SocketAddrV6

    同样 SocketAddr 是一个枚举

    pub enum SocketAddr {
        V4(SocketAddrV4),
        V6(SocketAddrV6),
    }
    
    • 1
    • 2
    • 3
    • 4

    使用用例:

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    1.3、std::net::TCPStream

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

    1.3.1、connect 方法连接服务器

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

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

    使用

    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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    1.3.2、connect_timeout

    pub fn connect_timeout(
        addr: &SocketAddr,
        timeout: Duration
    ) -> Result<TcpStream>
    
    • 1
    • 2
    • 3
    • 4

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

    1.3.3、shutdown

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

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

    特定平台的行为

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

    1.3.4、try_clone

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

    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<()>
    
    • 1
    • 2

    如果指定为 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

    1.3.7、set_linger

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

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

    如果设置了 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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    1.3.8、set_nodelay

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

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

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

    示例

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    二、 std::net::UdpSocket

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

    2.1.1、bind 方法

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

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

    2.1.2、recv_from 方法

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

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

    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];
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.1.3、peek_from 方法

    2.1.4、send_to 方法

    发送数据到服务端

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

    返回成功写入的字节数。

    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");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    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>
    
    • 1
    • 2

    默认值为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<()>
    
    • 1

    总结

    以上就是今天要讲的内容

  • 相关阅读:
    音视频专题--opengl (3)
    位运算及其使用技巧
    面试算法33:变位词组
    QT QFile
    Egress Gateway
    生鲜商城系统
    Unity实战之一个脚本实现雷达图
    Java2EE基础练习_chapter03数组
    改变Bootstrap4、Bootstrap5中复选框的颜色
    如何使用Java进行异步编程
  • 原文地址:https://blog.csdn.net/fj_Author/article/details/132584937
  • 最新文章
  • 沪漂五周年了:我越来越迷茫了
    Agentic Skill Routing 实战:别再把所有 Skill 塞进 AI Agent 上下文
    MySQL-Seconds_behind_master的精度误差
    [MAF预定义ChatClient中间件-03]CachingChatClient——利用缓存省钱省时间
    AI的至暗历史:从万众期待到被政府撤资,AI的两次死亡徘徊
    Agent OS :五种驯服不确定性的范式
    PortSwigger SQL注入LAB11
    数据库即时编译JIT
    [Begin]AI Learn Data Day 0
    深度学习进阶(二十七)现代 LLM 的核心架构设计其二:SwiGLU
  • 热门文章
  • 十款代码表白小特效 一个比一个浪漫 赶紧收藏起来吧!!!
    奉劝各位学弟学妹们,该打造你的技术影响力了!
    五年了,我在 CSDN 的两个一百万。
    Java俄罗斯方块,老程序员花了一个周末,连接中学年代!
    面试官都震惊,你这网络基础可以啊!
    你真的会用百度吗?我不信 — 那些不为人知的搜索引擎语法
    心情不好的时候,用 Python 画棵樱花树送给自己吧
    通宵一晚做出来的一款类似CS的第一人称射击游戏Demo!原来做游戏也不是很难,连憨憨学妹都学会了!
    13 万字 C 语言从入门到精通保姆级教程2021 年版
    10行代码集2000张美女图,Python爬虫120例,再上征途
小工具 小游戏
Copyright © 2022 侵权请联系2656653265@qq.com    京ICP备2022015340号-1

京公网安备 11010502049817号