【Rust http編程】Rust搭建webserver的底層原理與應用實戰

在這里插入圖片描述

?? 歡迎大家來到景天科技苑??

🎈🎈 養成好習慣,先贊后看哦~🎈🎈

🏆 作者簡介:景天科技苑
🏆《頭銜》:大廠架構師,華為云開發者社區專家博主,阿里云開發者社區專家博主,CSDN全棧領域優質創作者,掘金優秀博主,51CTO博客專家等。
🏆《博客》:Rust開發,Python全棧,Golang開發,云原生開發,PyQt5和Tkinter桌面開發,小程序開發,人工智能,js逆向,App逆向,網絡系統安全,數據分析,Django,fastapi,flask等框架,云原生K8S,linux,shell腳本等實操經驗,網站搭建,數據庫等分享。

所屬的專欄:Rust語言通關之路
景天的主頁:景天科技苑

在這里插入圖片描述

文章目錄

  • Rust http編程
    • 1. HTTP基礎與Rust生態系統
      • 1.1 HTTP協議回顧
      • 1.2 Rust HTTP生態系統概覽
    • 2. 使用標準庫進行HTTP編程
      • 2.1 基本HTTP服務端
      • 2.2 簡單HTTP客戶端
      • 2.3 服務端響應網頁
      • 2.4 有條件地響應網頁
      • 2.5 多線程的http服務器
      • 2.6 線程池webserver
      • 2.7 實現線程池清除的webserver

Rust http編程

Rust作為一門系統級編程語言,憑借其出色的性能、內存安全性和并發特性,在網絡編程領域展現出強大的潛力。
本文將詳細介紹如何使用Rust進行HTTP編程,涵蓋從基礎概念到實際應用的各個方面。

1. HTTP基礎與Rust生態系統

1.1 HTTP協議回顧

HTTP(HyperText Transfer Protocol)是應用層協議,基于請求-響應模型工作。Rust提供了多種處理HTTP協議的方式:
標準庫:基礎但功能有限
第三方庫:功能豐富,如reqwest、hyper等
Web框架:如actix-web、rocket等

1.2 Rust HTTP生態系統概覽

Rust的HTTP生態系統包含多個層次的組件:
底層庫:hyper、h2、http等
客戶端庫:reqwest、ureq等
服務器框架:actix-web、rocket、warp等
工具庫:serde(序列化)、tokio(異步運行時)等

2. 使用標準庫進行HTTP編程

雖然不推薦在生產環境中使用標準庫進行HTTP編程,但了解其基本用法有助于理解底層原理。
可以參考官方標準庫net庫 https://doc.rust-lang.org/stable/std/net/index.html
TcpListener可以創建http客戶端和服務端
在這里插入圖片描述

HTTP簡單介紹
(1)http請求報文包含三個部分內容 :請求行、請求頭 、請求體
Method Request-URI HTTP-Version CRLF //請求行:請求方式、協議版本等
headers CRLF //請求頭:包含若干個屬性,格式為“屬性名:屬性值”,格式為"屬性名:屬性值",服務端據此獲取客戶端的信息
message-body //請求體 :客戶端真正要傳送給服務端的內容

(2)http響應報文也有三部分內容:響應行、響應頭、響應體
HTTP-Version status-Code Reason-Phrase CRLF //響應行:報文協議及版本,狀態碼及狀態描述
headers CRLF //響應頭:由多個屬性組成
message-body //響應體:真正響應的內容

2.1 基本HTTP服務端

主要使用標準庫中的net庫和io庫

use std::net::{ TcpListener, TcpStream }; //導入TcpListener和TcpStream
use std::io::{ Read, Write }; //導入Read和Writefn handle_client(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//構建http響應,向客戶端打招呼//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//將響應寫入到客戶端stream.write_all(response.as_bytes()).unwrap();//刷新緩沖區stream.flush().unwrap();
}fn main() -> std::io::Result<()> {//創建監聽器let listener = TcpListener::bind("127.0.0.1:8080")?;//處理客戶端請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求for stream in listener.incoming() {//處理客戶端請求的邏輯//listener.incoming()返回的迭代器包含錯誤,需要使用?來處理handle_client(stream?);}Ok(())
}

2.2 簡單HTTP客戶端

