全网最全GB/T 28181网络基础-Rust实现TCP/UDP服务端

114 阅读10分钟

鉴于GB/T 28181协议要求同时监听TCP/UDP的特性,为确保服务端的高性能与高容错,编程语言选择需侧重于:

  • 高性能处理并发与I/O密集型任务。
  • 内置并发模型,如异步IO、事件驱动,优化资源利用。
  • 强大的网络编程支持,简化TCP/UDP协议实现。
  • 健壮的错误处理,确保稳定性和容错恢复能力。
  • 跨平台能力,适应多样化部署环境。

因此,倾向于选用如RustGoC++这类现代语言,它们在满足上述条件的同时,也促进了开发效率与系统稳定性,本篇我们选择了Rust作为开发语言来实现TCP/UDP服务端。

Rust 是一种高效、安全的系统编程语言,无垃圾回收,通过所有权系统保障内存安全,支持并发,擅长领域包括系统编程、网络服务、嵌入式开发、游戏后台、加密货币和云基础设施,融合了现代编程特性,适合性能关键型应用开发。开发文档地址: kaisery.github.io/trpl-zh-cn/

1. 初步实现

首先我们尝试使用Rust原生的std:net库实现一个基本能满足tcp/udp占用同一端口进行监听的程序,收发数据的流程,在实现GB/T 28181服务端中不管是信令还是媒体,都需要这样的功能作为基建。现实现如下,具体代码解释请参考注释,流程比较简单。

use std::net::{TcpListener, UdpSocket};
use std::thread;
use std::io::{Read, Write};

// 返回 Result 类型,成功时返回 (),失败时返回错误类型
pub fn test1() -> Result<(), Box<dyn std::error::Error>>{
    // 定义一个无符号16位整数类型的变量 listen_port,值设为 7878
    let listen_port: u16 = 7878;

    // 创建TCP监听器,绑定本地所有IP地址与 listen_port 端口
    let tcp_listener = TcpListener::bind(format!("0.0.0.0:{}", listen_port))?;
    // 打印TCP监听器的本地地址信息
    println!("TCP listen:{}", tcp_listener.local_addr()?);

    // 创建UDP套接字,绑定本地所有IP地址与 listen_port 端口
    let udp_socket = UdpSocket::bind(format!("0.0.0.0:{}", listen_port))?;
    // 打印UDP套接字的本地地址信息
    println!("UDP listen:{}", udp_socket.local_addr()?);

    // 启动TCP服务器处理函数,创建一个新的线程
    let tcp_server = std::thread::spawn(move || {
        // 遍历TCP监听器接收到的所有连接请求
        for tcp_stream in tcp_listener.incoming() {
            // 根据连接结果匹配不同的操作
            match tcp_stream {
                // 如果连接成功,打印新连接的客户端地址信息,并在新线程中处理数据收发
                Ok(mut stream) => {
                    println!("tcp new connect:{:?}", stream.peer_addr());
                    thread::spawn(move || {
                        // 初始化一个长度为1024的字节缓冲区
                        let mut buffer = [0; 1024];
                        // 循环接收并处理数据
                        loop {
                            // 尝试读取数据到缓冲区
                            match stream.read(&mut buffer) {
                                // 如果读取到的数据量为0,则结束循环
                                Ok(0) => break,
                                // 如果读取成功,打印接收到的数据,并发送回应信息
                                Ok(n) => {
                                    println!("TCP recv:{:?}", &buffer[..n]);
                                    let _ = stream.write_all("tcp send ok".as_ref());
                                }
                                // 如果读取失败,打印错误信息并结束循环
                                Err(e) => {
                                    println!("TCP recv error:{}", e);
                                    break;
                                }
                            }
                        }
                    });
                }
                // 如果连接失败,打印错误信息
                Err(e) => println!("TCP connect error:{}", e),
            }
        }
    });
    // 启动UDP服务器处理函数,创建一个新的线程
    let udp_server = std::thread::spawn(move || {
        // 初始化一个长度为1024的字节缓冲区
        let mut buffer = [0; 1024];
        // 循环接收并处理数据
        loop {
            // 尝试接收数据及源地址信息
            match udp_socket.recv_from(&mut buffer) {
                // 如果接收成功,打印接收到的数据,并发送回应信息至源地址
                Ok((size, peer)) => {
                    println!("UDP recv:{:?}", &buffer[..size]);
                    let _ = udp_socket.send_to("udp send ok".as_ref(), &peer);
                }
                // 如果接收失败,打印错误信息
                Err(e) => println!("UDP recv error:{}", e),
            }
        }
    });

    // 等待TCP服务器线程执行完毕
    tcp_server.join().unwrap();
    // 等待UDP服务器线程执行完毕
    udp_server.join().unwrap();

    // 函数成功执行,返回 Ok(())
    Ok(())
}

