鉴于GB/T 28181协议要求同时监听TCP/UDP的特性,为确保服务端的高性能与高容错,编程语言选择需侧重于:
- 高性能处理并发与I/O密集型任务。
- 内置并发模型,如异步IO、事件驱动,优化资源利用。
- 强大的网络编程支持,简化TCP/UDP协议实现。
- 健壮的错误处理,确保稳定性和容错恢复能力。
- 跨平台能力,适应多样化部署环境。
因此,倾向于选用如
Rust
、Go
、C++
这类现代语言,它们在满足上述条件的同时,也促进了开发效率与系统稳定性,本篇我们选择了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 。
- 异步
TCP
和UDP
套接字。
// 引入 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(())
}