use std::io::{ Read, Write };
use std::net::TcpStream;fn main() -> std::io::Result<()> {//創建TCP連接let mut stream = TcpStream::connect("localhost:8080")?;//構建HTTP請求let request ="GET / HTTP/1.1\r\n\Host: localhost:8080\r\n\Connection: close\r\n\\r\n";stream.write_all(request.as_bytes())?;//創建個緩沖區,用于讀取服務器的響應let mut buffer = Vec::new();//讀取服務器的響應stream.read_to_end(&mut buffer)?;//打印服務器的響應println!("{}", String::from_utf8_lossy(&buffer));Ok(())
}

服務端收到客戶端請求
在這里插入圖片描述

客戶端收到服務端響應
在這里插入圖片描述

2.3 服務端響應網頁

use std::net::{ TcpListener, TcpStream }; //導入TcpListener和TcpStream
use std::io::{ Read, Write }; //導入Read和Writefn handle_client(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//構建http響應,向客戶端打招呼//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//從文件讀取內容響應給客戶端let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);//將響應寫入到客戶端stream.write_all(response.as_bytes()).unwrap();//刷新緩沖區stream.flush().unwrap();
}fn main() -> std::io::Result<()> {//創建監聽器let listener = TcpListener::bind("127.0.0.1:8080")?;//處理客戶端請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求for stream in listener.incoming() {//處理客戶端請求的邏輯//listener.incoming()返回的迭代器包含錯誤,需要使用?來處理handle_client(stream?);}Ok(())
}

直接瀏覽器訪問查看
在這里插入圖片描述

2.4 有條件地響應網頁

有條件地響應網頁,主要是對客戶端的請求進行判斷,不同的請求路徑、請求方法等響應不同內容

use std::net::{ TcpListener, TcpStream }; //導入TcpListener和TcpStream
use std::io::{ Read, Write }; //導入Read和Writefn handle_client(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//構建http響應,向客戶端打招呼//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//獲取客戶端的請求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//判斷請求方法是否為GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;} else {//獲取客戶端的請求路徑let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判斷請求路徑是否為/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();return;}}
}fn main() -> std::io::Result<()> {//創建監聽器let listener = TcpListener::bind("127.0.0.1:8080")?;//處理客戶端請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求for stream in listener.incoming() {//處理客戶端請求的邏輯//listener.incoming()返回的迭代器包含錯誤,需要使用?來處理handle_client(stream?);}Ok(())
}

get方法 /路徑
在這里插入圖片描述

get方法其他路徑
在這里插入圖片描述

代碼優化,將一些重復的代碼封裝

use std::net::{ TcpListener, TcpStream }; //導入TcpListener和TcpStream
use std::io::{ Read, Write }; //導入Read和Writefn handle_client(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//獲取客戶端的請求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封裝一個函數,響應客戶端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判斷請求方法是否為GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//獲取客戶端的請求路徑let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判斷請求路徑是否為/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}fn main() -> std::io::Result<()> {//創建監聽器let listener = TcpListener::bind("127.0.0.1:8080")?;//處理客戶端請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求for stream in listener.incoming() {//處理客戶端請求的邏輯//listener.incoming()返回的迭代器包含錯誤,需要使用?來處理handle_client(stream?);}Ok(())
}

2.5 多線程的http服務器

單線程的的webserver存在的問題:
請求只能串行處理,也就是說當第一個連接處理完之前不會處理第二個連接。
這樣,當有海量請求的時候,就會出問題
我們采用多線程

//多線程的http服務器
use std::thread;
use std::net::{ TcpListener, TcpStream };
use std::io::{ Read, Write };fn handle_client(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//獲取客戶端的請求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封裝一個函數,響應客戶端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判斷請求方法是否為GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//獲取客戶端的請求路徑let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判斷請求路徑是否為/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}fn main() -> std::io::Result<()> {//創建監聽器let listener = TcpListener::bind("127.0.0.1:8080")?;//創建線程句柄let mut handles = Vec::new();//處理客戶端請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求for stream in listener.incoming() {//處理客戶端請求的邏輯//使用多線程let handle = thread::spawn(move || {handle_client(stream.unwrap());});handles.push(handle);}//等待所有線程結束for handle in handles {handle.join().unwrap();}Ok(())
}

2.6 線程池webserver

上面通過多線程創建的webserver,當請求不斷太多時,還是可以用一用。
但是當請求比較海量時,系統也會跟著創建海量的線程,最終造成系統資源耗盡而崩潰
此時,我們采用線程池來處理
在這里插入圖片描述

多線程,管道
從主線程將任務發送到管道,工作線程等待在管道的接收端,當收到任務時,進行處理。

