rust学习-构建服务器
单线程server
服务器会依次处理每一个请求,在完成第一个连接的处理之前不会处理第二个连接
// cat main.rs
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {// stream 此时类型:core::result::Result<std::net::tcp::TcpStream, std::io::error::Error>// unwrap() 要么取出Result的Some中的值,要么Paniclet stream = stream.unwrap();// stream 此时类型:std::net::tcp::TcpStreamhandle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 1024]; // [dataType;size],为每一个元素初始化为0stream.read(&mut buffer).unwrap();// 浏览器输入得到内容如下// Request: GET /favicon.ico HTTP/1.1// Host: 127.0.0.1:7878// Connection: keep-alive// sec-ch-ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114"// sec-ch-ua-mobile: ?0// User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36// sec-ch-ua-platform: "macOS"// Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8// Sec-Fetch-Site: same-origin// Sec-Fetch-Mode: no-cors// Sec-Fetch-Dest: image// Referer: http://127.0.0.1:7878/// Accept-Encoding: gzip, deflate, br// Accept-Language: zh-CN,zh;q=0.9println!("Request: {}", String::from_utf8_lossy(&buffer[..]));// 一个微型的成功 HTTP 响应,格式如下// HTTP-Version Status-Code Reason-Phrase CRLF// headers CRLF// message-body// let response = "HTTP/1.1 200 OK\r\n\r\n";// 给前端传输htmllet contents = fs::read_to_string("hello.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",contents.len(),contents);// flush 会等待并阻塞程序执行直到所有字节都被写入连接中// TcpStream 包含一个内部缓冲区来最小化对底层操作系统的调用stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}// 客户端的请求行
// Method Request-URI HTTP-Version CRLF
// headers CRLF
// message-body
// hello.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>Hello!</title></head><body><h1>Hello!</h1><p>Hi from Rust</p></body>
</html>
区分响应
// cat main.rs
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();println!("Request: {}", String::from_utf8_lossy(&buffer[..]));// 将与 / 请求相关的数据硬编码进变量 get// 数据开头增加 b"" 字节字符串语法将其转换为字节字符串let get = b"GET / HTTP/1.1\r\n";// 检查 buffer 是否以 get 中的字节开头。如果是,这就是一个格式良好的 / 请求if buffer.starts_with(get) {let contents = fs::read_to_string("hello.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {}\r\n\r\n{}",contents.len(),contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();} else {// 如果 buffer 不 以 get 中的字节开头,返回404let status_line = "HTTP/1.1 404 NOT FOUND";let contents = fs::read_to_string("404.html").unwrap();let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",status_line,contents.len(),contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();}
}
404.html
<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8"><title>Hello!</title></head><body><h1>Oops!</h1><p>Sorry, I don't know what you're asking for.</p></body>
重构 handle_connection
fn handle_connection(mut stream: TcpStream) {// --snip--let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK", "hello.html")} else {("HTTP/1.1 404 NOT FOUND", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",status_line,contents.len(),contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}
单线程
use std::thread;
use std::time::Duration;
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();handle_connection(stream);}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();println!("Request: {}", String::from_utf8_lossy(&buffer[..]));let get = b"GET / HTTP/1.1\r\n";let sleep = b"GET /sleep HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK", "hello.html")} else if buffer.starts_with(sleep) {thread::sleep(Duration::from_secs(5));("HTTP/1.1 200 OK", "hello.html")} else {("HTTP/1.1 404 NOT FOUND", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",status_line,contents.len(),contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}
多线程
奔溃版本1
fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();for stream in listener.incoming() {let stream = stream.unwrap();thread::spawn(|| {handle_connection(stream);});}
}
// thread::spawn的类型
// F是参数类型,T是返回类型
// F 的 trait 有 (FnOnce() -> T)、Send,生命周期有 'static
// 处理请求的线程只会执行闭包一次,所以用FnOnce
// 需要 Send 来将闭包从一个线程转移到另一个线程
// 'static 是因为并不知道线程会执行多久
pub fn spawn<F, T>(f: F) -> JoinHandle<T>whereF: FnOnce() -> T + Send + 'static,T: Send + 'static
根据奔溃版本和thread::spawn构造假想线程池
fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();let pool = ThreadPool::new(4);for stream in listener.incoming() {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}
}
impl ThreadPool {// --snip--pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static{}
}
构造线程池-糟糕版本
thread::spawn,期望获取一些一旦创建线程就应该执行的代码
但是对于线程池不适用,线程池是当在需要时才执行代码执行
(1)定义 Worker 结构体存放 id 和 JoinHandle<()>
(2)修改 ThreadPool 存放一个 Worker 实例的 vector
(3)定义 Worker::new 函数,它获取一个 id 数字并返回一个带有 id 和用空闭包分配的线程的 Worker 实例
(4)在 ThreadPool::new 中,使用 for 循环计数生成 id,使用这个 id 新建 Worker,并储存进 vector 中
execute 将通过 ThreadPool ,向其中空闲的线程 Worker 实例发送任务。
(1)ThreadPool 会创建一个通道并充当发送端。
(2)每个 Worker 将会充当通道的接收端。
(3)新建一个 Job 结构体来存放用于向通道中发送的闭包。
(4)execute 方法会在通道发送端发出期望执行的任务。
(5)在线程中,Worker 会遍历通道的接收端并执行任何接收到的任务。
use std::thread;
use std::sync::mpsc;pub struct ThreadPool {workers: Vec<Worker>,// threads: Vec<thread::JoinHandle<()>>,sender: mpsc::Sender<Job>,
}struct Job;impl ThreadPool {/// 创建线程池。////// 线程池中线程的数量。////// # Panics////// `new` 函数在 size 为 0 时会 panic。pub fn new(size: usize) -> ThreadPool {// 创建一个没有任何线程的线程池应该是不可恢复的错误assert!(size > 0);/*// with_capacity, 与 Vec::new 做了同样的工作// 它为 vector 预先分配空间// 预分配比 Vec::new 要稍微有效率let mut threads = Vec::with_capacity(size);for _ in 0..size {// create some threads and store them in the vector}ThreadPool {threads}*//*let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id));}ThreadPool {workers}*/// 创建通道并让 ThreadPool 实例充当发送端// Job是通道的待执行任务let (sender, receiver) = mpsc::channel();let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id, receiver));}ThreadPool {workers,sender,}}// 在 execute 方法中获得期望执行的闭包pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static{}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}/*
impl Worker {fn new(id: usize) -> Worker {// 一个空闭包let thread = thread::spawn(|| {});Worker {id,thread,}}
}
*/impl Worker {// Rust 所提供的通道实现是多生产者,单消费者// 将通道的接收端传递给 workerfn new(id: usize, receiver: mpsc::Receiver<Job>) -> Worker {let thread = thread::spawn(|| {receiver;});Worker {id,thread,}}
}
终极版本
// cat lib.rs
use std::thread;
use std::sync::mpsc;
use std::sync::Arc;
use std::sync::Mutex;pub struct ThreadPool {workers: Vec<Worker>,// threads: Vec<thread::JoinHandle<()>>,sender: mpsc::Sender<Job>,
}type Job = Box<dyn FnOnce() + Send + 'static>;impl ThreadPool {/// 创建线程池。////// 线程池中线程的数量。////// # Panics////// `new` 函数在 size 为 0 时会 panic。pub fn new(size: usize) -> ThreadPool {// 创建一个没有任何线程的线程池应该是不可恢复的错误assert!(size > 0);/*// with_capacity, 与 Vec::new 做了同样的工作// 它为 vector 预先分配空间// 预分配比 Vec::new 要稍微有效率let mut threads = Vec::with_capacity(size);for _ in 0..size {// create some threads and store them in the vector}ThreadPool {threads}*//*let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id));}ThreadPool {workers}*/// 创建通道并让 ThreadPool 实例充当发送端// Job是通道的待执行任务/*let (sender, receiver) = mpsc::channel();let mut workers = Vec::with_capacity(size);for id in 0..size {workers.push(Worker::new(id));}ThreadPool {workers,sender,}*/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,}}// 在 execute 方法中获得期望执行的闭包pub fn execute<F>(&self, f: F)whereF: FnOnce() + Send + 'static{let job = Box::new(f);self.sender.send(job).unwrap();}
}struct Worker {id: usize,thread: thread::JoinHandle<()>,
}/*
impl Worker {fn new(id: usize) -> Worker {// 一个空闭包let thread = thread::spawn(|| {});Worker {id,thread,}}
}
*/// Rust 所提供的通道实现是多生产者,单消费者
// 将通道的接收端传递给 worker
impl Worker {fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {let thread = thread::spawn(move || {loop {let job = receiver.lock().unwrap().recv().unwrap();println!("Worker {} got a job; executing.", id);job();}});Worker {id,thread,}}
}
// cat main.rs
use std::thread;
use std::time::Duration;
use std::fs;
use std::io::prelude::*;
use std::net::TcpListener;
use std::net::TcpStream;
use rust_demo::ThreadPool;fn main() {let listener = TcpListener::bind("127.0.0.1:7878").unwrap();let pool = ThreadPool::new(4);for stream in listener.incoming() {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}
}fn handle_connection(mut stream: TcpStream) {let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();println!("Request: {}", String::from_utf8_lossy(&buffer[..]));let get = b"GET / HTTP/1.1\r\n";let sleep = b"GET /sleep HTTP/1.1\r\n";let (status_line, filename) = if buffer.starts_with(get) {("HTTP/1.1 200 OK", "hello.html")} else if buffer.starts_with(sleep) {thread::sleep(Duration::from_secs(5));("HTTP/1.1 200 OK", "hello.html")} else {("HTTP/1.1 404 NOT FOUND", "404.html")};let contents = fs::read_to_string(filename).unwrap();let response = format!("{}\r\nContent-Length: {}\r\n\r\n{}",status_line,contents.len(),contents);stream.write(response.as_bytes()).unwrap();stream.flush().unwrap();
}
相关文章:
rust学习-构建服务器
单线程server 服务器会依次处理每一个请求,在完成第一个连接的处理之前不会处理第二个连接 // cat main.rs use std::io::prelude::*; use std::net::TcpListener; use std::net::TcpStream;fn main() {let listener TcpListener::bind("127.0.0.1:7878&quo…...
Java并发----进程、线程、并行、并发
一、进程与线程 进程 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至 CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的 当一个程序被运行…...
【计算机网络】第 4 课 - 物理层
欢迎来到博主 Apeiron 的博客,祝您旅程愉快 ! 时止则止,时行则行。动静不失其时,其道光明。 目录 1、物理层的基本概念 2、物理层协议的主要任务 3、物理层任务 4、总结 1、物理层的基本概念 在计算机网络中,用来…...
深入理解MVVM架构模式
MVVM原理 MVVM是一种用于构建用户界面的软件架构模式,它的名称代表着三个组成部分:Model(模型)、View(视图)和ViewModel(视图模型)。MVVM的主要目标是将应用程序的UI与其底层数据模…...
配置IPv6 over IPv4手动隧道示例
组网需求 如图1所示,两台IPv6主机分别通过SwitchA和SwitchC与IPv4骨干网络连接,客户希望两台IPv6主机能通过IPv4骨干网互通。 图1 配置IPv6 over IPv4手动隧道组网图 配置思路 配置IPv6 over IPv4手动隧道的思路如下: 配置IPv4网络。配置接…...
Vue3--->组合式API与Pinia
目录 使用create-vue搭建 1、使用create-vue创建项目 2、项目目录和关键文件 组合式API 1、组合式API - setup选项 2、组合式API - reactive和ref函数 3、组合式API - computed 4、组合式API - watch 1、基础使用 - 侦听单个数据 2、基础使用 - 侦听多个数据 3、immediate&…...
三言两语说透柯里化和反柯里化
JavaScript中的柯里化(Currying)和反柯里化(Uncurrying)是两种很有用的技术,可以帮助我们写出更加优雅、泛用的函数。本文将首先介绍柯里化的概念、实现原理和应用场景,然后介绍反柯里化的概念、实现原理和应用场景,通过大量的代码示例帮助读…...
细讲TCP三次握手四次挥手(四)
常见面试题 为什么TCP连接的时候是3次?2次不可以吗? 因为需要考虑连接时丢包的问题,如果只握手2次,第二次握手时如果服务端发给客户端的确认报文段丢失,此时服务端已经准备好了收发数(可以理解服务端已经连接成功)据…...
HarmonyOS/OpenHarmony元服务开发-配置卡片的配置文件
卡片相关的配置文件主要包含FormExtensionAbility的配置和卡片的配置两部分: 1.卡片需要在module.json5配置文件中的extensionAbilities标签下,配置FormExtensionAbility相关信息。FormExtensionAbility需要填写metadata元信息标签,其中键名称…...
mac安装nacos,M1芯片
第一步,官网下载 》nacos官网 去github中下载对应的版本,本人下载的是1.4.1版本 在这儿选择其他的版本,下面这里选择 tar.gz 压缩包 解压后放到一个非中文的目录下,我选择在 user目录下面创建一个other目录,将使用的环…...
老板说把跳针改过去,什么是主板跳针
最近骑车拍了很多视频,把电脑磁盘堆满了,想着买一条固态SSD卡扩展一下。 一咬牙一跺脚,直接安排,毫不犹豫。顺带加装了无限网卡和蓝牙5.2。 收到后立马安装。安装完发现识别不到新磁盘 确认安装没问题。然后就去问固态硬盘的客服 …...
PyTorch代码实战入门
人这辈子千万不要马虎两件事 一是找对爱人、二是选对事业 因为太阳升起时要投身事业 太阳落山时要与爱人相拥 一、准备数据集 蚂蚁蜜蜂数据集 蚂蚁蜜蜂的图片,文件名就是数据的label 二、使用Dataset加载数据 打开pycharm,选择Anaconda创建的pytorch环…...
TSINGSEE青犀视频汇聚平台EasyCVR多种视频流播放协议介绍
众所周知,TSINGSEE青犀视频汇聚平台EasyCVR可支持多协议方式接入,包括主流标准协议GB28181、RTSP/Onvif、RTMP等,以及厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。今天我们来说一说,EasyCVR平台支持分…...
Vivado进行自定义IP封装
一. 简介 本篇文章将介绍如何使用Vivado来对上篇文章(FPGA驱动SPI屏幕)中的代码进行一个IP封装,Vivado自带的IP核应该都使用过,非常方便。 这里将其封装成IP核的目的主要是为了后续项目的调用,否则当我新建一个项目的时候,我需要将…...
开放自动化软件的硬件平台
自动化行业的产品主要以嵌入式系统为主,历来对产品硬件的可靠性和性能都提出很高的要求。最典型的产品要数PLC。PLC 要求满足体积小,实时性,可靠性,可扩展性强,环境要求高等特点。它们通常采用工业级高性能嵌入式SoC 实…...
AdvancedInstaller打包程序
文章目录 1. AdvancedInstaller 下载2. AdvancedInstaller 启动3. 新建工程4. 配置安装包详细信息5. 配置安装参数6. 添加要打包的文件7. 设置安装完成后启动程序8. 构建打包 1. AdvancedInstaller 下载 下载网址:https://www.advancedinstaller.com/ 2. AdvancedIn…...
无穷限积分习题
前置知识:无穷限积分 习题1 计算 ∫ 1 ∞ ln x x 2 d x \int_1^{\infty}\dfrac{\ln x}{x^2}dx ∫1∞x2lnxdx 解: \qquad 原式 ( − ln x x ) ∣ 1 ∞ ∫ 1 ∞ 1 x 2 d x ( − ln x x ) ∣ 1 ∞ ( − 1 x ) ∣ 1 ∞ (-\dfrac{\…...
AI 3D结构光技术加持,小米引领智能门锁新标准
一直以来,小米智能门锁系列产品让更多家庭走进了安全便捷的智能生活,安全至上的设计让很多家庭都轻松告别了随身钥匙。 7月27日,小米正式推出小米智能门锁M20 Pro,再一次引领智能门锁产品的发展潮流。该款门锁采用AI 3D结构光技术…...
管理类联考——逻辑——形式逻辑——汇总篇
简述 形式逻辑: 识别题型:逻辑符号表达及标志词:联假言符号化特殊命题“除非否则”;五大关系:矛盾、等价、包含、至少有一真、至少有一假;【通过“关系”,串联起“假联选”言】 识别题型&…...
架构的分类
目录 一、 RUP41 架构 1.1 RUP41架构方法概述 1.2 RUP41架构总体 1.3 RUP41架构方法内容 1.3.1 逻辑视图 1.3.2 开发视图 1.3.3 物理视图 1.3.4 处理视图 1.3.5 场景视图 二、 TOGAF9 架构 2.1 TOGAF9 架构概述 2.2 TOGAF9 架构分类 2.2.1 业务架构 2.2.2 数据架…...
日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
LLM基础1_语言模型如何处理文本
基于GitHub项目:https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken:OpenAI开发的专业"分词器" torch:Facebook开发的强力计算引擎,相当于超级计算器 理解词嵌入:给词语画"…...
【HarmonyOS 5 开发速记】如何获取用户信息(头像/昵称/手机号)
1.获取 authorizationCode: 2.利用 authorizationCode 获取 accessToken:文档中心 3.获取手机:文档中心 4.获取昵称头像:文档中心 首先创建 request 若要获取手机号,scope必填 phone,permissions 必填 …...
算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
服务器--宝塔命令
一、宝塔面板安装命令 ⚠️ 必须使用 root 用户 或 sudo 权限执行! sudo su - 1. CentOS 系统: yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh2. Ubuntu / Debian 系统…...
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析
Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问(基础概念问题) 1. 请解释Spring框架的核心容器是什么?它在Spring中起到什么作用? Spring框架的核心容器是IoC容器&#…...
【JVM面试篇】高频八股汇总——类加载和类加载器
目录 1. 讲一下类加载过程? 2. Java创建对象的过程? 3. 对象的生命周期? 4. 类加载器有哪些? 5. 双亲委派模型的作用(好处)? 6. 讲一下类的加载和双亲委派原则? 7. 双亲委派模…...