3.增加线程池、以及锁处理

线程池确保了任务快速分配与执行,减少了资源浪费,而精细化的锁机制有效协调了多线程间的数据访问,避免了冲突,这样不仅大幅提高了系统吞吐量和响应速度,还稳固了系统在高压并发环境下的运行稳定性。

use std::net::{TcpListener, TcpStream, UdpSocket};
use std::io::{ErrorKind, Read, Write};
use std::sync::{Arc, Mutex};
use std::{env, thread};
use std::error::Error;
use std::sync::mpsc;

// 定义一个可以被发送并且执行的作业类型
type Job = Box<dyn FnOnce() + Send + 'static>;

// 定义一个 Worker 结构体,表示一个工作线程
struct Worker {
    id: usize,             // 工作线程的唯一标识符
    thread: thread::JoinHandle<()>, // 线程句柄,用于等待线程完成
}

// 实现 Worker 结构体的方法
impl Worker {
    // 构造一个新的 Worker,给定 ID 和接收作业的接收器
    fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {
        let thread = thread::spawn(move || {
            // 循环接收并执行作业,直到通道关闭
            while let Ok(job) = receiver.lock().unwrap().recv() {
                job();
            }
        });

        // 打印开始工作线程的信息
        println!("starting worker {:?}", &id);

        Worker { id, thread }
    }
}

// 定义一个线程池结构体,包含多个 Worker 和发送作业的发送器
struct ThreadPool {
    workers: Vec<Worker>, // 工作线程的向量
    sender: mpsc::Sender<Job>, // 发送作业的通道
}

// 实现 ThreadPool 结构体的方法
impl ThreadPool {
    // 构建一个新的线程池,给定线程数量
    fn new(size: usize) -> ThreadPool {
        assert!(size > 0); // 确保线程池大小大于零

        // 创建一个用于发送作业的通道
        let (sender, receiver) = mpsc::channel();
        let receiver = Arc::new(Mutex::new(receiver)); // 将接收器封装在 Arc 和 Mutex 中以允许多个 Worker 共享

        // 初始化工作线程向量
        let mut workers = Vec::with_capacity(size);

        // 循环创建指定数量的工作线程
        for id in 0..size {
            workers.push(Worker::new(id, Arc::clone(&receiver))); // 克隆 Arc 并创建新的 Worker
        }

        ThreadPool { workers, sender } // 返回新构建的线程池实例
    }

    // 执行一个作业,给定闭包函数作为作业
    fn execute<F>(&self, f: F)
    where
        F: FnOnce() + Send + 'static, // 确保闭包可以跨线程发送且生命周期足够长
    {
        let job = Box::new(f); // 将闭包包装成 Job 类型
        if let Err(e) = self.sender.send(job) { // 尝试通过通道发送作业
            eprintln!("Failed to send job to thread pool: {}", e); // 如果失败,则打印错误信息
        }
    }
}

// 启动 TCP 服务器,监听指定端口并处理连接
fn start_tcp_server(port: u16, pool: Arc<ThreadPool>) {
    // 尝试绑定 TCP 监听器到指定端口
    match TcpListener::bind(format!("0.0.0.0:{}", port)) {
        Ok(listener) => {
            println!("TCP Server listening on port {}", port); // 打印监听信息

            // 循环接收并处理每个传入的连接
            for stream in listener.incoming() {
                match stream {
                    Ok(stream) => {
                        // 使用线程池执行处理 TCP 连接的作业
                        pool.execute(move || {
                            if let Err(e) = handle_tcp_connection(stream) {
                                eprintln!("Error handling TCP connection: {}", e);
                            }
                        });
                    }
                    Err(e) => eprintln!("Error accepting TCP connection: {}", e), // 打印连接错误信息
                }
            }
        }
        Err(e) => {
            eprintln!("Failed to bind TCP port: {}", e); // 打印绑定端口失败信息
            if e.kind() == ErrorKind::AddrInUse {
                eprintln!("Port {} is already in use.", port); // 特别处理端口已被使用的情况
            }
        }
    }
}