? 創建文件結構:
.
├── main.rs
├── lib.rs // 線程池模塊

🔧 Cargo.toml(依賴可以不用加,使用標準庫)

[package]
name = "myhttpserver3"
version = "0.1.0"
edition = "2024"[dependencies]

📄 src/main.rs

use std::net::TcpListener;
use std::io::prelude::*;
use std::net::TcpStream;
use myhttpserver3::ThreadPool; //這里myhttpserver3是Cargo.toml中定義的依賴庫名稱,就是項目的名稱fn main() {//創建監聽器,監聽7878端口let listener = TcpListener::bind("127.0.0.1:7878").unwrap();//創建線程池,線程池大小為4let pool = ThreadPool::new(4);println!("Server running on 127.0.0.1:7878");//使用線程池處理請求for stream in listener.incoming().take(10) {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}fn handle_connection(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//獲取客戶端的請求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封裝一個函數,響應客戶端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判斷請求方法是否為GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//獲取客戶端的請求路徑let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判斷請求路徑是否為/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}

📄 src/lib.rs

//線程池
use std::sync::{ Arc, Mutex };
use std::sync::mpsc;
use std::thread;//定義一個結構體,表示線程池
#[allow(dead_code)]
pub struct ThreadPool {workers: Vec<Worker>,sender: mpsc::Sender<Job>,
}//使用type關鍵字定義一個類型別名,表示任務。使用type起類型別名,用于簡化代碼
//這個類型是依照ThreadPool的excute()方法的參數類型來的
type Job = Box<dyn FnOnce() + Send + 'static>;//為ThreadPool實現方法
impl ThreadPool {// 創建新線程池pub fn new(size: usize) -> ThreadPool {//線程池的大小必須大于0assert!(size > 0);println!("Creating a thread pool of size {}", size);//創建通道let (sender, receiver) = mpsc::channel();//將接收端放入互斥鎖中,再放入Arc中,實現共享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 }}// 執行任務。這里是參照標準庫 thread::spawn()的實現的//對F有約束pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static {//將任務包裝成Boxlet job = Box::new(f);self.sender.send(job).unwrap();}
}//定義一個結構體,表示工作線程
#[allow(dead_code)]
struct Worker {id: usize, //工作線程的idthread: thread::JoinHandle<()>, //線程句柄
}//為Worker實現方法
impl Worker {//接收端需要線程安全,所以需要Arc<Mutex<T>>fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Job>>>) -> Worker {//創建工作線程let thread = thread::spawn(move || {//循環從通道中接收任務,并執行loop {//recv會阻塞線程,直到有數據可讀let job = receiver.lock().unwrap().recv().unwrap();println!("Worker {} got a job; executing.", id);//執行任務job();}});//返回工作線程Worker { id, thread }}
}

📄 index.html(放在項目根目錄)

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><h1>Hello, Jingtian!</h1></body>
</html>

📄 404.html

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><h1>Oops!</h1><p>The page you are looking for does not exist.</p></body>
</html>

運行服務器
cargo run

然后在瀏覽器打開 http://127.0.0.1:7878/
在這里插入圖片描述

如果是其他路徑
在這里插入圖片描述

2.7 實現線程池清除的webserver

在之前的用線程池實現的webserver中,每個工作線程中通過loop進行循環,從channel的接收端等待任務,然后執行。
但是在代碼中,work采用的是loop循環,沒有跳出循環的條件,沒有提供一種機制,來通知工作線程結束。
現在我們就來實現線程池對象的正確清除。
通過為ThreadPool實現Drop trait來實現線程池對象清除

修改Worker如下:

struct Worker {id: usize, //工作線程的id//線程句柄,將thread::JoinHandle<()>包裝成Option,用于在drop()方法中調用take()方法//Option中有take()方法,可以將Some中的值取出來,同時將Some置為Nonethread: Option<thread::JoinHandle<()>>,
}

Option中有take方法
在這里插入圖片描述

完成的代碼:
src/lib.rs

