Rust异步编程实战:构建高性能WebSocket服务

Rust异步编程实战:构建高性能WebSocket服务

Rust异步编程实战:构建高性能WebSocket服务

在这里插入图片描述

一、WebSocket协议概述

1.1 WebSocket的基本概念

💡WebSocket是一种在单个TCP连接上进行全双工通信的协议,它允许服务器主动向客户端发送消息,而不需要客户端先发起请求。这种通信方式适用于实时应用,如聊天应用、实时通知、在线游戏等。

WebSocket协议的主要特点:

  • 全双工通信:服务器和客户端可以同时发送和接收消息。
  • 低延迟:WebSocket通信的延迟比HTTP低,因为它不需要每次请求都建立新的连接。
  • 可靠性:WebSocket使用TCP协议,保证了消息的可靠传输。
  • 跨域支持:WebSocket支持跨域请求,只需要在服务器端设置相应的CORS策略。

1.2 WebSocket与HTTP的区别

特性HTTPWebSocket
通信方式客户端发起请求,服务器响应全双工通信,服务器可以主动发送消息
连接类型无状态,每次请求建立新连接持久连接,连接建立后保持打开状态
延迟高,因为每次请求需要建立连接低,连接建立后直接通信
适用场景静态资源请求、RESTful API实时应用,如聊天、通知、游戏等

1.3 WebSocket协议的工作原理

  1. 握手阶段:客户端向服务器发送HTTP请求,请求升级协议为WebSocket。
  2. 连接建立:服务器响应升级请求,WebSocket连接建立成功。
  3. 数据传输:服务器和客户端可以通过WebSocket连接发送和接收消息。
  4. 连接关闭:服务器或客户端发送关闭帧,连接关闭。

二、异步WebSocket服务端开发

2.1 使用Axum实现WebSocket服务端

Axum是Rust社区中常用的异步HTTP框架,它提供了简单易用的API来实现WebSocket服务端。

在Cargo.toml中添加依赖:

