当我们写完一个服务器端的程序,需要在线部署的时候,我们或多或少会和操作系统的守护进程打交道。毕竟没有人希望果壳关闭,停止服务。今天就来说说这个吧。
最早部署应用的通常操作是“nohup xxxx”,更何况还有类似weblogic或者其他java容器的启动脚本,其实都差不多;我很喜欢nginx的-d参数,或者你可以在redis配置文件中指定是否作为守护进程启动。看起来很优雅。
那么,是否可以通过使用一个参数来指定要启动的应用程序守护模式,同时使用stop模式来优雅地停止,从而使用rust编写一个服务器端程序呢?下面通过一个例子来说一下基本的实现。
该范例仍被纳入[国际气候变化倡议-遥感](https://github.com/Jiashiwen/InteractCLI-RS)项目。
首先,模拟一个已启动的服务流程/src /src/server/server.rs
` ` rustpub fn start(前缀:String) { for i in 0.1000 { println!(' {} 'prefix . clone()I . to _ string());thread:sleep(Duration:from _ secs(1));}} ` ` `` `程序每秒输出一个字符串,持续999秒,足够验证实验结果的时间。
后台启动有两种实现方式,分别是使用[fork](github.com/immediate/fork)或者[daemon](github . com/KNSD/daemon)。这两种板条箱实现在原理上是相似的,但在使用上略有不同。
/src/cmd/cmdserver.rs,构造两个promoter命令分别调用fork和daemon的守护启动。
` ` rustpub fn new _ server _ cmd()-Command { clap:new(' server ')。关于(“服务器”)。子命令(server_start_byfork())。sub Command(server _ start _ bydaemonize())} pub fn server _ start _ by fork()-Command { clap:new(' by fork ')。关于('通过叉子板条箱启动守护程序')。arg( Arg:new('daemon ')。短(' d ')。long('daemon ')。action(ArgAction:SetTrue)。帮助(“作为守护程序启动”)。必选(false),)} pub fn server _ start _ bydaemonize()-Command { clap:new(' bydaemonize ')。about('通过守护化机箱启动守护程序')。arg( Arg:new('daemon ')。短(' d ')。long ('daemon ')。action (argaction: set true)。帮助(“作为守护程序启动”)。必选(false),)} ````` server的子命令byfork启动通过fork实现的函数,bydaemonize调用通过daemon实现的函数。
该命令的解析代码在/src /src/cmd/rootcmd.rs文件中
我们先来看看基于fork的实现:
` ` rustif let Some(startbyfork)=server . sub command _ matches(' by fork '){ println!(‘由叉开始’);if startbyfork . get _ flag(' daemon '){ let args:Vec=env:args()。collect();if let ok(fork:child)=daemon(true,false){//promoter process let mut cmd=command:new(args[0])for id Xin 1.args.len () {let arg=args.get (idx)。expect ('get cmd arg error!');//删除后台启动参数,避免ifarg . eq('-d ')| | arg . eq('-daemon '){ continue;} cmd . arg(arg);let child=cmd.spawn()。expect('子进程无法启动。);fs:write('pid 'child.id()。to_string())。unwrap();println!('进程id为:{} 'STD:id());println!('子id为:{} 'child . id());} println!(' {} ''守护程序模式');process:exit(0);} start('by_fork:'to _ string());}```
首先通过Fork:daemon函数派生出一个子进程;然后分析当前命令,删除-d参数,并构建一个开始命令。子命令启动和退出父进程。这基本符合操作系统创建守护进程的流程——fork两次。
让我们来看看基于daemonize的实现:
` ` rustif let Some(startbydaemonize)=server。sub command _ matches(' bydaemonize '){ println!('由妖魔化开始');设base_dir=env:current_dir().unwrap();如果startbydaemonize。get _ flag(' daemon '){ let stdout=File:create('/tmp/daemon。out’).unwrap();let stderr=File:create('/tmp/daemon。err’).unwrap();println!('{:} 'base _ dir);设daemonize=Daemonize:new().pid_file('/tmp/test.pid') //除"新的"和"开始"之外的所有方法chown_pid_file(true) //是可选的,请参见“妖魔化”文档working _ directory(base _ dir。as _ path())//默认行为umask(0o777) //设置umask,默认为0o 027 ' stdout(stdout)//将标准输出重定向到`/tmp/daemon.out `标准错误(stderr) //将标准错误重定向到`/tmp/daemon.err `特权_操作(|| '在'删除权限'之前执行);匹配daemonize。start(){ Ok(_)={ println!('成功,妖魔化');} Err(e)=eprintln!(' Error,{ } ' e),} } println!(' pid为:{} 'STD:id());fs:write('pid 'process:id().to_string()).unwrap();开始(' by_daemonize:').to _ string());}```
首先获取当前的工作目录,默认情况下妖魔化会将工作目录设置为'/'为了避免权限问题,我们获取当前目录作为守护进程的工作目录。不知道是什么原因,在配置了pid _文件后,启动守护进程时并没在文件中有记录pid。不过也没关系,我们可以在外部获取并记录守护进程的pid。
两种方式启动的守护进程均可在关闭壳的情况下维持进程运行。
从实现上来讲,不论是叉还是妖魔化都是通过危险的方式调用了libc api,类unix操作系统操作系统系统大多跑起来没问题,windows系统作者没有验证。
本期关于守护进程的话题就聊到这儿。
咱们下期见。
审核汤梓红
标签:进程fork程序