//線程池
use std::sync::{ Arc, Mutex };
use std::sync::mpsc;
use std::thread;//定義一個結構體,表示線程池
#[allow(dead_code)]
pub struct ThreadPool {workers: Vec<Worker>,// sender: mpsc::Sender<Job>,sender: mpsc::Sender<Message>,
}//使用type關鍵字定義一個類型別名,表示任務。使用type起類型別名,用于簡化代碼
//這個類型是依照ThreadPool的excute()方法的參數類型來的
type Job = Box<dyn FnOnce() + Send + 'static>;//發送結束消息給worker,所有發送job的地方都要修改
enum Message {//兩種情況NewJob(Job),Terminate,
}//為ThreadPool實現方法
impl ThreadPool {// 創建新線程池pub fn new(size: usize) -> ThreadPool {//線程池的大小必須大于0assert!(size > 0);println!("Creating a thread pool of size {}", size);//創建通道let (sender, receiver) = mpsc::channel();//將接收端放入互斥鎖中,再放入Arc中,實現共享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 }}// 執行任務。這里是參照標準庫 thread::spawn()的實現的//對F有約束pub fn execute<F>(&self, f: F) where F: FnOnce() + Send + 'static {//將任務包裝成Boxlet job = Box::new(f);// self.sender.send(job).unwrap();self.sender.send(Message::NewJob(job)).unwrap();}
}//為ThreadPool實現Drop trait
impl Drop for ThreadPool {//當線程池被銷毀時,關閉所有工作線程//實現Drop trait,只需要實現drop()方法即可fn drop(&mut self) {//發送結束消息給workerfor _ in &self.workers {self.sender.send(Message::Terminate).unwrap();}//等待所有工作線程結束for worker in &mut self.workers {println!("Shutting down worker {}", worker.id);//等待工作線程結束if let Some(thread) = worker.thread.take() {thread.join().unwrap();}}}
}//定義一個結構體,表示工作線程
#[allow(dead_code)]
struct Worker {id: usize, //工作線程的id//線程句柄,將thread::JoinHandle<()>包裝成Option,用于在drop()方法中調用take()方法//Option中有take()方法,可以將Some中的值取出來,同時將Some置為Nonethread: Option<thread::JoinHandle<()>>,
}//為Worker實現方法
impl Worker {//接收端需要線程安全,所以需要Arc<Mutex<T>>fn new(id: usize, receiver: Arc<Mutex<mpsc::Receiver<Message>>>) -> Worker {//創建工作線程let thread = thread::spawn(move || {//循環從通道中接收任務,并執行loop {//recv會阻塞線程,直到有數據可讀// let job = receiver.lock().unwrap().recv().unwrap();let message = receiver.lock().unwrap().recv().unwrap();// println!("Worker {} got a job; executing.", id);//判斷消息類型match message {Message::NewJob(job) => {println!("Worker {} got a job; executing.", id);job();}Message::Terminate => {println!("Worker {} was told to terminate.", id);//收到結束消息,退出循環break;}}}});//返回工作線程Worker { id, thread: Some(thread) }}
}

src/main.rs

use std::net::TcpListener;
use std::io::prelude::*;
use std::net::TcpStream;
use myhttpserver4::ThreadPool; //這里myhttpserver3是Cargo.toml中定義的依賴庫名稱,就是項目的名稱fn main() {//創建監聽器,監聽7878端口let listener = TcpListener::bind("127.0.0.1:7878").unwrap();//創建線程池,線程池大小為4let pool = ThreadPool::new(4);println!("Server running on 127.0.0.1:7878");//使用線程池處理請求//listener.incoming()返回一個迭代器,用于接收客戶端的連接請求//take(4)表示只接收4個連接請求,可以根據實際情況調整for stream in listener.incoming().take(4) {let stream = stream.unwrap();pool.execute(|| {handle_connection(stream);});}println!("Shutting down.");
}fn handle_connection(mut stream: TcpStream) {//讀取客戶端請求,每次讀取1024個字節let mut buffer = [0; 1024];stream.read(&mut buffer).unwrap();//打印客戶端請求println!("Request: {}", String::from_utf8_lossy(&buffer[..]));//獲取客戶端地址let client_addr = stream.peer_addr().unwrap();println!("New connection: {}", client_addr);// let response = format!("HTTP/1.1 200 OK\r\n\r\nhello {client_addr}!");//獲取客戶端的請求方法let request_method = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_method = request_method.split(" ").nth(0).unwrap();println!("Request method: {}", request_method);//封裝一個函數,響應客戶端fn response_client(mut stream: TcpStream, response: String) {stream.write_all(response.as_bytes()).unwrap();stream.flush().unwrap();}//判斷請求方法是否為GETif request_method != "GET" {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {//獲取客戶端的請求路徑let request_path = std::str::from_utf8(&buffer).unwrap().lines().next().unwrap();let request_path = request_path.split(" ").nth(1).unwrap();println!("Request path: {}", request_path);//判斷請求路徑是否為/if request_path == "/" {let content = std::fs::read_to_string("index.html").unwrap();let response = format!("HTTP/1.1 200 OK\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);} else {let content = std::fs::read_to_string("404.html").unwrap();let response = format!("HTTP/1.1 404 Not Found\r\n\r\n{}", content);// stream.write_all(response.as_bytes()).unwrap();// stream.flush().unwrap();response_client(stream, response);}}
}

