文盤Rust -- 把程式作為守護程式啟動
當我們寫完一個服務端程式,需要上線部署的時候,或多或少都會和作業系統的守護程式打交道,畢竟誰也不希望shell關閉既停服。今天我們就來聊聊這個事兒。
最早大家部署應用的通常操作是 “nohup xxxx &”,別說像weblogic 或者其他java 容器有啟動指令碼,裡面其實也差不多;很喜歡 nginx的 -d 引數,或者像redis 配置檔案裡可以指定是否以守護程式啟動。看起來很優雅。
那麼,使用rust 寫一個服務端程式能不能優雅的使用一個引數指定應用 daemon 模式啟動,同時使用stop 方式優雅的停機呢?我們透過一個例子來說說基本的實現方式。
例項程式碼依然整合在[interactcli-rs](https://github.com/jiashiwen/interactcli-rs)工程中。
首先來模擬一個啟動的服務程式 /src/server/server.rs
pub fn start(prefix: String) { for i in 0..1000 { println!("{}", prefix.clone() + &i.to_string()); thread::sleep(Duration::from_secs(1)); } }
程式每秒輸出一個字串,持續999秒,這個時間足夠驗證實驗結果了。
後臺啟動有兩個實現,分別是利用[fork](github.com/immortal/fork) 或 [daemonize](github.com/knsd/daemonize),這兩個crate 實現原理類似,但在使用上稍有不同。
/src/cmd/cmdserver.rs,構建了兩個啟動子命令,分別來呼叫 fork 和 daemonize的守護程式啟動實現.
pub fn new_server_cmd() -> Command { clap::Command::new("server") .about("server") .subcommand(server_start_byfork()) .subcommand(server_start_bydaemonize()) } pub fn server_start_byfork() -> Command { clap::Command::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::Command::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 實現的功能,bydaemonize 則呼叫透過 daemonize 的功能實現。
命令解析的程式碼在 /src/cmd/rootcmd.rs 檔案中。
先來看看基於 fork 的實現:
if let Some(startbyfork) = server.subcommand_matches("byfork") { println!("start by fork"); if startbyfork.get_flag("daemon") { let args: Vec<String> = env::args().collect(); if let Ok(Fork::Child) = daemon(true, false) { // 啟動子程式 let mut cmd = Command::new(&args[0]) for idx in 1..args.len() { let arg = args.get(idx).expect("get cmd arg error!"); // 去除後臺啟動引數,避免重複啟動 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::process::id()); println!("child id is:{}", child.id()); } println!("{}", "daemon mod"); process::exit(0); } start("by_fork:".to_string()); }
首先,透過 Fork::daemon 函式派生出一個子程式;然後解析一下當前命令,去掉 -d 引數,構建一個啟動命令,子命令啟動,退出父程式。這基本符合作業系統建立守護程式的過程 -- 兩次 fork。
再來看看基於 daemonize 的實現:
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::process::id()); fs::write("pid", process::id().to_string()).unwrap(); start("by_daemonize:".to_string()); }
首先獲取當前的工作目錄,預設情況下 daemonize 會將工作目錄設定為 "/",為了避免許可權問題,我們獲取當前目錄作為守護程式的工作目錄。不知道是什麼原因,在配置了pid_file 後,啟動守護程式時並沒在檔案中有記錄 pid。不過也沒關係,我們可以在外部獲取並記錄守護程式的pid。
兩種方式啟動的守護程式均可在關閉shell的情況下維持程式執行。
從實現上來講,不論是 fork 還是 daemonize 都是 透過unsafe 方式呼叫了 libc api,類 unix 系統大多跑起來沒問題,windows 系統作者沒有驗證。
本期關於守護程式的話題就聊到這兒。
我們們下期見。
作者:賈世聞
相關文章
- Linux守護程式的啟動方法2022-10-28Linux
- Linux 守護程式的啟動方法2016-02-28Linux
- 守護程式2020-11-09
- opentracker改造為daemon守護程式2019-01-15
- Node 程式守護2019-02-16
- rstatd守護程式2011-10-26
- gated 守護程式2009-08-14
- PHP實現守護程式的啟動和終止操作2015-03-06PHP
- Linux 守護程式和超級守護程式(xinetd)2015-02-17Linux
- 如何手動重啟 Node.js 和用守護程式自動重啟2019-11-17Node.js
- 程式守護 supervisor2020-06-07
- 守護程式那些事2019-06-25
- Linux 守護程式2021-06-06Linux
- 理解linux/unix作業系統守護程式(轉)2016-03-01Linux作業系統
- Python編寫守護程式程式2018-07-28Python
- Ventoy製作啟動盤和使用VMware測試啟動盤(論文版)2023-01-05
- PHP 實現守護程式2019-08-08PHP
- Golang 程式守護 Supervisor2021-09-16Golang
- PHP 編寫守護程式2019-01-17PHP
- wim檔案製作U盤啟動盤教程【圖文】2019-12-11
- 【Gin-API系列】守護程式和平滑重啟(八)2020-09-08API
- 怎樣把u盤製作成uefi啟動盤_最新uefi啟動u盤製作步驟詳解2021-08-11
- Linux守護程式及Systemd2021-08-16Linux
- Windows守護程式簡單示例2018-12-28Windows
- linux中守護程式啟停工具start-stop-daemon2017-10-17Linux
- 如何製作windows啟動盤 u盤製作win10啟動盤2021-12-31WindowsWin10
- rsync 守護程式及實時同步2021-12-29
- rsync 守護程式備份報錯2019-01-14
- Linux下的守護程式分析2018-06-25Linux
- 程式守護系統,你懂嗎?2016-11-01
- 用Python實現守護程式2017-05-11Python
- 深入理解Linux守護程式2014-03-25Linux
- 一個簡單的守護程式2014-07-25
- 深入理解Linux作業系統下的守護程式(轉)2018-12-07Linux作業系統
- 深入理解Linux作業系統下的守護程式(1)2009-02-15Linux作業系統
- 深入理解Linux作業系統下的守護程式(2)2009-02-15Linux作業系統
- win10系統u盤啟動盤怎麼製作【圖文】2021-08-11Win10
- UltraISO製作U盤啟動盤2015-08-30AI