├── listener.go ├── conn.go ├── README.md └── server.go /listener.go: -------------------------------------------------------------------------------- 1 | package godaemon 2 | 3 | import ( 4 | "net" 5 | "time" 6 | ) 7 | 8 | func (this *listener) Accept() (conn net.Conn, err error) { 9 | tc, err := this.Listener.(*net.TCPListener).AcceptTCP() 10 | if err != nil { 11 | return 12 | } 13 | tc.SetKeepAlive(true) 14 | tc.SetKeepAlivePeriod(3 * time.Minute) 15 | return tc, nil 16 | } 17 | 18 | //获取sock文件句柄 19 | func (this *listener) File() (uintptr, error) { 20 | f, err := this.Listener.(*net.TCPListener).File() 21 | if err != nil { 22 | return 0, err 23 | } 24 | return f.Fd(), nil 25 | } 26 | -------------------------------------------------------------------------------- /conn.go: -------------------------------------------------------------------------------- 1 | package godaemon 2 | 3 | import ( 4 | "net" 5 | "sync" 6 | "time" 7 | ) 8 | 9 | type ConnectionManager struct { 10 | sync.WaitGroup 11 | Counter int 12 | mux sync.Mutex 13 | idleConns map[string]net.Conn 14 | } 15 | 16 | func newConnectionManager() *ConnectionManager { 17 | cm := &ConnectionManager{} 18 | cm.WaitGroup = sync.WaitGroup{} 19 | cm.idleConns = make(map[string]net.Conn) 20 | return cm 21 | } 22 | 23 | func (cm *ConnectionManager) add(delta int) { 24 | cm.Counter += delta 25 | cm.WaitGroup.Add(delta) 26 | } 27 | 28 | func (cm *ConnectionManager) done() { 29 | cm.Counter-- 30 | cm.WaitGroup.Done() 31 | } 32 | 33 | func (cm *ConnectionManager) close(t time.Duration) { 34 | cm.mux.Lock() 35 | dt := time.Now().Add(t) 36 | for _, c := range cm.idleConns { 37 | c.SetDeadline(dt) 38 | } 39 | cm.idleConns = nil 40 | cm.mux.Unlock() 41 | cm.WaitGroup.Wait() 42 | return 43 | } 44 | 45 | func (cm *ConnectionManager) rmIdleConns(key string) { 46 | cm.mux.Lock() 47 | delete(cm.idleConns, key) 48 | cm.mux.Unlock() 49 | } 50 | 51 | func (cm *ConnectionManager) addIdleConns(key string, conn net.Conn) { 52 | cm.mux.Lock() 53 | cm.idleConns[key] = conn 54 | cm.mux.Unlock() 55 | } 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 为应用增加daemon和graceful 2 | 3 | 4 | ## 使用方法 5 | 6 | - 只增加daemon功能 7 | 8 | 要让你的应用支持daemon很简单,只需导入godaemon包即可,无需再调用任何方法 9 | 10 | ``` 11 | package main 12 | import( 13 | _ "github.com/tim1020/godaemon" //仅导入,包的init方法被自动调用,嵌入daemon功能 14 | ) 15 | func main(){ 16 | //正常的业务代码 17 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 18 | fmt.Fprintf(w, "pid=%d", os.Getpid()) 19 | }) 20 | http.ListenAndServe(":8080", nil) 21 | } 22 | ``` 23 | 24 | - 增加graceful 25 | 26 | ``` 27 | package main 28 | import( 29 | "github.com/tim1020/godaemon" //注意: 与仅daemon时不一样 30 | ) 31 | func main(){ 32 | mux1 := http.NewServeMux() 33 | mux1.HandleFunc("/", handler) 34 | godaemon.GracefulServe(":8080", mux1) 35 | } 36 | 37 | func handler(w http.ResponseWriter, r *http.Request){ 38 | //业务处理 39 | } 40 | ```` 41 | 42 | > 注意: 因为使用了syscall包,Win下不支持哦 43 | 44 | 45 | ## 命令行操作 46 | 47 | 使用godaemon后,你可以在命令行中以下列指令来管理你的应用: 48 | 49 | (假设你编译出来的应用执行文件叫“app”,如果你的应用本身需要带参数运行,请把daemon指令参数放在最后) 50 | 51 | - app [start] 52 | 53 | 带start参数或不带参数,启动为daemon进程 54 | 55 | - app restart 56 | 57 | 带restart参数,重启进程(GracefulServe时不中断服务,仅daemon时会先停止再启动) 58 | 59 | - app stop 60 | 61 | 带stop参数,停止应用进程 (kill -HUP) 62 | 63 | - app -h 64 | 65 | 带-h参数, 显示命令行指令提示 66 | 67 | 68 | > 带其它不识别的参数启动,godaemon不接管,直接短路至业务代码 69 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package godaemon 2 | 3 | import ( 4 | //"errors" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net" 9 | "net/http" 10 | "os" 11 | "os/signal" 12 | "path/filepath" 13 | "strconv" 14 | "syscall" 15 | "time" 16 | ) 17 | 18 | var ( 19 | TimeDeadLine = 10 * time.Second 20 | srv *server 21 | appName string 22 | pidFile string 23 | pidVal int 24 | ) 25 | 26 | //improvement http.Server 27 | type server struct { 28 | http.Server 29 | listener *listener 30 | cm *ConnectionManager 31 | } 32 | 33 | //用来重载net.Listener的方法 34 | type listener struct { 35 | net.Listener 36 | server *server 37 | } 38 | 39 | func init() { 40 | file, _ := filepath.Abs(os.Args[0]) 41 | appPath := filepath.Dir(file) 42 | appName = filepath.Base(file) 43 | pidFile = appPath + "/" + appName + ".pid" 44 | if os.Getenv("__Daemon") != "true" { //master 45 | cmd := "start" //缺省为start 46 | if l := len(os.Args); l > 1 { 47 | cmd = os.Args[l-1] 48 | } 49 | switch cmd { 50 | case "start": 51 | if isRunning() { 52 | log.Printf("[%d] %s is running\n", pidVal, appName) 53 | } else { //fork daemon进程 54 | if err := forkDaemon(); err != nil { 55 | log.Fatal(err) 56 | } 57 | } 58 | case "restart": //重启: 59 | if !isRunning() { 60 | log.Printf("%s not running\n", appName) 61 | } else { 62 | log.Printf("[%d] %s restart now\n", pidVal, appName) 63 | restart(pidVal) 64 | } 65 | case "stop": //停止 66 | if !isRunning() { 67 | log.Printf("%s not running\n", appName) 68 | } else { 69 | 70 | syscall.Kill(pidVal, syscall.SIGTERM) //kill 71 | } 72 | case "-h": 73 | fmt.Printf("Usage: %s start|restart|stop\n", appName) 74 | default: //其它不识别的参数 75 | return //返回至调用方 76 | } 77 | //主进程退出 78 | os.Exit(0) 79 | } 80 | go handleSignals() 81 | } 82 | 83 | //检查pidFile是否存在以及文件里的pid是否存活 84 | func isRunning() bool { 85 | if mf, err := os.Open(pidFile); err == nil { 86 | pid, _ := ioutil.ReadAll(mf) 87 | pidVal, _ = strconv.Atoi(string(pid)) 88 | } 89 | running := false 90 | if pidVal > 0 { 91 | if err := syscall.Kill(pidVal, 0); err == nil { //发一个信号为0到指定进程ID,如果没有错误发生,表示进程存活 92 | running = true 93 | } 94 | } 95 | return running 96 | } 97 | 98 | //保存pid 99 | func savePid(pid int) error { 100 | file, err := os.OpenFile(pidFile, os.O_CREATE|os.O_WRONLY, os.ModePerm) 101 | if err != nil { 102 | return err 103 | } 104 | defer file.Close() 105 | file.WriteString(strconv.Itoa(pid)) 106 | return nil 107 | } 108 | 109 | //捕获系统信号 110 | func handleSignals() { 111 | signals := make(chan os.Signal) 112 | signal.Notify(signals, syscall.SIGHUP, syscall.SIGTERM, syscall.SIGINT) 113 | var err error 114 | for { 115 | sig := <-signals 116 | switch sig { 117 | case syscall.SIGHUP: //重启 118 | if srv != nil { 119 | err = srv.fork() 120 | } else { //only deamon时不支持kill -HUP,因为可能监听地址会占用 121 | log.Printf("[%d] %s stopped.", os.Getpid(), appName) 122 | os.Remove(pidFile) 123 | os.Exit(2) 124 | } 125 | if err != nil { 126 | log.Fatalln(err) 127 | } 128 | case syscall.SIGINT: 129 | fallthrough 130 | case syscall.SIGTERM: 131 | log.Printf("[%d] %s stop graceful", os.Getpid(), appName) 132 | if srv != nil { 133 | srv.shutdown() 134 | } else { 135 | log.Printf("[%d] %s stopped.", os.Getpid(), appName) 136 | } 137 | os.Exit(1) 138 | } 139 | } 140 | } 141 | 142 | //forkDaemon,当checkPid为true时,检查是否有存活的,有则不执行 143 | func forkDaemon() error { 144 | args := os.Args 145 | os.Setenv("__Daemon", "true") 146 | procAttr := &syscall.ProcAttr{ 147 | Env: os.Environ(), 148 | Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd()}, 149 | } 150 | pid, err := syscall.ForkExec(os.Args[0], args, procAttr) 151 | if err != nil { 152 | return err 153 | } 154 | log.Printf("[%d] %s start daemon\n", pid, appName) 155 | savePid(pid) 156 | return nil 157 | } 158 | 159 | //重启(先发送kill -HUP到运行进程,手工重启daemon ...当有运行的进程时,daemon不启动) 160 | func restart(pid int) { 161 | syscall.Kill(pid, syscall.SIGHUP) //kill -HUP, daemon only时,会直接退出 162 | fork := make(chan bool, 1) 163 | go func() { //循环,查看pidFile是否存在,不存在或值已改变,发送消息 164 | for { 165 | f, err := os.Open(pidFile) 166 | if err != nil || os.IsNotExist(err) { //文件已不存在 167 | fork <- true 168 | break 169 | } else { 170 | pidVal, _ := ioutil.ReadAll(f) 171 | if strconv.Itoa(pid) != string(pidVal) { 172 | fork <- false 173 | break 174 | } 175 | } 176 | time.Sleep(500 * time.Millisecond) 177 | } 178 | }() 179 | //处理结果 180 | select { 181 | case r := <-fork: 182 | if r { 183 | forkDaemon() 184 | } 185 | case <-time.After(time.Second * 5): 186 | log.Fatalln("restart timeout") 187 | } 188 | 189 | } 190 | 191 | //处理http.Server,使支持graceful stop/restart 192 | func Graceful(s http.Server) error { 193 | os.Setenv("__GRACEFUL", "true") 194 | srv = &server{ 195 | cm: newConnectionManager(), 196 | Server: s, 197 | } 198 | srv.ConnState = func(conn net.Conn, state http.ConnState) { 199 | switch state { 200 | case http.StateNew: 201 | srv.cm.add(1) 202 | case http.StateActive: 203 | srv.cm.rmIdleConns(conn.LocalAddr().String()) 204 | case http.StateIdle: 205 | srv.cm.addIdleConns(conn.LocalAddr().String(), conn) 206 | case http.StateHijacked, http.StateClosed: 207 | srv.cm.done() 208 | } 209 | } 210 | l, err := srv.getListener() 211 | if err == nil { 212 | err = srv.Server.Serve(l) 213 | } 214 | return err 215 | } 216 | 217 | //使用addr和handler来启动一个支持graceful的服务 218 | func GracefulServe(addr string, handler http.Handler) error { 219 | s := http.Server{ 220 | Addr: addr, 221 | Handler: handler, 222 | } 223 | return Graceful(s) 224 | } 225 | 226 | //获取listener 227 | func (this *server) getListener() (*listener, error) { 228 | var l net.Listener 229 | var err error 230 | if os.Getenv("_GRACEFUL_RESTART") == "true" { //grace restart出来的进程,从FD FILE获取 231 | f := os.NewFile(3, "") 232 | l, err = net.FileListener(f) 233 | syscall.Kill(syscall.Getppid(), syscall.SIGTERM) //发信号给父进程,让父进程停止服务 234 | } else { //初始启动,监听addr 235 | l, err = net.Listen("tcp", this.Addr) 236 | } 237 | if err == nil { 238 | this.listener = &listener{ 239 | Listener: l, 240 | server: this, 241 | } 242 | } 243 | return this.listener, err 244 | } 245 | 246 | //fork一个新的进程 247 | func (this *server) fork() error { 248 | os.Setenv("_GRACEFUL_RESTART", "true") 249 | lFd, err := this.listener.File() 250 | if err != nil { 251 | return err 252 | } 253 | execSpec := &syscall.ProcAttr{ 254 | Env: os.Environ(), 255 | Files: []uintptr{os.Stdin.Fd(), os.Stdout.Fd(), os.Stderr.Fd(), lFd}, 256 | } 257 | pid, err := syscall.ForkExec(os.Args[0], os.Args, execSpec) 258 | if err != nil { 259 | return err 260 | } 261 | savePid(pid) 262 | log.Printf("[%d] %s fork ok\n", pid, appName) 263 | return nil 264 | } 265 | 266 | //关闭服务 267 | func (this *server) shutdown() { 268 | this.SetKeepAlivesEnabled(false) 269 | this.cm.close(TimeDeadLine) 270 | this.listener.Close() 271 | log.Printf("[%d] %s stopped.", os.Getpid(), appName) 272 | } 273 | --------------------------------------------------------------------------------