用Rust实现一个简单的KV Server: day1

557 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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" }

serdeserde_json就不多介绍了,都是序列化用的库。 另外,因为tokio有很多features,我们只是demo项目,就用full开启全部feature

这里值得注意的就是anyhow了,这个库比较有意思,是一个处理返回类型的库,主要就是把ResultError进行了特殊的封装处理。这样就不用因为返回类型不匹配而到处报错了。

配置文件

这里我使用json做配置文件,serdeserde_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