接收4個請求后,服務器就關閉

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/86310.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/86310.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/86310.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

4 Geotools坐標參考系與轉換

在地理信息系統 (GIS) 開發中&#xff0c;坐標參考系統 (Coordinate Reference System, CRS) 是核心概念之一。無論是處理地圖投影、坐標轉換&#xff0c;還是在 Spring Boot 應用中管理空間數據&#xff0c;理解和正確使用 CRS 都至關重要。本文將圍繞 GeoTools 庫&#xff0c…

docker start mysql失敗,解決方案

文章目錄 1.查看端口占用情況2.關閉7767進程3.再次檢查4.運行docker start mysql 1.查看端口占用情況 sudo netstat -tanlp | grep :33062.關閉7767進程 sudo kill -9 77673.再次檢查 進程已關閉 4.運行docker start mysql 正確啟動 備注&#xff1a;可能要關閉防火墻

SQL關鍵字三分鐘入門:DELETE —— 刪除數據

在數據庫操作中&#xff0c;除了添加和修改記錄外&#xff0c;我們有時還需要刪除不需要的記錄。例如&#xff1a; 清除不再使用的用戶賬號&#xff1b;刪除已完成并歸檔的訂單&#xff1b;移除測試時插入的數據。 這時候就需要用到 SQL 中非常基礎但極其重要的關鍵字 —— D…

electron 全量更新

electron-builder.yml配置更新地址 # 配置自動更新的信息 publish:provider: generic # 更新服務提供者url: http://xxx.xxxx.com/pc/xxx-xx# 更新的地址服務器地址 會自動讀取latest.yml 下的版本號比較 檢測更新方法autoUpdater.js// src/main/autoUpdater.jsimport { app, d…

《大模型 Agent 應用實戰指南》第2章:商業目標與 Agent 能力邊界定義

在任何技術項目,特別是像大模型 Agent 這樣具有創新性和復雜性的項目啟動之初,明確清晰的商業目標是成功的基石。這不僅僅是技術團隊的職責,更需要產品、運營、銷售甚至高層管理者的深度參與。一個明確的目標能確保所有團隊成員步調一致,資源有效分配,并最終衡量項目的成功…

提供穩定可靠的自助共享空間系統,支撐客戶無人自助門店運營不錯數據,歷程感想

以技術產品研發系統為主&#xff0c;為客戶提供自助共享空間系統解決方案&#xff0c;適用于共享棋牌室&#xff0c;共享麻將室&#xff0c;共享臺球室&#xff0c;共享KTV&#xff0c;共享舞蹈室等場景&#xff0c;以下是其中一位客戶真實門店運營數據&#xff0c;第一家店本月…

Golang單例實現

Go語言中&#xff0c;實現單例模式的方式有很多種。單例模式確保一個類只有一個實例&#xff0c;并提供一個全局訪問點。Go語言沒有類的概念&#xff0c;但是可以通過結構體、函數和包級變量來實現類似的功能。 懶漢實現 type Product interface {DoSomething() }type single…

JVM元空間(Metaspace)詳解及其工作流程

JVM元空間(Metaspace)詳解與工作流程分析 元空間概述 元空間(Metaspace)是Java虛擬機(JVM)在HotSpot VM 1.8及以后版本中引入的&#xff0c;用于替代永久代(PermGen)的內存區域。它主要存儲類的元數據信息&#xff0c;包括&#xff1a; 類的結構信息&#xff08;如方法、字段…

【JAVA】idea中打成jar包后報錯錯誤: 找不到或無法加載主類

排查步驟 首先要排查的是&#xff0c;將jar文件打開&#xff0c;查看里面的內容是否完整是否有META-INF/MANIFEST.MF是否有MANIFEST.MF里面類路徑的目錄排查路徑里面是否有class文件&#xff0c;如主類 com.example.Main 對應的 class 文件應位于 com/example/Main.class 常見…

