用 Rust 搭建一个优雅的多线程服务器:从零开始的详细指南
嘿,小伙伴们!今天咱们来聊聊怎么用 Rust 搭建一个牛气哄哄的多线程服务器,还能在需要的时候优雅地关机。为啥要用 Rust 呢?因为 Rust 是个超级靠谱的语言,它能保证内存安全,写并发代码的时候不用担心那些让人头疼的并发问题,比如数据竞争和死锁。而且,Rust 的标准库提供了超好用的网络编程工具,能帮咱们轻松搞定网络服务。
一、先搞个线程池
线程池这东西,就像是个“任务分配中心”,能帮咱们把任务合理分配给线程,避免线程频繁创建和销毁,节省资源,提升性能。想象一下,如果你每次有任务就新建一个线程,那线程创建和销毁的开销会让你的程序慢得像蜗牛。有了线程池,这些线程就像是一群随时待命的工人,任务一来,立马就能开工。
下面就是个简单的线程池代码,瞅瞅:
use std::sync::{mpsc, Arc, Mutex};
use std::thread;pub struct ThreadPool {workers: Vec<Worker>,sender: Option<mpsc::Sender<Job>>,
}type Job = Box<dyn FnOnce() + Send + 'static>;impl ThreadPool {pub fn new(size: usize) -> ThreadPool {assert!(size > 0);let (sender, receiver) = mpsc::channel();let receiver = Arc::new(Mutex::new(receiver));let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, Arc::clone(&receiver)));}ThreadPool {workers,sender: Some(sender),}}pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static,{let job = Box::new(f);self.sender.as_ref().unwrap().send(job).unwrap();}
}impl Drop for ThreadPool {fn drop(&mut self) {drop(self.sender.take());for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);if let Some(thread) = worker.thread.take() {thread.join().unwrap();}}}
}struct Worker {id: usize,thread: Option<thread::JoinHandle<()>>,
}impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || loop {let message = receiver.lock().unwrap().recv();match message {Ok(job) => {println!("Worker {id} got a job; executing.");job();}Err(_) => {println!("Worker {id} disconnected; shutting down.");break;}}});Worker {id,thread: Some(thread),}}
}
重点来了
- 创建线程:用
mpsc::channel
搭个通道,让主线程和工作线程能通信。工作线程就等着通道里有任务过来,接收到就执行。这就像是有个传送带,任务一过来,工人(线程)就拿起来干活。 - 分配任务:
execute
方法把任务扔进通道,工作线程就能拿到任务干活了。这就像是你把任务扔到传送带上,工人会自己去拿。 - 线程管理:服务器要关机的时候,
Drop
实现能保证所有工作线程都乖乖退出,不会留下“孤儿线程”。这就像是下班的时候,你得确保所有工人都安全离开,不会有人被锁在工厂里。
二、监听网络连接
Rust 里监听网络连接有俩常用方法:listener.incoming()
和 listener.accept()
。这俩有啥区别呢?想象一下,你开了一家餐厅,listener.incoming()
就像是门口的迎宾,一直站在那里迎接客人,每次有客人进来就通知后厨;而 listener.accept()
就像是你每次只接待一个客人,等这个客人坐下后,再去门口看看有没有下一个客人。
listener.incoming()
这哥们儿是个迭代器,会一直监听新的连接,每次迭代返回一个 Result<TcpStream, std::io::Error>
。用起来就像这样:
for stream in listener.incoming() {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});
}
listener.accept()
这哥们儿每次调用会阻塞当前线程,直到有新的连接过来,返回一个 Result<(TcpStream, SocketAddr), std::io::Error>
。用起来是这样:
loop {match listener.accept() {Ok((stream, _)) => {pool.execute(|| {handle_connection(stream);});}Err(e) => {eprintln!("Failed to accept connection: {:?}", e);}}
}
选谁好呢?
listener.incoming()
:适合需要持续监听多个连接的情况,用在循环里很顺手。这就像是餐厅门口的迎宾,能同时处理多个客人的到来。listener.accept()
:适合每次只处理一个连接的情况,用在同步逻辑里很方便。这就像是你每次只接待一个客人,处理完再去接待下一个。
三、优雅关机
服务器跑着跑着,说不定会收到外部信号,比如 Ctrl+C 或 SIGTERM
,这时候咱得让它能优雅地关机,而不是直接“死机”。这咋整呢?用 signal-hook
库来捕获信号呗!这就像是你给服务器设置了一个“紧急停止”按钮,按下按钮的时候,服务器能安全地关闭,而不是直接断电。
use signal_hook::iterator::Signals;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();let pool = ThreadPool::new(4);// 搞个原子布尔变量,控制服务器要不要关机let running = Arc::new(AtomicBool::new(true));let r = running.clone();// 捕获信号let signals = Signals::new(&[signal_hook::SIGINT, signal_hook::SIGTERM]).unwrap();thread::spawn(move || {for _ in signals.forever() {println!("Received termination signal, shutting down...");r.store(false, Ordering::SeqCst);}});// 主循环for stream in listener.incoming() {if !running.load(Ordering::SeqCst) {println!("Shutting down.");break;}let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}
重点来了
- 捕获信号:用
signal-hook
捕获SIGINT
和SIGTERM
信号。这就像是给服务器装了个“耳朵”,能听到外部的“关机”信号。 - 原子布尔变量:用
AtomicBool
来控制服务器要不要继续跑。这就像是服务器的“心跳”,一旦“心跳”停止,服务器就知道该关机了。 - 主循环检查:每次迭代都瞅瞅
running
的值,要是false
,就退出循环关机。这就像是你每次接待客人的时候,都会看看“紧急停止”按钮有没有被按下。
四、处理连接
服务器接收到连接后,得处理啊。下面是个简单的 handle_connection
函数,处理 HTTP 请求并返回响应:
fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();let response = "HTTP/1.1 200 OK\r\n\r\nHello, world!";stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}
重点来了
- 读取请求:从
TcpStream
里把客户端发的数据读出来。这就像是你从客人那里拿到订单。 - 构造响应:根据请求整一个简单的 HTTP 响应。这就像是你根据订单准备食物。
- 发送响应:把响应发回客户端。这就像是你把准备好的食物送到客人桌上。
五、总结
好啦,今天咱们用 Rust 搭了个多线程服务器,还学会了怎么让它能优雅地关机。关键点都给你唠清楚了:
- 线程池:用
mpsc::channel
和Arc<Mutex<...>>
搭个线程池,任务分配妥妥的。这就像是你有了一个高效的“任务分配中心”。 - 网络监听:用
listener.incoming()
或listener.accept()
监听网络连接,看场景选合适的。这就像是你选择是让迎宾接待客人,还是自己一个个接待。 - 信号处理:用
signal-hook
捕获信号,AtomicBool
控制服务器运行状态。这就像是你给服务器装了个“紧急停止”按钮。 - 连接处理:在工作线程里处理连接,读请求、整响应、发回去。这就像是你在餐厅里接待客人,处理订单,送食物。
有了这些知识,你就能搭一个高性能、靠谱的网络服务啦,还能在收到终止信号的时候优雅地关机。赶紧试试吧,有问题随时问我哦!
相关文章:
用 Rust 搭建一个优雅的多线程服务器:从零开始的详细指南
嘿,小伙伴们!今天咱们来聊聊怎么用 Rust 搭建一个牛气哄哄的多线程服务器,还能在需要的时候优雅地关机。为啥要用 Rust 呢?因为 Rust 是个超级靠谱的语言,它能保证内存安全,写并发代码的时候不用担心那些让…...
redis 数据结构-01( SET、GET、DEL)
使用 Redis 字符串:SET、GET、DEL Redis 字符串是用于存储和操作文本或二进制数据的基本数据类型。它们是 Redis 中最简单但功能最丰富的数据结构,可作为构建更复杂结构的基石。了解如何有效地使用字符串对于充分利用 Redis 的缓存、会话管理以及其他各…...