// 启动 UDP 服务器,监听指定端口并处理数据包
fn start_udp_server(port: u16, pool: Arc<ThreadPool>) {
    // 尝试绑定 UDP 套接字到指定端口
    match UdpSocket::bind(format!("0.0.0.0:{}", port)) {
        Ok(socket) => {
            println!("UDP Server listening on port {}", port); // 打印监听信息

            let mut buf = [0; 1024]; // 初始化缓冲区

            // 循环接收和处理数据包
            loop {
                match socket.recv_from(&mut buf) {
                    Ok((bytes_read, src)) => {
                        // 复制有效数据到缓冲区前端,清空未使用的部分
                        buf.copy_within(0..bytes_read, 0);
                        buf[bytes_read..].fill(0);

                        // 使用线程池执行处理 UDP 数据包的作业
                        pool.execute(move || {
                            if let Err(e) = handle_udp_packet(&buf, &src) {
                                eprintln!("Error handling UDP packet: {}", e);
                            }
                        });
                    }
                    Err(e) => eprintln!("UDP error: {}", e), // 打印 UDP 错误信息
                }
            }
        }
        Err(e) => {
            eprintln!("Failed to bind UDP port: {}", e); // 打印绑定端口失败信息
            if e.kind() == ErrorKind::AddrInUse {
                eprintln!("Port {} is already in use.", port); // 特别处理端口已被使用的情况
            }
        }
    }
}

// 主函数:初始化服务器
fn main() {
    let mut port: u16 = 7878; // 默认监听端口

    // 从命令行参数中获取端口号,如果存在则更新默认值
    let args: Vec<String> = env::args().collect();
    if args.len() > 1 {
        port = match args[1].parse::<u16>() {
            Ok(num) => num,
            Err(_) => {
                eprintln!("Error: Port must be a number."); // 如果端口号无效,打印错误信息并退出程序
                std::process::exit(1);
            }
        };
    }

    // 创建一个包含四个工作线程的线程池
    let pool = ThreadPool::new(4);
    let pool_arc = Arc::new(pool); // 将线程池封装在 Arc 中以允许多个引用共享

    // 启动 TCP 服务器
    thread::spawn({
        let pool_clone = Arc::clone(&pool_arc);
        move || {
            start_tcp_server(port, pool_clone);
        }
    });

    // 启动 UDP 服务器
    let pool_clone = Arc::clone(&pool_arc);
    start_udp_server(port, pool_clone);
}

// 处理 TCP 连接的函数
fn handle_tcp_connection(mut stream: TcpStream) -> Result<(), Box<dyn Error>> {
    let mut buffer = [0; 1024]; // 初始化缓冲区

    // 设置流为非阻塞模式
    stream.set_nonblocking(true)?;

    // 循环读取和响应数据
    loop {
        match stream.read(&mut buffer) {
            Ok(bytes_read) if bytes_read > 0 => {
                // 解析请求并打印
                let request = String::from_utf8_lossy(&buffer[..bytes_read]);
                println!("Received TCP: {}", request);

                // 构建响应并发送
                let response = format!("Hello, you sent: {}", request.trim_end());
                if let Err(e) = stream.write_all(response.as_bytes()) {
                    eprintln!("Failed to write to TCP stream: {}", e);
                    return Err(Box::new(e)); // 如果写入失败,返回错误
                }
            }
            Ok(_) => break, // 如果读取结果为零,表示连接已关闭
            Err(ref e) if e.kind() == ErrorKind::WouldBlock => continue, // 如果错误为会阻塞,则继续循环
            Err(e) => {
                eprintln!("TCP read error: {}", e);
                return Err(Box::new(e)); // 如果发生其他错误,返回错误
            }
        }
    }

    Ok(()) // 成功处理连接
}

// 处理 UDP 数据包的函数
fn handle_udp_packet(buf: &[u8], src: &std::net::SocketAddr) -> Result<(), Box<dyn Error>> {
    // 解析数据包并打印信息
    let message = String::from_utf8_lossy(buf);
    println!("Received UDP from {}: {}", src, message);
    Ok(()) // 成功处理数据包
}