Fisco Bcos學習 - 開發第一個區塊鏈應用

文章目錄 一、前言二、業務場景分析&#xff1a;簡易資產管理系統三、智能合約設計與實現3.1 存儲結構設計3.2 接口設計3.3 完整合約代碼 四、合約編譯與Java接口生成五、SDK配置與項目搭建5.1 獲取Java工程項目5.2 項目目錄結構5.3 引入Web3SDK5.4 證書與配置文件 六、業務開發…

軟件設計模式選擇、判斷解析-1

前言 解析是我個人的理解&#xff0c;相對來說我覺得是能對上定義的邏輯的 目錄 一.單選題 1.設計模式的兩大主題是(??)? 解析&#xff1a;無 2.下列模式中,屬于行為型模式的是&#xff08;&#xff09; 解析&#xff1a; 排除A和D&#xff0c;剩下的觀察者的“觀察”…

【編程基本功】Win11中Git安裝配置全攻略,包含Git以及圖形化工具TortoiseGit

1 摘要 今天田辛老師給大家帶來了一份超實用的博客&#xff0c;手把手教你安裝并配置 Git 及其圖形化界面 TortoiseGit&#xff0c;從官網下載到最終完成配置&#xff0c;每一個步驟都給大家講得明明白白&#xff0c;還配有相應的截圖&#xff0c;即使是新手小白也能輕松上手&…

細談QT信號與槽機制

轉自個人博客 信號與槽是我個人認為QT中最牛的機制之一&#xff0c;最近沒有其他的內容可寫&#xff0c;今天就來細細總結一下這個信號與槽機制。 1. 信號與槽機制概述 信號與槽機制可以理解為QT中的一種通信手段&#xff0c;在運行相關代碼前&#xff0c;分別聲明信號和槽&a…

Docker Swarm 與 Kubernetes 在集群管理上的主要區別

Docker Swarm 和 Kubernetes 是兩種流行的容器編排工具&#xff0c;它們都可以用于部署、管理和擴展容器化應用&#xff0c;但在集群管理方面有明顯的差異。 下面從多個維度對比它們在集群管理上的主要區別&#xff1a; ? 一、總體定位 項目Docker SwarmKubernetes官方支持D…

【StarRocks系列】查詢優化

步驟參考官網 分析查詢 | StarRocks StarRocks-Profile分析及優化指南 StarRocks-Profile分析及優化指南 - 經驗教程 - StarRocks中文社區論壇

軟測八股--測試理論 1 測試基礎

軟件測試&#xff1f; 發現程序中的側屋執行程序工程 目的&#xff1a;不僅是找出錯誤&#xff0c;還要分析錯誤產生原因和錯誤分布。檢查開發如阿健過程出現的bug&#xff0c;使開發人員及時修改。測試只能說明軟件中存在錯誤 目標&#xff1a;盡可能發現多的錯誤。一個好的…

mfc與vs成功在xp系統所需做的修改

目錄 前言一、MFC程序 inet_pton 、CT2A 未聲明問題1&#xff09;問題1&#xff1a;inet_pton &#xff1a;undeclared identifier - inet_pton未聲明2&#xff09;問題1&#xff1a;CT2A &#xff1a;undeclared identifier - CT2A未聲明 二、VS程序 使用事件、委托問題1&…

SpringMVC系列(三)(請求處理的十個實驗(上))

0 引言 作者正在學習SpringMVC相關內容&#xff0c;學到了一些知識&#xff0c;希望分享給需要短時間想要了解SpringMVC的讀者朋友們&#xff0c;想用通俗的語言講述其中的知識&#xff0c;希望與諸位共勉&#xff0c;共同進步&#xff01; 本系列會持續更新&#xff01;&…

Python案例練習:函數專題

用函數重新設計文章單詞出現次數程序 composition This is my family. We have a father, a mother and two brothers. My father is a doctor. He works in a hospital. My mother is a teacher. She teaches English in a school. My older brother is a student. He stu…

數據驅動 AI 時代:數據庫行業的技術躍遷與生態重構

在數據驅動的 AI 戰場&#xff0c;真正的決勝武器不是復雜精妙的算法模型&#xff0c;而是深埋在企業核心系統中的高維數據網絡&#xff08;圖&#xff09;。 時至今日&#xff0c;市場對AI的風向正從“狂飆突進”轉向“精耕細作”&#xff0c;就在上周&#xff08;米國時間6月…