You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux下带状态Rust程序转守护进程及CLI交互最佳实践

Linux下实现后台状态维护Rust服务的非阻塞交互最佳实践

问题背景

我有一个需要在后台维护内部状态的Rust库,当前通过标准输入输出做阻塞式交互:

use tokio::io::AsyncBufReadExt;

#[tokio::main]
async fn main() {
    let mut state = 0;

    let mut reader = tokio::io::BufReader::new(tokio::io::stdin());

    loop {
        let mut buffer = Vec::new();
        reader.read_until(b'\n', &mut buffer).await.unwrap();
        let cmd = std::str::from_utf8(&buffer).unwrap();

        match cmd {
            "get-count\n" => {
                println!("{}", state);
                state += 1;
            }
            _ => {}
        }
    }
}

这种方式会阻塞终端,无法结合其他Bash命令灵活交互。目标是将其作为守护进程运行,实现非阻塞的CLI交互:

>> ./start-service
>> ./service-cli get-count
0
>> ./service-cli get-count
1
>> COUNT=$(./service-cli get-count)
>> echo $COUNT
2

最佳实现方案:Unix域套接字IPC

Linux本地进程间通信(IPC)的首选方案是Unix域套接字,相比TCP套接字更高效,且仅限本地访问,安全性更高。以下是具体实现步骤:

1. 改造Rust服务端(守护进程)

将原来的标准输入监听改为Unix套接字监听,维护内部状态并处理客户端请求:

use tokio::net::UnixListener;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use std::path::Path;

#[tokio::main]
async fn main() {
    // 定义套接字路径,通常放在/run目录下(临时文件系统,重启后自动清理)
    let socket_path = "/run/my-service.sock";
    
    // 如果套接字文件已存在,先删除(避免服务重启失败)
    if Path::new(socket_path).exists() {
        std::fs::remove_file(socket_path).unwrap();
    }

    // 启动Unix套接字监听
    let listener = UnixListener::bind(socket_path).unwrap();
    // 设置套接字权限,让普通用户也能访问(根据需求调整)
    std::fs::set_permissions(socket_path, std::fs::Permissions::from_mode(0o666)).unwrap();

    let mut state = 0;

    loop {
        // 等待客户端连接
        match listener.accept().await {
            Ok((mut stream, _)) => {
                let mut buffer = Vec::new();
                // 读取客户端发送的命令
                stream.read_to_end(&mut buffer).await.unwrap();
                let cmd = std::str::from_utf8(&buffer).unwrap().trim();

                // 处理命令并返回结果
                match cmd {
                    "get-count" => {
                        let response = format!("{}\n", state);
                        stream.write_all(response.as_bytes()).await.unwrap();
                        state += 1;
                    }
                    _ => {
                        stream.write_all(b"unknown command\n").await.unwrap();
                    }
                }
            }
            Err(e) => eprintln!("Failed to accept connection: {}", e),
        }
    }
}

2. 实现CLI客户端

编写一个轻量的客户端程序,连接Unix套接字并发送命令:

use tokio::net::UnixStream;
use tokio::io::{AsyncReadExt, AsyncWriteExt};

#[tokio::main]
async fn main() {
    let socket_path = "/run/my-service.sock";
    // 获取命令行参数
    let args: Vec<String> = std::env::args().collect();
    if args.len() < 2 {
        eprintln!("Usage: {} <command>", args[0]);
        std::process::exit(1);
    }
    let cmd = &args[1];

    // 连接服务端套接字
    let mut stream = UnixStream::connect(socket_path).await.unwrap();
    // 发送命令
    stream.write_all(cmd.as_bytes()).await.unwrap();
    // 关闭写端,告诉服务端已发送完毕
    stream.shutdown().await.unwrap();

    // 读取服务端响应
    let mut buffer = Vec::new();
    stream.read_to_end(&mut buffer).await.unwrap();
    print!("{}", std::str::from_utf8(&buffer).unwrap());
}

3. 用systemd管理守护进程

创建systemd服务配置文件/etc/systemd/system/my-service.service,实现服务的自动启动、生命周期管理:

[Unit]
Description=My Stateful Rust Service
After=network.target

[Service]
Type=simple
# 替换为你的服务可执行文件路径
ExecStart=/usr/local/bin/my-service
# 服务运行用户(根据需求调整,建议用非root用户)
User=nobody
Group=nogroup
# 服务退出后自动重启
Restart=always
# 标准输出/错误重定向到journald
StandardOutput=journal+console
StandardError=journal+console

[Install]
WantedBy=multi-user.target

systemd服务操作命令

  • 加载新配置:sudo systemctl daemon-reload
  • 启动服务:sudo systemctl start my-service
  • 设置开机自启:sudo systemctl enable my-service
  • 查看服务状态:sudo systemctl status my-service
  • 查看服务日志:journalctl -u my-service -f

使用流程

  1. 编译服务端和客户端:
# 编译服务端
cargo build --release --bin my-service
# 编译客户端
cargo build --release --bin service-cli
# 复制到系统目录(可选)
sudo cp target/release/my-service /usr/local/bin/
sudo cp target/release/service-cli /usr/local/bin/
  1. 启动服务:
sudo systemctl start my-service
  1. 非阻塞交互:
# 查询计数
service-cli get-count
# 捕获输出到变量
COUNT=$(service-cli get-count)
echo $COUNT

内容的提问来源于stack exchange,提问作者Kevin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 13:43:17