golang 后臺(tái)進(jìn)程的啟動(dòng)和停止操作
啟動(dòng)命令
我們先來(lái)個(gè)非后臺(tái)運(yùn)行的啟動(dòng)命令
func init() { startCmd := &cobra.Command{ Use: "start", Short: "Start Gonne", Run: func(cmd *cobra.Command, args []string) { startHttp() }, } startCmd.Flags().BoolVarP(&daemon, "deamon", "d", false, "is daemon?") RootCmd.AddCommand(startCmd) }
startHttp方法啟動(dòng)一個(gè)http的web服務(wù)
func startHttp() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, "Hello cmd!") }) if err := http.ListenAndServe(":9090", nil); err != nil { log.Fatal("ListenAndServe: ", err) } }
現(xiàn)在通過(guò)gonne start便可以啟動(dòng)一個(gè)web服務(wù)了,但是程序停留在命令行,如果ctrl+C程序也會(huì)終止了
命令行參數(shù)
如果想要后臺(tái)啟動(dòng),那么得讓start命令知道是要后臺(tái)運(yùn)行的,參照docker命令行的方式就是加上-d,給一個(gè)命令添加參數(shù)的判斷只需很少的代碼
改造一下代碼
func init() { var daemon bool startCmd := &cobra.Command{ Use: "start", Short: "Start Gonne", Run: func(cmd *cobra.Command, args []string) { if daemon { fmt.Println("gonne start",daemon) } startHttp() }, } startCmd.Flags().BoolVarP(&daemon, "deamon", "d", false, "is daemon?") RootCmd.AddCommand(startCmd) }
命令行輸入
gonne start -d
這樣就可以接收到-d參數(shù)了,這里要說(shuō)明一下,第一個(gè)參數(shù)取值,第二個(gè)參數(shù)代碼--deamon,第三個(gè)參數(shù)代表-d
第四個(gè)參數(shù)代碼不加-d時(shí)候的默認(rèn)值,第五參數(shù)是描述
后臺(tái)運(yùn)行
后臺(tái)運(yùn)行其實(shí)這里使用的是一個(gè)巧妙的方法,就是使用系統(tǒng)的command命令行啟動(dòng)自己的命令行輸入,是不是有點(diǎn)繞,再看看看改造后的代碼
Run: func(cmd *cobra.Command, args []string) { if daemon { command := exec.Command("gonne", "start") command.Start() fmt.Printf("gonne start, [PID] %d running...\n", command.Process.Pid) ioutil.WriteFile("gonne.lock", []byte(fmt.Sprintf("%d", command.Process.Pid)), 0666) daemon = false os.Exit(0) } else { fmt.Println("gonne start") } startHttp() },
用exec的Command啟動(dòng)剛輸入的gonne start -d,就會(huì)攔截到這條請(qǐng)求然后通過(guò)gonne start,但是程序就不會(huì)停留在命令行了,然后發(fā)現(xiàn)http服務(wù)還在,還可以訪問(wèn)。
還有一點(diǎn)就是把pid輸出到gonne.lock文件,給停止的程序調(diào)用
終止后臺(tái)程序
有了之前的操作后,停止就簡(jiǎn)單多了
func init() { RootCmd.AddCommand(stopCmd) } var stopCmd = &cobra.Command{ Use: "stop", Short: "Stop Gonne", Run: func(cmd *cobra.Command, args []string) { strb, _ := ioutil.ReadFile("gonne.lock") command := exec.Command("kill", string(strb)) command.Start() println("gonne stop") }, }
執(zhí)行 gonne stop 即可終止之前啟動(dòng)的http服務(wù)
help命令
好了,關(guān)于命令的操作講完了,再看看cobra給的福利,自動(dòng)生成的help命令
這個(gè)不需要你做什么操作,只需要輸入gonne help,相關(guān)信息已經(jīng)幫你生產(chǎn)好了。
appletekiMacBook-Pro:andev apple$ gonne help Usage: gonne [flags] gonne [command] Available Commands: help Help about any command start Start Gonne stop Stop Gonne version Print the version number of Gonne Flags: -h, --help help for gonne Use "gonne [command] --help" for more information about a command.
當(dāng)然,子命令也有
appletekiMacBook-Pro:andev apple$ gonne start -h Start Gonne Usage: gonne start [flags] Flags: -d, --deamon is daemon? -h, --help help for start
自此告別各種腳本!
補(bǔ)充:golang子進(jìn)程的啟動(dòng)和停止,mac與linux的區(qū)別
今天接到一個(gè)任務(wù)是將原來(lái)運(yùn)行在mac的應(yīng)用移植到linux,原因當(dāng)然是因?yàn)榭蛻裟沁叜?dāng)前是linux環(huán)境,也不想再采購(gòu)mac電腦。
通常來(lái)說(shuō),這個(gè)工作并不難,因?yàn)槲疫x用的服務(wù)器端技術(shù)是c或者golang,這兩種技術(shù)具有很好的可移植性,而且大多是重新編譯即可運(yùn)行,所以接到任務(wù)的開(kāi)始并沒(méi)有把這個(gè)當(dāng)一回事。
跟想象中的也差不多,搭建好linux測(cè)試服務(wù)器,在mac上把運(yùn)行很久的應(yīng)用重新交叉編譯了一遍,部署到linux實(shí)驗(yàn)環(huán)境,啟動(dòng)、測(cè)試,看起來(lái)一切正常。準(zhǔn)備打包交活,這時(shí)候發(fā)現(xiàn)一個(gè)問(wèn)題,程序無(wú)法終止。
簡(jiǎn)單調(diào)試后就找到了原因,在系統(tǒng)中啟動(dòng)的子進(jìn)程,發(fā)出終止信號(hào)之后居然仍在運(yùn)行,導(dǎo)致父進(jìn)程也一直無(wú)法退出,尷尬了。
列一下采用的代碼(代碼為簡(jiǎn)化版僅供示例):
func startChild1() { cmd := exec.Command("/bin/sh", "-c", "sleep 1000") time.AfterFunc(10*time.Second, func() { fmt.Println("PID1=", cmd.Process.Pid) syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT) fmt.Println("killed") }) fmt.Println("begin run") cmd.Run() }
示例代碼首先啟動(dòng)一個(gè)sleep的子進(jìn)程,表示某個(gè)子業(yè)務(wù)開(kāi)始工作,然后延時(shí)10秒鐘之后,把這個(gè)子進(jìn)程殺死。
這段代碼啟動(dòng)子進(jìn)程和關(guān)閉子進(jìn)程在mac電腦的原有系統(tǒng)上工作都很正常,但是到了linux,啟動(dòng)子進(jìn)程仍然沒(méi)有問(wèn)題,關(guān)閉子進(jìn)程不成功。
檢查了一下在linux的工作過(guò)程,發(fā)現(xiàn)啟動(dòng)子進(jìn)程之后,實(shí)際上是啟動(dòng)了兩個(gè)進(jìn)程,一個(gè)進(jìn)程是/bin/sh,隨后sh又啟動(dòng)了一個(gè)子進(jìn)程自身的子進(jìn)程sleep。
而發(fā)出退出命令的時(shí)候,只有sh退出了,sleep進(jìn)程仍然繼續(xù)運(yùn)行。對(duì)比同樣的mac電腦上,sh進(jìn)程是沒(méi)有出現(xiàn)的,只有一個(gè)sleep進(jìn)程,所以發(fā)出退出命令的時(shí)候,sleep正常關(guān)閉,系統(tǒng)表現(xiàn)正常。
使用/bin/sh來(lái)啟動(dòng)另外的命令行程序是有原因的,這源于golang本身的設(shè)計(jì),golang的exec.Command,后面第一個(gè)參數(shù)是命令行程序本身,之后的每一個(gè)exec.Command參數(shù),都代表命令行程序的一個(gè)參數(shù),而不是我們常用的,命令行程序路徑和參數(shù)都可以寫(xiě)在一個(gè)字符串,用空格隔開(kāi)即可。
所以有的時(shí)候我們是為了省事,也有的時(shí)候是順手移植了別的語(yǔ)言的代碼,就使用/bin/sh來(lái)啟動(dòng)需要的命令行程序,就如同上面示例代碼一樣,這樣情況下,除了-c參數(shù)要單獨(dú)占用一個(gè)字符串,我們?cè)疽獑?dòng)的字符串程序及其參數(shù),就可以如同常見(jiàn)語(yǔ)言處理方式那樣,放在一個(gè)字符串了。
我們可以嘗試一下這個(gè)代碼:
func startChild2() { cmd := exec.Command("sleep", "1000") time.AfterFunc(10*time.Second, func() { fmt.Println("PID2=", cmd.Process.Pid) syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT) fmt.Println("killed") }) fmt.Println("begin run") cmd.Run() }
測(cè)試一下,這段代碼因?yàn)闆](méi)有經(jīng)過(guò)/bin/sh程序,在linux上也只有sleep這一個(gè)進(jìn)程被建立,直接向其發(fā)出退出指令是可以正常工作的。這從進(jìn)程的觀察中及實(shí)驗(yàn)的結(jié)果中,都可以證實(shí)我們的判斷。
知道了原因,處理起來(lái)也很容易,一是把程序改成類(lèi)似上面這樣的方式啟動(dòng)進(jìn)程。另外一個(gè)辦法則是直接為/bin/sh及我們的命令行進(jìn)程建立一個(gè)進(jìn)程組,這樣最后發(fā)出的指令退出這個(gè)進(jìn)程組,同樣可以同時(shí)退出/bin/sh及sleep兩個(gè)進(jìn)程,達(dá)到我們的需求。
寫(xiě)代碼測(cè)試一下:
func startChild3() { cmd := exec.Command("/bin/sh", "-c", "sleep 1000") cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} time.AfterFunc(10*time.Second, func() { fmt.Println("PID3=", cmd.Process.Pid) syscall.Kill(-cmd.Process.Pid, syscall.SIGQUIT) fmt.Println("killed") }) fmt.Println("begin run") cmd.Run() }
經(jīng)過(guò)實(shí)際測(cè)試,這段代碼在不改變?cè)械拿钚袇?shù)傳遞習(xí)慣的基礎(chǔ)上,可以正常在linux及mac電腦順利執(zhí)行。
最后再說(shuō)一下命令cmd.Process.Signal,golang文檔上說(shuō)的很清楚,這是向進(jìn)程發(fā)送消息信號(hào),比如同樣的syscall.SIGQUIT,這也是告訴子進(jìn)程退出的意思。
所以大多的應(yīng)用中,我們希望一個(gè)進(jìn)程退出,直接用:
cmd.Process.Signal(syscall.SIGQUIT)
也是可以正常執(zhí)行的,但對(duì)于我們上面說(shuō)的情況,如果先使用/bin/sh啟動(dòng)了另外一個(gè)子進(jìn)程,這種方法就無(wú)效了(指在linux無(wú)效,mac測(cè)試是一樣可以用的,關(guān)鍵區(qū)別同樣是在mac,/bin/sh進(jìn)程不會(huì)保留并等待我們啟動(dòng)的子進(jìn)程退出,所以退出消息可以正常的發(fā)送到正常的子進(jìn)程)。
所以為了跨平臺(tái)的通用性,建議還是使用Process.Kill或者syscall.Kill來(lái)殺死子進(jìn)程。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
golang之?dāng)?shù)據(jù)校驗(yàn)的實(shí)現(xiàn)代碼示例
這篇文章主要介紹了golang之?dāng)?shù)據(jù)校檢的實(shí)現(xiàn)代碼示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類(lèi)型操作
這篇文章主要介紹了golang 實(shí)現(xiàn)interface{}轉(zhuǎn)其他類(lèi)型操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12GoFrame通用類(lèi)型變量gvar與interface基本使用對(duì)比
這篇文章主要為大家介紹了GoFrame通用類(lèi)型變量gvar與interface基本使用對(duì)比,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go語(yǔ)言利用ffmpeg轉(zhuǎn)hls實(shí)現(xiàn)簡(jiǎn)單視頻直播
這篇文章主要為大家介紹了Go語(yǔ)言利用ffmpeg轉(zhuǎn)hls實(shí)現(xiàn)簡(jiǎn)單視頻直播,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04