【Nacos】env NACOS_AUTH_TOKEN must be set with Base64 String.
【Nacos】env NACOS_AUTH_TOKEN must be set with Base64 String. 问题描述 env NACOS_AUTH_TOKEN must be set with Base64 String.原因分析 从错误日志中可以看出,Nacos 启动失败的原因是缺少必要的环境变量 NACOS_AUTH_TOKEN。 NACOS_AUTH_TOKEN: Nacos 用于生…...

秋招准备——2.跨时钟相关
格雷码异步FIFO跨时钟域处理 格雷码 一、格雷码规律 相邻性:相邻两个数的格雷码只有一位不同,例如: 0000 → 0001(仅最低位变化)0001 → 0011(仅次低位变化)0011 → 0010(仅最低位…...

激光打印机常见打印故障简单处理意见
一、 问题描述: 给打印机更换新的硒鼓时拉开硒鼓封条时有微量碳粉带出; 原因: 出厂打印测试时,可能会有微量碳粉在磁辊上或者磁辊仓; 解决方法: 擦干净即可正常使用; 二、 问题描述&…...
语言学中的对象语言与元语言 | 概念 / 区别 / 实例分析
注:英文引文,机翻未校。 语言学中的“对象语言”和“元语言” 刘福长 现代外语 1989年第3期(总第45期) 在阅读语言学著作时,我们有时会遇到这样两个术语:对象语言(object language࿰…...

【2025最新】Windows系统装VSCode搭建C/C++开发环境(附带所有安装包)
文章目录 为什么选择VSCode作为C/C开发工具?一、VSCode安装过程(超简单!)二、VSCode中文界面设置(再也不用对着英文发愁!)三、安装C/C插件(编程必备神器!)四、…...

MYSQL 查询去除小数位后多余的0
MYSQL 查询去除小数位后多余的0 在MySQL中,有时候我们需要去除存储在数据库中的数字字段小数点后面多余的0。这种情况通常发生在处理金额或其他需要精确小数位的数据时。例如,数据库中存储的是decimal (18,6)类型的数据,但在页面展示时不希望…...

基于GF域的多进制QC-LDPC误码率matlab仿真,译码采用EMS算法
目录 1.算法仿真效果 2.算法涉及理论知识概要 3.MATLAB核心程序 4.完整算法代码文件获得 1.算法仿真效果 matlab2022a仿真结果如下(完整代码运行后无水印): 本课题实现的是四进制QC-LDPC 仿真操作步骤可参考程序配套的操作视频。 2.算…...

Vitrualbox完美显示系统界面(只需三步)
目录 1.使用vitrualbox的增强功能:编辑 2.安装增强功能(安装完后要重启虚拟机): 3. 调整界面尺寸(如果一个选项不行的话,就多试试其他不同的百分比): 先看看原来的,…...

王炸组合!STL-VMD二次分解 + Informer-LSTM 并行预测模型
往期精彩内容: 单步预测-风速预测模型代码全家桶-CSDN博客 半天入门!锂电池剩余寿命预测(Python)-CSDN博客 超强预测模型:二次分解-组合预测-CSDN博客 VMD CEEMDAN 二次分解,BiLSTM-Attention预测模型…...

n8n 修改或者智能体用文档知识库创建pdf
以下是对 Nextcloud、OnlyOffice、Seafile、Etherpad、BookStack 和 Confluence 等本地部署文档协作工具的综合评测、对比分析和使用推荐,帮助您根据不同需求选择合适的解决方案。 🧰 工具功能对比 工具名称核心功能本地部署支持适用场景优势与劣势Next…...

论坛系统(中-1)
软件开发 编写公共代码 定义状态码 对执⾏业务处理逻辑过程中可能出现的成功与失败状态做针对性描述(根据需求分析阶段可以遇见的问题提前做出定义),⽤枚举定义状态码,先定义⼀部分,业务中遇到新的问题再添加 定义状态码如下 状态码类型描…...

FPGA+ESP32 = GameBoy 是你的童年吗?
之前介绍的所有的复古游戏机都是基于Intel-Altera FPGA制作的,今天就带来一款基于AMD-Xilinx FPGA的复古掌上游戏机-Game Bub。 Game Bub是一款掌上游戏机,旨在畅玩 Game Boy、Game Boy Color 和 Game Boy Advance 游戏。与大多数现代掌上游戏机一样&…...

3D迷宫探险:伪3D渲染与运动控制的数学重构
目录 3D迷宫探险:伪3D渲染与运动控制的数学重构引言第一章 伪3D渲染引擎1.1 射线投射原理1.2 纹理透视校正第二章 迷宫生成算法2.1 图论生成模型2.2 复杂度控制第三章 第一人称控制3.1 运动微分方程3.2 鼠标视角控制第四章 碰撞检测优化4.1 层级检测体系4.2 滑动响应算法第五章…...

【金仓数据库征文】_金仓数据库在金融行业的两地三中心容灾架构实践
金仓数据库在金融行业的两地三中心容灾架构实践 🌟嗨,我是LucianaiB! 🌍 总有人间一两风,填我十万八千梦。 🚀 路漫漫其修远兮,吾将上下而求索。 引言 随着国家对信息技术应用创新࿰…...

Python作业练习3
任务简述 字符田字格绘制 代码实现 def print_tianzige():for i in range(11):if i in [0, 5, 10]:print("" "-----" * 2)else:print("|" " |" * 2)print_tianzige() 结果展示...

十五种光电器件综合对比——《器件手册--光电器件》
十五、光电器件 名称 原理 特点 应用 发光二极管(LED) 基于半导体材料的电致发光效应,当电流通过时,电子与空穴复合,释放出光子。 高效、节能、寿命长、响应速度快、体积小。 广泛用于指示灯、照明、显示&#…...

【计算机视觉】OpenCV项目实战:基于face_recognition库的实时人脸识别系统深度解析
基于face_recognition库的实时人脸识别系统深度解析 1. 项目概述2. 技术原理与算法设计2.1 人脸检测模块2.2 特征编码2.3 相似度计算 3. 实战部署指南3.1 环境配置3.2 数据准备3.3 实时识别流程 4. 常见问题与解决方案4.1 dlib安装失败4.2 人脸检测性能差4.3 误识别率高 5. 关键…...
Python面向对象编程(OOP)深度解析:从封装到继承的多维度实践
引言 面向对象编程(Object-Oriented Programming, OOP)是Python开发中的核心范式,其三大特性——封装、继承、多态——为构建模块化、可维护的代码提供了坚实基础。本文将通过代码实例与理论结合的方式,系统解析Python OOP的实现机制与高级特性…...
自我奖励语言模型:突破人类反馈瓶颈
核心思想 自我奖励语言模型提出了一种全新的语言模型对齐范式。传统方法如RLHF或DPO依赖人类反馈数据训练固定的奖励模型,这使模型的能力受限于人类标注数据的质量和数量。论文作者认为,要实现超人类能力的AI代理,未来的模型需要突破人类反馈…...

游戏资源传输服务器
目录 项目简介项目实现nginx配置服务器逻辑图 项目代码简介reactor 模型部分文件传输部分 项目演示视频演示演示分析 项目简介 使用C开发,其中资源存储在fastdfs 中,用户通过http上传或下载资源文件,此项目需要开启nginx中的nginx-upload-mod…...

2025-5-13渗透测试:CVE-2021-42278 和日志分析,NTLM 协议和PTH (Pass-the-Hash) Relay 捕获 Hash
CVE-2021-42278/42287 漏洞利用 漏洞原理 42278:通过修改计算机账户的 sAMAccountName(如去掉 $),伪装成域控制器(DC)名称,欺骗KDC生成高权限TGT。42287:KDC在验证TGT时若找不到匹配…...

基于深度学习的水果识别系统设计
一、选择YOLOv5s模型 YOLOv5:YOLOv5 是一个轻量级的目标检测模型,它在 YOLOv4 的基础上进行了进一步优化,使其在保持较高检测精度的同时,具有更快的推理速度。YOLOv5 的网络结构更加灵活,可以根据不同的需求选择不同大…...

C——五子棋小游戏
前言 五子棋,又称连珠棋,是一种双人对弈的棋类游戏。游戏目标是在一个棋盘上,通过在横、竖、斜线上依次放置棋子,使自己的五个棋子连成一线,即横线、竖线或斜线,且无被对手堵住的空位,从而获胜…...

【线段树】P9349 [JOI 2023 Final] Stone Arranging 2|普及+
本文涉及知识点 C线段树 P9349 [JOI 2023 Final] Stone Arranging 2 题目描述 JOI-kun has N N N go stones. The stones are numbered from 1 1 1 to N N N. The color of each stone is an integer between 1 1 1 and 1 0 9 10^9 109, inclusive. In the beginning,…...
分别在windows和linux上使用curl,有啥区别?
作为开发者常用的网络工具,curl 在 Windows 和 Linux 上的使用看似相似,但实际存在不少细节差异。以下从 命令语法、环境特性、功能支持 和 开发体验 四个角度展开对比,帮助读者避免跨平台开发时的常见“坑”。 一、命令语法差异:…...

CodeBuddy终极测评:中国版Cursor的开发革命(含安装指南+HTML游戏实战)
一、腾讯云CodeBuddy产品全景解读 1. 什么是腾讯云代码助手? 官方定义: Tencent Cloud CodeBuddy是由腾讯自研的AI编程辅助工具,基于混元大模型DeepSeek双引擎,提供: ✅ 智能代码补全(支持200语言&#x…...

从数据中台到数据飞轮:实现数据驱动的升级之路
从数据中台到数据飞轮:实现数据驱动的升级之路 随着数字化转型的推进,数据已经成为企业最重要的资产之一,企业普遍搭建了数据中台,用于整合、管理和共享数据;然而,近年来,数据中台的风潮逐渐减退…...
机器学习第八讲:向量/矩阵 → 数据表格的数学表达,如Excel表格转数字阵列
机器学习第八讲:向量/矩阵 → 数据表格的数学表达,如Excel表格转数字阵列 资料取自《零基础学机器学习》。 查看总目录:学习大纲 关于DeepSeek本地部署指南可以看下我之前写的文章:DeepSeek R1本地与线上满血版部署:…...