开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第10天,点击查看活动详情
最近我在一个大佬的公众号看到的用rust实现KV Server
的项目。本着天下文章一般抄的想法,我也跟着公众号的文章复制粘贴了不少代码。但是最终还是发现纸上得来终觉浅,绝知此事要躬行。
所以,还是开个坑吧。把代码都敲(复制)一遍,再加上自己的注释来一遍(当然也不是100%保持和原版一样,会有一点点魔改)。
首先创建一个项目
cargo new --lib kv_server
然后就是需要用到的库:
[dependencies]
anyhow = { version = "1.0.65" }
tokio = { version = "1.21.2", features = ["full"] }
serde = { version = "1.0.144", features = ["derive"] <p align=center>}</p>
serde_json = { version = "1.0.85" }
serde
和serde_json
就不多介绍了,都是序列化用的库。
另外,因为tokio
有很多features
,我们只是demo项目,就用full
开启全部feature
。
这里值得注意的就是anyhow
了,这个库比较有意思,是一个处理返回类型的库,主要就是把Result
和Error
进行了特殊的封装处理。这样就不用因为返回类型不匹配而到处报错了。
配置文件
这里我使用json
做配置文件,serde
和serde_json
来处理配置的序列化和反序列化。当然也可以用环境变量或者其他格式(toml、yaml等)来做配置文件。在项目根目录下新建conf
目录,并在下面新建server.json
文件:
{
"listen_address": {
"address": "127.0.0.1:3000"
}
}
和client.json
文件:
{
"client_address": {
"server_addr": "127.0.0.1:3000"
}
}
新建src/config.rs
文件:
use serde::{Deserialize, Serialize};
use serde_json;
use std::{error::Error, fs};
//server端配置
#[derive(Debug, Serialize, Deserialize)]
pub struct ServerConfig {
pub listen_address: ListenerAddress,
}
//监听地址
#[derive(Debug, Serialize, Deserialize)]
pub struct ListenerAddress {
pub address: String,
}
// Client端配置
#[derive(Debug, Serialize, Deserialize)]
pub struct ClientConfig {
pub client_address: ConnectAddress,
}
// 连接地址
#[derive(Debug, Serialize, Deserialize)]
pub struct ConnectAddress {
pub server_addr: String,
}
impl ServerConfig {
//加载Server端配置文件
pub fn load(path: &str) -> Result<Self, Box<dyn Error>> {
let config = fs::read_to_string(path)?;
let server_config = serde_json::from_str(&config)?;
Ok(server_config)
}
}
impl ClientConfig {
//加载Client端配置文件
pub fn load(path: &str) -> Result<Self, Box<dyn Error>> {
let config = fs::read_to_string(path)?;
let client_config = serde_json::from_str(&config)?;
Ok(client_config)
}
}
里面把我们即将用到的变量都加上Serialize, Deserialize
序列化宏,使用serde
库会自动进行序列化处理,同时使用load
方法对json
文件进行读取
然后在lib.rs
中将模块导出:
mod config;
pub use config::*;
Server端
在src目录下创建bin
文件夹,然后创建kv_server.rs
文件:
use anyhow::Result;
use kv_server::ServerConfig;
use std::error::Error;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpListener,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
//读取配置
let server_conf = ServerConfig::load("conf/server.json")?;
let addr = server_conf.listen_address.address;
//连接ip地址
let listener = TcpListener::bind(&addr).await?;
println!("Listening on {} ......", addr);
loop {
let (mut stream, addr) = listener.accept().await?;
println!("Client: {:?} connected", addr);
//新建协程处理
tokio::spawn(async move {
//缓存buf
let mut buf = vec![0u8; 1024];
loop {
let n = stream.read(&mut buf).await.expect("从Socket读取数据失败!");
if n == 0 {
return;
}
stream
.write_all(&buf[0..n])
.await
.expect("向Socket写入数据失败!");
}
});
}
}
这里就比较简单了。从json
文件读取的配置在此使用。如在127.0.0.1:3000
地址监听客户端的连接,收到客户端发来的信息后再返回给客户端。
Client
在src/bin
目录下创建kv_client.rs
文件:
use std::error::Error;
use anyhow::Result;
use kvserver_rust_part_1::ClientConfig;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::TcpStream,
};
#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
//读取配置
let client_config = ClientConfig::load("conf/client.json")?;
let addr = client_config.client_address.server_addr;
let mut stream = TcpStream::connect(&addr).await?;
let n = stream.write(b"Hello, world!").await?;
println!("Send info successed!n = {n}");
let mut buf = vec![0u8; 1024];
let n = stream.read(&mut buf).await.expect("从Socket读取数据失败!");
println!("Receive info:{}, n = {n}", String::from_utf8(buf).unwrap());
Ok(())
}
同时先读取json
配置内容,连接server
端配置好的127.0.0.1:3000
地址,向Server端发送Hello, world!
消息,然后再接收server
端返回的消息。
打开两个终端,分别执行:
cargo run --bin kv_server
cargo run --bin kv_client
或者在Cargo.toml
文件加上别名:
[[bin]]
name = "server"
path = "src/bin/kv_server.rs"
[[bin]]
name = "client"
path = "src/bin/kv_client.rs"
这样命令就可以改为:
cargo run --bin server
cargo run --bin client
执行结果
kv_server
端:
Listening on 127.0.0.1:3000 ......
Client: 127.0.0.1:51724 connected
kv_client
端:
Send info successed!n = 13
Receive info:Hello, world!, n = 13