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

用 Codex + GitHub Spec-Kit 做一次“规格驱动开发”实战

用 Codex + GitHub Spec-Kit 做一次“规格驱动开发”实战

* 用 Codex + GitHub Spec-Kit 做一次“规格驱动开发”实战 * 1) 初始化:把 spec-kit 工作区真正建起来(多种方式) * 方式 A:uvx 一次性运行(推荐) * 方式 B:uv tool install(全局安装 specify) * 方式 C:pipx 安装(Python 工具常用法) * 2) 初始化后,正确的目录结构长什么样( * 3) 在 Codex 里跑 speckit:统一输入规则(非常重要) * 4) 标准流水线:Constitution → Spec → Plan → Tasks → Implement * Step 1:

By Ne0inhk
VSCode Github Copilot使用OpenAI兼容的自定义模型方法

VSCode Github Copilot使用OpenAI兼容的自定义模型方法

背景 VSCode 1.105.0发布了,但是用户最期待的Copilot功能却没更新!!! (Github Copilot Chat 中使用OpenAI兼容的自定义模型。) 🔥官方也关闭了Issue,并且做了回复,并表示未来也不会更新这个功能: “实际上,这个功能在可预见的未来只面向内部人员开放,作为一种“高级”实验功能。是否实现特定模型提供者的功能,我们交由扩展作者自行决定。仅限内部人员使用可以让我们快速推进,并提供一种可能并非始终百分之百完善,但能够持续改进并快速修复 bug 的体验。如果这个功能对你很重要,我建议切换到内部版本 insider。” 🤗 官方解决方案:安装VSCode扩展支持 你们完全不用担心只需要在 VS Code 中安装扩展:OAI Compatible Provider for Copilot 通过任何兼容 OpenAI 的提供商驱动的 GitHub Copilot Chat,使用前沿开源大模型,如 Kimi K2、DeepSeek

By Ne0inhk
使用 VS Code 将项目代码上传到 Gitee 的完整指南

使用 VS Code 将项目代码上传到 Gitee 的完整指南

在现代软件开发流程中,版本控制是不可或缺的一环。 Gitee(码云)作为国内领先的代码托管平台,为开发者提供了稳定、快速的 Git 服务。 本文将详细介绍如何使用 Visual Studio Code(VS Code)将本地项目代码上传至 Gitee 仓库,涵盖从环境配置、初始化仓库到推送代码的完整流程。 一、准备工作 1. 安装必要工具 * Git:确保你的系统已安装 Git。 可通过终端运行 git --version  或 git -v 验证是否安装成功。 * VS Code:下载并安装 Visual Studio Code。 * Gitee 账号:前往 Gitee 官网 注册账号(如尚未注册)。 2. 安装 VS

By Ne0inhk
使用Git将代码从远程仓库拉取到本地(详细图解、简单易懂)

使用Git将代码从远程仓库拉取到本地(详细图解、简单易懂)

目录 一、前言 二、全流程 一、前言 本博客主要记录一下使用Git将代码从远程仓库拉取到本地的全流程,使用Git拉取代码在学校内多同学合作开发项目或者是实习拉取公司代码等场景都很常见,单纯记录希望对你有帮助 二、全流程 首先在你想要存放代码的位置新建一个文件夹并改名 进入刚刚创建的空文件中,右键然后点击显示更多选项 然后点击Git Bash Here 然后就会出现如图所示的命令行窗口 此时先不用管命令行窗口,找到你要远程仓库所在的平台(我这里以Gitee演示),如图点击克隆/下载按钮 HTTPS下方就是远程仓库的url地址,只要有远程仓库的url地址,只需要在刚刚的命令行窗口打上git clone在将url地址复制在后面再回车即可(Gitee下面的提示也给了,直接复制带git clone的命令就行,没有的话就自己敲git clone) 复制到命令行窗口之后,等待片刻即可 然后点开刚刚创建的文件夹就可以看到拉取下来的代码了,后续用IDEA打开该文件就可以在本地进行开发了

By Ne0inhk