當(dāng)我們寫完一個服務(wù)端程序,需要上線部署的時候,或多或少都會和操作系統(tǒng)的守護(hù)進(jìn)程打交道,畢竟誰也不希望shell關(guān)閉既停服。今天我們就來聊聊這個事兒。
最早大家部署應(yīng)用的通常操作是 “nohup xxxx &”,別說像weblogic 或者其他java 容器有啟動腳本,里面其實(shí)也差不多;很喜歡 nginx的 -d 參數(shù),或者像redis 配置文件里可以指定是否以守護(hù)進(jìn)程啟動??雌饋砗軆?yōu)雅。
那么,使用rust 寫一個服務(wù)端程序能不能優(yōu)雅的使用一個參數(shù)指定應(yīng)用 daemon 模式啟動,同時使用stop 方式優(yōu)雅的停機(jī)呢?我們通過一個例子來說說基本的實(shí)現(xiàn)方式。
實(shí)例代碼依然集成在[interactcli-rs](https://github.com/jiashiwen/interactcli-rs)工程中。
首先來模擬一個啟動的服務(wù)進(jìn)程 /src/server/server.rs
```rust pub fn start(prefix: String) { for i in 0..1000 { println!("{}", prefix.clone() + &i.to_string()); thread::sleep(Duration::from_secs(1)); } } ```程序每秒輸出一個字符串,持續(xù)999秒,這個時間足夠驗(yàn)證實(shí)驗(yàn)結(jié)果了。
后臺啟動有兩個實(shí)現(xiàn),分別是利用[fork](github.com/immortal/fork) 或 [daemonize](github.com/knsd/daemonize),這兩個crate 實(shí)現(xiàn)原理類似,但在使用上稍有不同。
/src/cmd/cmdserver.rs,構(gòu)建了兩個啟動子命令,分別來調(diào)用 fork 和 daemonize的守護(hù)進(jìn)程啟動實(shí)現(xiàn)。
```rust pub fn new_server_cmd() -> Command { clap::new("server") .about("server") .subcommand(server_start_byfork()) .subcommand(server_start_bydaemonize()) } pub fn server_start_byfork() -> Command { clap::new("byfork") .about("start daemon by fork crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), ) } pub fn server_start_bydaemonize() -> Command { clap::new("bydaemonize") .about("start daemon by daemonize crate") .arg( Arg::new("daemon") .short('d') .long("daemon") .action(ArgAction::SetTrue) .help("start as daemon") .required(false), ) } ```server 的子命令 byfork 啟動 通過 fork 實(shí)現(xiàn)的功能,bydaemonize 則調(diào)用通過 daemonize 的功能實(shí)現(xiàn)。
命令解析的代碼在 /src/cmd/rootcmd.rs 文件中。
先來看看基于 fork 的實(shí)現(xiàn):
```rust if let Some(startbyfork) = server.subcommand_matches("byfork") { println!("start by fork"); if startbyfork.get_flag("daemon") { let args: Vec= env::args().collect(); if let Ok(Fork::Child) = daemon(true, false) { // 啟動子進(jìn)程 let mut cmd = Command::new(&args[0]) for idx in 1..args.len() { let arg = args.get(idx).expect("get cmd arg error!"); // 去除后臺啟動參數(shù),避免重復(fù)啟動 if arg.eq("-d") || arg.eq("-daemon") { continue; } cmd.arg(arg); let child = cmd.spawn().expect("Child process failed to start."); fs::write("pid", child.id().to_string()).unwrap(); println!("process id is:{}", std::id()); println!("child id is:{}", child.id()); } println!("{}", "daemon mod"); process::exit(0); } start("by_fork:".to_string()); } ```
首先,通過 Fork::daemon 函數(shù)派生出一個子進(jìn)程;然后解析一下當(dāng)前命令,去掉 -d 參數(shù),構(gòu)建一個啟動命令,子命令啟動,退出父進(jìn)程。這基本符合操作系統(tǒng)創(chuàng)建守護(hù)進(jìn)程的過程 -- 兩次 fork。
再來看看基于 daemonize 的實(shí)現(xiàn):
```rust if let Some(startbydaemonize) = server.subcommand_matches("bydaemonize") { println!("start by daemonize"); let base_dir = env::current_dir().unwrap(); if startbydaemonize.get_flag("daemon") { let stdout = File::create("/tmp/daemon.out").unwrap(); let stderr = File::create("/tmp/daemon.err").unwrap(); println!("{:?}", base_dir); let daemonize = Daemonize::new() .pid_file("/tmp/test.pid") // Every method except `new` and `start` .chown_pid_file(true) // is optional, see `Daemonize` documentation .working_directory(base_dir.as_path()) // for default behaviour. .umask(0o777) // Set umask, `0o027` by default. .stdout(stdout) // Redirect stdout to `/tmp/daemon.out`. .stderr(stderr) // Redirect stderr to `/tmp/daemon.err`. .privileged_action(|| "Executed before drop privileges"); match daemonize.start() { Ok(_) => { println!("Success, daemonized"); } Err(e) => eprintln!("Error, {}", e), } } println!("pid is:{}", std::id()); fs::write("pid", process::id().to_string()).unwrap(); start("by_daemonize:".to_string()); } ```
首先獲取當(dāng)前的工作目錄,默認(rèn)情況下 daemonize 會將工作目錄設(shè)置為 "/",為了避免權(quán)限問題,我們獲取當(dāng)前目錄作為守護(hù)進(jìn)程的工作目錄。不知道是什么原因,在配置了pid_file 后,啟動守護(hù)進(jìn)程時并沒在文件中有記錄 pid。不過也沒關(guān)系,我們可以在外部獲取并記錄守護(hù)進(jìn)程的pid。
兩種方式啟動的守護(hù)進(jìn)程均可在關(guān)閉shell的情況下維持進(jìn)程運(yùn)行。
從實(shí)現(xiàn)上來講,不論是 fork 還是 daemonize 都是 通過unsafe 方式調(diào)用了 libc api,類 unix 系統(tǒng)大多跑起來沒問題,windows 系統(tǒng)作者沒有驗(yàn)證。
本期關(guān)于守護(hù)進(jìn)程的話題就聊到這兒。
咱們下期見。
審核編輯:湯梓紅
-
程序
+關(guān)注
關(guān)注
117文章
3785瀏覽量
81001 -
Rust
+關(guān)注
關(guān)注
1文章
228瀏覽量
6598
原文標(biāo)題:文盤Rust -- 把程序作為守護(hù)進(jìn)程啟動
文章出處:【微信號:Rust語言中文社區(qū),微信公眾號:Rust語言中文社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論