[dependencies] axum = { version = "0.5", features = ["ws"] } tokio = { version = "1.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 

实现WebSocket服务端:

useaxum::{routing::get,extract::ws::{WebSocket,Message,WebSocketUpgrade},response::IntoResponse,Router,};usetracing_subscriber::prelude::*;usetracing::info;asyncfnws_handler(ws:WebSocketUpgrade)->implIntoResponse{info!("New WebSocket connection"); ws.on_upgrade(|socket|handle_socket(socket))}asyncfnhandle_socket(mut socket:WebSocket){// 发送欢迎消息ifletErr(e)= socket.send(Message::Text("Welcome to WebSocket server".to_string())).await{info!("Error sending welcome message: {}", e);return;}whileletSome(msg)= socket.recv().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);// 回复消息let reply =format!("You said: {}", text);ifletErr(e)= socket.send(Message::Text(reply)).await{info!("Error sending message: {}", e);break;}}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping");ifletErr(e)= socket.send(Message::Pong(data)).await{info!("Error sending pong: {}", e);break;}}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}info!("WebSocket connection closed");}#[tokio::main]asyncfnmain(){// 初始化日志tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();// 创建路由let app =Router::new().route("/ws",get(ws_handler));// 启动服务器let listener =tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();info!("WebSocket server running on http://0.0.0.0:3000");axum::serve(listener, app).await.unwrap();}

2.2 连接管理与消息广播

在实际应用中,我们需要管理多个WebSocket连接,并支持消息广播。我们可以使用tokio::sync::broadcast通道来实现消息广播。

useaxum::{routing::get,extract::ws::{WebSocket,Message,WebSocketUpgrade},response::IntoResponse,Router,};usetracing_subscriber::prelude::*;usetracing::info;usetokio::sync::broadcast;asyncfnws_handler(ws:WebSocketUpgrade, tx:broadcast::Sender<String>)->implIntoResponse{info!("New WebSocket connection");let rx = tx.subscribe(); ws.on_upgrade(|socket|handle_socket(socket, tx, rx))}asyncfnhandle_socket(mut socket:WebSocket, tx:broadcast::Sender<String>,mut rx:broadcast::Receiver<String>,){// 发送欢迎消息ifletErr(e)= socket.send(Message::Text("Welcome to WebSocket server".to_string())).await{info!("Error sending welcome message: {}", e);return;}tokio::spawn(asyncmove{whileletOk(msg)= rx.recv().await{ifletErr(e)= socket.send(Message::Text(msg)).await{info!("Error sending broadcast message: {}", e);break;}}});whileletSome(msg)= socket.recv().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);// 广播消息ifletErr(e)= tx.send(text){info!("Error broadcasting message: {}", e);break;}}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping");ifletErr(e)= socket.send(Message::Pong(data)).await{info!("Error sending pong: {}", e);break;}}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}info!("WebSocket connection closed");}#[tokio::main]asyncfnmain(){// 初始化日志tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();// 创建广播通道let(tx, _)=broadcast::channel(100);// 创建路由let app =Router::new().route("/ws",get(ws_handler)).with_state(tx);// 启动服务器let listener =tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();info!("WebSocket server running on http://0.0.0.0:3000");axum::serve(listener, app).await.unwrap();}

2.3 心跳检测与连接超时

为了确保WebSocket连接的有效性,我们需要实现心跳检测与连接超时机制。

useaxum::{routing::get,extract::ws::{WebSocket,Message,WebSocketUpgrade},response::IntoResponse,Router,};usetracing_subscriber::prelude::*;usetracing::info;usetokio::sync::broadcast;usetokio::time::{interval,Duration};asyncfnws_handler(ws:WebSocketUpgrade, tx:broadcast::Sender<String>)->implIntoResponse{info!("New WebSocket connection");let rx = tx.subscribe(); ws.on_upgrade(|socket|handle_socket(socket, tx, rx))}asyncfnhandle_socket(mut socket:WebSocket, tx:broadcast::Sender<String>,mut rx:broadcast::Receiver<String>,){// 发送欢迎消息ifletErr(e)= socket.send(Message::Text("Welcome to WebSocket server".to_string())).await{info!("Error sending welcome message: {}", e);return;}// 心跳检测letmut heartbeat_interval =interval(Duration::from_secs(10));// 广播接收任务let broadcast_task =tokio::spawn(asyncmove{whileletOk(msg)= rx.recv().await{ifletErr(e)= socket.send(Message::Text(msg)).await{info!("Error sending broadcast message: {}", e);break;}}});// 消息处理任务let message_task =tokio::spawn(asyncmove{whileletSome(msg)= socket.recv().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);ifletErr(e)= tx.send(text){info!("Error broadcasting message: {}", e);break;}}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping");ifletErr(e)= socket.send(Message::Pong(data)).await{info!("Error sending pong: {}", e);break;}}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}});// 心跳检测任务let heartbeat_task =tokio::spawn(asyncmove{loop{tokio::select!{ _ = heartbeat_interval.tick()=>{ifletErr(e)= socket.send(Message::Ping(vec![])).await{info!("Error sending ping: {}", e);break;}} _ = message_task =>{break;} _ = broadcast_task =>{break;}}}});// 等待所有任务完成let _ =tokio::try_join!(heartbeat_task, message_task, broadcast_task);info!("WebSocket connection closed");}#[tokio::main]asyncfnmain(){tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();let(tx, _)=broadcast::channel(100);let app =Router::new().route("/ws",get(ws_handler)).with_state(tx);let listener =tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();info!("WebSocket server running on http://0.0.0.0:3000");axum::serve(listener, app).await.unwrap();}

三、异步WebSocket客户端开发

3.1 使用Tungstenite实现WebSocket客户端

Tungstenite是Rust社区中常用的WebSocket客户端库,它提供了异步和同步两种API。

在Cargo.toml中添加依赖:

[dependencies] tungstenite = "0.18" tokio = { version = "1.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } 

实现WebSocket客户端:

usetungstenite::connect;usetungstenite::Message;useurl::Url;usetracing_subscriber::prelude::*;usetracing::info;#[tokio::main]asyncfnmain(){tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();info!("Connecting to WebSocket server");let(mut socket, response)=connect(Url::parse("ws://127.0.0.1:3000/ws").unwrap()).unwrap();info!("Connected to WebSocket server, response: {:?}", response);// 发送消息 socket.write_message(Message::Text("Hello, WebSocket!".to_string())).unwrap();// 接收消息loop{match socket.read_message(){Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping"); socket.write_message(Message::Pong(data)).unwrap();}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}info!("Client disconnected");}

3.2 异步WebSocket客户端

Tungstenite也提供了异步API,我们可以使用Tokio的异步运行时来实现异步WebSocket客户端。

usetokio_tungstenite::connect_async;usetungstenite::protocol::Message;useurl::Url;usetracing_subscriber::prelude::*;usetracing::info;#[tokio::main]asyncfnmain(){tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();info!("Connecting to WebSocket server");let(ws_stream, response)=connect_async(Url::parse("ws://127.0.0.1:3000/ws").unwrap()).await.unwrap();info!("Connected to WebSocket server, response: {:?}", response);let(mut write,mut read)= ws_stream.split();// 发送消息任务tokio::spawn(asyncmove{ write.send(Message::Text("Hello, WebSocket!".to_string())).await.unwrap();tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; write.send(Message::Text("Another message".to_string())).await.unwrap();tokio::time::sleep(tokio::time::Duration::from_secs(2)).await; write.send(Message::Close(None)).await.unwrap();});// 接收消息任务whileletSome(msg)= read.next().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping"); write.send(Message::Pong(data)).await.unwrap();}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}info!("Client disconnected");}

3.3 重连机制

在实际应用中,WebSocket连接可能会由于网络问题而断开,我们需要实现重连机制。

usetokio_tungstenite::connect_async;usetungstenite::protocol::Message;useurl::Url;usetracing_subscriber::prelude::*;usetracing::info;usetokio::time::{sleep,Duration};asyncfnconnect_and_handle()->Result<(),Box<dynstd::error::Error>>{info!("Connecting to WebSocket server");let(ws_stream, response)=connect_async(Url::parse("ws://127.0.0.1:3000/ws").unwrap()).await?;info!("Connected to WebSocket server, response: {:?}", response);let(mut write,mut read)= ws_stream.split();// 发送消息任务tokio::spawn(asyncmove{ write.send(Message::Text("Hello, WebSocket!".to_string())).await.unwrap();loop{tokio::time::sleep(tokio::time::Duration::from_secs(5)).await; write.send(Message::Text("Ping".to_string())).await.unwrap();}});// 接收消息任务whileletSome(msg)= read.next().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping"); write.send(Message::Pong(data)).await.unwrap();}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}info!("Client disconnected");Ok(())}#[tokio::main]asyncfnmain(){tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();loop{ifletErr(e)=connect_and_handle().await{info!("Connection error: {}, retrying in 5 seconds", e);sleep(Duration::from_secs(5)).await;}}}

四、实战项目:构建实时聊天应用

4.1 项目需求与架构设计

我们将构建一个简单的实时聊天应用,支持以下功能:

  • 多用户同时聊天
  • 消息广播
  • 用户加入/离开通知
  • 心跳检测
  • 连接超时

项目架构设计:

  • 使用Axum作为WebSocket服务端
  • 使用tokio::sync::broadcast实现消息广播
  • 使用tokio::sync::Mutex管理用户信息
  • 使用HTML和JavaScript实现客户端界面

4.2 服务端实现

创建src/main.rs:

useaxum::{routing::{get, post},extract::{ws::{WebSocket,Message,WebSocketUpgrade},State},response::{IntoResponse,Html},Router,};usetracing_subscriber::prelude::*;usetracing::info;usetokio::sync::{broadcast,Mutex};usetokio::time::{interval,Duration};usestd::collections::HashMap;usestd::sync::Arc;#[derive(Debug, Clone)]structUser{ id:String, name:String,}typeUsers=Arc<Mutex<HashMap<String,User>>>;asyncfnindex()->Html<&'staticstr>{Html(r#" <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Real-Time Chat</title> <style> body { font-family: Arial, sans-serif; margin: 0; padding: 0; background-color: #f5f5f5; } .container { max-width: 800px; margin: 0 auto; padding: 20px; } h1 { color: #333; text-align: center; } #chat { height: 400px; overflow-y: scroll; border: 1px solid #ddd; padding: 10px; margin-bottom: 10px; background-color: white; } .message { margin-bottom: 10px; padding: 10px; background-color: #f0f0f0; border-radius: 5px; } .system { color: #666; font-style: italic; } .user { font-weight: bold; } #message-input { width: 80%; padding: 10px; font-size: 14px; } #send-btn { padding: 10px 20px; font-size: 14px; background-color: #007bff; color: white; border: none; border-radius: 5px; cursor: pointer; } #send-btn:hover { background-color: #0056b3; } </style> </head> <body> <div> <h1>Real-Time Chat</h1> <div></div> <input type="text" placeholder="Enter your message"> <button>Send</button> </div> <script> const ws = new WebSocket('ws://' + location.host + '/ws'); const chat = document.getElementById('chat'); const messageInput = document.getElementById('message-input'); const sendBtn = document.getElementById('send-btn'); ws.onmessage = (event) => { const data = JSON.parse(event.data); const messageDiv = document.createElement('div'); messageDiv.className = 'message'; if (data.type === 'system') { messageDiv.className += ' system'; messageDiv.textContent = data.content; } else if (data.type === 'user') { messageDiv.innerHTML = `<span>${data.user}:</span> ${data.content}`; } chat.appendChild(messageDiv); chat.scrollTop = chat.scrollHeight; }; sendBtn.addEventListener('click', () => { const message = messageInput.value.trim(); if (message) { ws.send(JSON.stringify({ type: 'message', content: message })); messageInput.value = ''; } }); messageInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') { sendBtn.click(); } }); </script> </body> </html> "#)}asyncfnws_handler( ws:WebSocketUpgrade, users:State<Users>, tx:State<broadcast::Sender<String>>,)->implIntoResponse{info!("New WebSocket connection");let user_id =uuid::Uuid::new_v4().to_string();let user_name =format!("User{}", user_id.chars().take(8).collect::<String>()); users.lock().await.insert(user_id.clone(),User{ id: user_id.clone(), name: user_name.clone()});let rx = tx.get_ref().subscribe(); ws.on_upgrade(|socket|handle_socket(socket, users, tx, rx, user_id, user_name))}asyncfnhandle_socket(mut socket:WebSocket, users:State<Users>, tx:State<broadcast::Sender<String>>,mut rx:broadcast::Receiver<String>, user_id:String, user_name:String,){// 发送系统消息通知用户加入let join_message =serde_json::json!({"type":"system","content":format!("{} joined the chat", user_name)}); tx.get_ref().send(serde_json::to_string(&join_message).unwrap()).unwrap();// 发送欢迎消息let welcome_message =serde_json::json!({"type":"system","content":format!("Welcome, {}!", user_name)}); socket.send(Message::Text(serde_json::to_string(&welcome_message).unwrap())).await.unwrap();// 心跳检测letmut heartbeat_interval =interval(Duration::from_secs(10));// 广播接收任务let broadcast_task =tokio::spawn(asyncmove{whileletOk(msg)= rx.recv().await{ifletErr(e)= socket.send(Message::Text(msg)).await{info!("Error sending broadcast message: {}", e);break;}}});// 消息处理任务let message_task =tokio::spawn(asyncmove{whileletSome(msg)= socket.recv().await{match msg {Ok(msg)=>{match msg {Message::Text(text)=>{info!("Received message: {}", text);let data:serde_json::Value=serde_json::from_str(&text).unwrap();if data["type"]=="message"{let user_message =serde_json::json!({"type":"user","user": user_name,"content": data["content"]}); tx.get_ref().send(serde_json::to_string(&user_message).unwrap()).unwrap();}}Message::Binary(data)=>{info!("Received binary message ({} bytes)", data.len());}Message::Ping(data)=>{info!("Received ping"); socket.send(Message::Pong(data)).await.unwrap();}Message::Pong(_)=>{info!("Received pong");}Message::Close(_)=>{info!("Connection closed");break;}}}Err(e)=>{info!("Error receiving message: {}", e);break;}}}});// 心跳检测任务let heartbeat_task =tokio::spawn(asyncmove{loop{tokio::select!{ _ = heartbeat_interval.tick()=>{ifletErr(e)= socket.send(Message::Ping(vec![])).await{info!("Error sending ping: {}", e);break;}} _ = message_task =>{break;} _ = broadcast_task =>{break;}}}});// 等待所有任务完成let _ =tokio::try_join!(heartbeat_task, message_task, broadcast_task);// 发送系统消息通知用户离开let leave_message =serde_json::json!({"type":"system","content":format!("{} left the chat", user_name)}); tx.get_ref().send(serde_json::to_string(&leave_message).unwrap()).unwrap();// 从用户列表中删除 users.lock().await.remove(&user_id);info!("WebSocket connection closed");}#[tokio::main]asyncfnmain(){tracing_subscriber::registry().with(tracing_subscriber::EnvFilter::new("info")).with(tracing_subscriber::fmt::layer()).init();let users =Arc::new(Mutex::new(HashMap::new()));let(tx, _)=broadcast::channel(100);let app =Router::new().route("/",get(index)).route("/ws",get(ws_handler)).with_state(users).with_state(tx);let listener =tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();info!("WebSocket server running on http://0.0.0.0:3000");axum::serve(listener, app).await.unwrap();}

在Cargo.toml中添加依赖:

[dependencies] axum = { version = "0.5", features = ["ws"] } tokio = { version = "1.0", features = ["full"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter", "json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" uuid = { version = "1.1", features = ["v4"] } 

4.3 客户端实现

客户端界面已经包含在服务端的index函数中,使用HTML和JavaScript实现。当用户访问http://localhost:3000时,会显示聊天界面。

4.4 性能测试与优化

我们可以使用ab(Apache Bench)工具测试服务端的HTTP性能:

ab -n1000-c100 http://localhost:3000/ 

或者使用wrk工具测试WebSocket连接的性能:

wrk -t12-c400-d30s http://localhost:3000/ws 

性能优化方法:

  • 使用连接池:对于数据库等资源,使用连接池可以避免频繁创建和销毁连接。
  • 优化消息处理:减少消息处理的耗时,提高处理效率。
  • 使用压缩算法:对消息进行压缩,减少传输数据量。
  • 使用负载均衡:对于高并发场景,使用负载均衡可以分散请求压力。

五、常见问题与最佳实践

5.1 WebSocket连接建立失败

问题:客户端无法与服务器建立WebSocket连接。

解决方案

  1. 检查服务器是否正在运行。
  2. 检查服务器地址和端口是否正确。
  3. 检查防火墙是否阻止了WebSocket连接。
  4. 检查服务器的CORS策略是否允许跨域请求。

5.2 消息发送失败

问题:客户端发送消息失败。

解决方案

  1. 检查WebSocket连接是否已经关闭。
  2. 检查消息格式是否正确。
  3. 检查服务器的消息处理逻辑是否有错误。
  4. 检查网络连接是否正常。

5.3 连接超时

问题:WebSocket连接在一段时间后自动断开。

解决方案

  1. 实现心跳检测与连接超时机制。
  2. 检查服务器的超时设置。
  3. 检查网络连接是否稳定。

5.4 消息乱序

问题:客户端接收到的消息顺序不正确。

解决方案

  1. 使用可靠的消息队列实现消息广播。
  2. 在消息中添加时间戳或序列号,客户端根据时间戳或序列号进行排序。

5.5 性能问题

问题:WebSocket服务的性能不佳。

解决方案

  1. 优化服务器的消息处理逻辑。
  2. 使用连接池和异步编程提高处理效率。
  3. 使用负载均衡分散请求压力。
  4. 对消息进行压缩,减少传输数据量。

六、总结

WebSocket协议为实时应用提供了高效的通信方式,Rust的异步编程能力使得构建高性能WebSocket服务变得简单。在本章中,我们介绍了WebSocket协议的基本概念、异步服务端和客户端的开发,以及实战项目的实现。

我们使用Axum框架实现了WebSocket服务端,支持消息广播、心跳检测和连接超时机制。我们还使用Tungstenite库实现了异步WebSocket客户端,支持重连机制。最后,我们构建了一个实时聊天应用,展示了WebSocket在实际项目中的应用。

通过学习本章内容,读者可以掌握Rust异步编程中WebSocket服务的开发方法,并了解常见问题和最佳实践。希望读者能够将这些知识应用到实际项目中,构建高性能的实时应用。

Read more

Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

Re:从零开始的 C++ STL篇(七)二叉搜索树增删查操作系统讲解(含代码)+key/key-value场景联合分析

◆ 博主名称: 晓此方-ZEEKLOG博客大家好,欢迎来到晓此方的博客。⭐️C++系列个人专栏: 主题曲:C++程序设计⭐️ 踏破千山志未空,拨开云雾见晴虹。 人生何必叹萧瑟,心在凌霄第一峰 0.1概要&序論 这里是「此方」,好久不见。 今天我们要学习的是二叉搜索树。它是在普通二叉树的基础上加入特定约束,从而具备了高效的搜索能力。虽然这种结构能够支持高效的插入、删除与查找操作,但其性能背后也隐藏着潜在的 效率风险 。同时,在 key 与 key-value 两种不同的应用场景 下,二叉搜索树的设计与实现方式也会产生不同的变化。这里是「此方」。让我们现在开始吧! 前情提要,没有系统学习过一般二叉树的小伙伴直接看这篇文章可能会有些吃力,此方在这里留一个传送门:Re:从零开始的链式二叉树:建树、遍历、计数、查找、判全、销毁全链路实现与底层剖析 一,二叉搜索树的概念

By Ne0inhk
第十六届蓝桥杯省赛(软件类真题)C/C++ 大学A组

第十六届蓝桥杯省赛(软件类真题)C/C++ 大学A组

大纲: A.寻找质数 B:黑白棋 题目&解析&代码 A题 题目解析 本题的目标是枚举质数并计数,直到数到第2025个。由于2025不算太大,第2025个质数大约在17000~18000之间,完全可以在合理时间内通过简单枚举得到。 解题步骤: 从2开始遍历每个整数,判断它是否是质数。 质数判断采用试除法:对于一个数n,只需检查从2到√n的所有整数是否能整除n。若存在能整除的数,则n不是质数;否则是质数。 每找到一个质数,计数器加1。 当计数器达到2025时,输出当前的质数并结束。 优化点: 除了2以外,偶数不可能是质数,因此可以跳过偶数判断(直接步进2)。 在isPrime函数中,可以先处理特殊情况(n<2返回false),然后单独判断偶数,再对奇数进行试除,步进也可以设为2。 C++ 参考代码 以下代码实现了上述算法,并输出第2025个质数。 cpp

By Ne0inhk
C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板

C++ 模板进阶:特化、萃取与可变参数模板 💡 学习目标:掌握模板进阶技术的核心用法,理解模板特化的深层应用、类型萃取的实现原理,以及可变参数模板的灵活使用,提升泛型编程的实战能力。 💡 学习重点:模板特化的进阶场景、类型萃取工具的设计与应用、可变参数模板的展开技巧、折叠表达式的使用方法。 一、模板特化进阶:处理复杂类型场景 💡 模板特化不只是针对单一类型的定制,还能处理指针、引用、数组等复杂类型,实现更精细的类型适配逻辑。 1.1 指针类型的模板特化 通用模板默认处理普通类型,我们可以为指针类型单独编写特化版本,实现指针专属的逻辑。 #include<iostream>#include<string>usingnamespace std;// 通用模板:处理普通类型template<typenameT>classTypeProcessor{public:staticvoidprocess(T data){ cout

By Ne0inhk

JSP 文件上传详解

JSP 文件上传详解 引言 在Web开发中,文件上传是一个常见的功能,它允许用户将文件从客户端发送到服务器。Java Server Pages(JSP)作为一种强大的服务器端技术,也支持文件上传功能。本文将详细讲解JSP文件上传的实现过程,包括技术原理、实现步骤和注意事项。 技术原理 JSP文件上传主要依赖于HTTP协议的multipart/form-data编码类型。这种编码类型允许表单中包含文件类型的输入字段。当用户提交表单时,浏览器会将表单数据以文件的形式发送到服务器。 服务器端使用Java的javax.servlet包中的HttpServletRequest和HttpServletResponse对象来接收这些文件。同时,javax.servlet包中的javax.servlet.http模块提供了Part接口,用于访问上传的文件内容。 实现步骤 以下是使用JSP实现文件上传的基本步骤: 1. 创建HTML表单 首先,我们需要创建一个HTML表单,其中包含一个文件类型的输入字段。以下是一个简单的示例: <form action="upload.jsp"

By Ne0inhk