package main import ( "context" "fmt" "log" "net" "net/http" "os" "os/exec" "os/signal" "syscall" "time" "flag" ) var ( listener net.Listener err error server http.Server graceful = flag.Bool("g", false, "listen on fd open 3 (internal use only)") ) func init(){ log.SetFlags(log.Ldate | log.Lshortfile) } // MyHandler 控制台 type MyHandler struct { } func (*MyHandler)ServeHTTP(w http.ResponseWriter, r *http.Request){ log.Println("request start at ", time.Now(), r.URL.Path+"?"+r.URL.RawQuery, "request done at ", time.Now(), " pid:", os.Getpid()) time.Sleep(10 * time.Second) fmt.Fprintln(w,"cout") log.Println("request done at ", time.Now(), " pid:", os.Getpid() ) log.Println(r.RemoteAddr) } func main() { flag.Parse() fmt.Println("start-up at " , time.Now(), *graceful) if *graceful { // 这暗藏杀机,先复制fd到新的fd, 然后设置子进程exec时自动关闭父进程的fd,即“F_DUPFD_CLOEXEC” // 要是接收到信号后,不执行这里,就会发生端口被占用的错误 f := os.NewFile(3, "") listener, err = net.FileListener(f) fmt.Printf( "graceful-reborn %v %v %#v \n", f.Fd(), f.Name(), listener) }else{ listener, err = net.Listen("tcp", ":8080") } server := http.Server{ Handler: &MyHandler{}, ReadTimeout: 6 * time.Second, } log.Printf("Actual pid is %d\n", syscall.Getpid()) if err != nil { log.Println(err) return } log.Printf(" listener: %v\n", listener) go func(){//不要阻塞主进程 err := server.Serve(listener) if err != nil { log.Println(err) } }() //signals func(){ ch := make(chan os.Signal, 1) signal.Notify(ch, syscall.SIGINT) for{ //阻塞主进程, 不停的监听系统信号 sig := <- ch log.Printf("signal: %v", sig) ctx, _ := context.WithTimeout(context.Background(), 20*time.Second) // 这里就是上下文 switch sig { // 我用终止信号,做例子,大家可以使用别的信号 case syscall.SIGINT: println("signal cause reloading") signal.Stop(ch) { // fork子进程 tl, ok := listener.(*net.TCPListener) if !ok { log.Println("listener is not tcp listener") return } // 接收父进程的socket文件描述符 currentFD, err := tl.File() if err != nil { log.Println("acquiring listener file failed") return } // 好坑啊,这里,“-g"是必须的,因为再次运行之后,g=true cmd := exec.Command(os.Args[0],"-g") log.Println(cmd.Args) // currentFD 这里是子进程了,fork的时候,将socket文件描述传给子进程 // 父进程将socket文件描述符传递给子进程可以通过命令行 // f := os.NewFile(3, "") []*os.File{currentFD} 想到对应 cmd.ExtraFiles, cmd.Stdout,cmd.Stderr = []*os.File{currentFD} ,os.Stdout, os.Stderr err = cmd.Start() // 重启运行 if err != nil { log.Println("cmd.Start fail: ", err) return } log.Println("forked new pid : ",cmd.Process.Pid) } // 阻塞到父进程将所有的请求处理完毕 server.Shutdown(ctx) // 优雅的等待关闭 log.Println("graceful shutdown at ", time.Now()) } } }() }