3.异步编程以及多路复用

Tokio作为Rust语言的异步运行库,极大提升了高并发系统的性能与效率。它通过异步I/O处理,使单个线程能高效管理大量并发连接,减少了资源消耗和延迟,提升了系统的可扩展性和响应速度。

Tokio 是 Rust 中的异步编程框架,它将复杂的异步编程抽象为 Futures、Tasks 和 Executor,并提供了 Timer 等基础设施。Tokio 快速、可靠,且可扩展。

Tokio 是一个事件驱动的非阻塞 I/O 平台,用于使用 Rust 编程语言编写异步应用。在高层设计上,它提供了一些主要组件:

  • 多线程、工作窃取(work-stealing)的 task scheduler 。
  • 由操作系统的事件队列(epoll,kqueue,IOCP 等)支撑的 reactor 。
  • 异步 TCPUDP套接字。
// 引入 Tokio 库中的网络模块,用于异步 TCP 和 UDP 操作
use tokio::net::{TcpListener, TcpStream, UdpSocket};
// 引入 Tokio 的读写操作扩展,用于异步 I/O
use tokio::io::{AsyncReadExt, AsyncWriteExt};
// 引入标准库错误模块,用于错误处理
use std::error::Error;
// 引入标准库网络模块,用于 Socket 地址类型
use std::net::SocketAddr;

// 定义主函数,使用 Tokio 运行时
#[tokio::main]
pub(crate) async fn run_server() {
    // 解析并创建服务器地址
    let addr = "0.0.0.0:7878".parse::<SocketAddr>().unwrap();

    // 创建TCP监听器,绑定到指定地址
    let tcp_listener = TcpListener::bind(&addr).await.unwrap();
    // 输出TCP监听信息
    println!("TCP Server listening on {}", addr);

    // 创建UDP监听器,绑定到相同地址
    let udp_socket = UdpSocket::bind(&addr).await.unwrap();
    // 输出UDP监听信息
    println!("UDP Server listening on {}", addr);

    // 使用 Tokio spawn 一个新的任务,启动UDP服务器
    tokio::spawn(async move {
        // 初始化接收缓冲区
        let mut buf = vec![0; 1024];
        loop {
            // 异步接收来自 UDP socket 的数据包
            match udp_socket.recv_from(&mut buf).await {
                Ok((num, src)) => {
                    // 将接收到的数据转换为 UTF-8 字符串并输出
                    let data = String::from_utf8_lossy(&buf[..num]).into_owned();
                    println!("Received UDP from {}: {}", src, data);
                },
                Err(e) => println!("UDP error: {}", e), // 处理 UDP 错误
            }
        }
    });

    // 循环接受并处理TCP连接
    while let Ok((stream, _)) = tcp_listener.accept().await {
        // 使用 Tokio spawn 一个新的任务,处理每个TCP连接
        tokio::spawn(async move {
            // 如果处理TCP连接时出现错误,则输出错误信息
            if let Err(e) = handle_tcp_connection(stream).await {
                println!("Error handling TCP connection: {}", e);
            }
        });
    }
}

// 定义一个异步函数,用于处理单个TCP连接
async fn handle_tcp_connection(mut stream: TcpStream) -> Result<(), Box<dyn Error>> {
    // 初始化读取缓冲区
    let mut buffer = [0; 1024];

    loop {
        // 异步读取来自TCP流的数据
        let bytes = match stream.read(&mut buffer).await {
            Ok(n) if n > 0 => n, // 成功读取字节
            Ok(_) => break, // 连接关闭
            Err(e) => { // 处理读取错误
                println!("TCP read error: {}", e);
                return Err(e.into()); // 返回错误
            }
        };

        // 将读取的字节转换为UTF-8字符串并输出
        let request = String::from_utf8_lossy(&buffer[..bytes]);
        println!("Received TCP: {}", request);

        // 构建响应消息
        let response = format!("Hello, you sent: {}", request.trim_end());
        // 异步写入响应到TCP流
        if let Err(e) = stream.write_all(response.as_bytes()).await {
            // 如果写入失败,输出错误信息并返回错误
            println!("Failed to write to TCP stream: {}", e);
            return Err(e.into());
        }
    }

    // 成功处理TCP连接
    Ok(())
}