优雅地关闭多个服务器

huangapple go评论119阅读模式
英文:

Gracefully shutting down multiple servers

问题

我有一个应用程序,运行一个基本的HTTP服务器,并且还接受TCP连接。

基本的伪代码如下:

package main

import (
    "log"
    "net"
    "net/http"
)

func main() {
    // 创建HTTP服务器
    serveSvr := http.NewServeMux()
    serveSvr.HandleFunc("/", handler())

    // 创建服务器错误通道
    svrErr := make(chan error)

    // 启动HTTP服务器
    go func() {
        svrErr <- http.ListenAndServe(":8080", serveSvr)
    }()

    // 启动TCP服务器
    go func() {
        lnr, err := net.Listen("tcp", ":1111")
        if err != nil {
            svrErr <- err
            return
        }
        defer lnr.Close()

        for {
            conn, err := lnr.Accept()
            if err != nil {
                log.Printf("连接错误:%v", err)
                continue
            }
            // 处理每个连接的代码
        }
    }()

    select {
    case err := <-svrErr:
        log.Print(err)
    }
}

我在单独的goroutine中运行这两个服务器,如果其中任何一个失败,我需要一种优雅地关闭它们的方法。例如,如果HTTP服务器出错,我该如何返回并关闭TCP服务器/执行任何清理操作?

英文:

I have an application that runs a basic HTTP server and also accepts connections over TCP.

Basic pseudo code is as follows:

package main

import (
	&quot;log&quot;
	&quot;net&quot;
	&quot;net/http&quot;
)

func main() {
	// create serve HTTP server.
	serveSvr := http.NewServeMux()
	serveSvr.HandleFunc(&quot;/&quot;, handler())

	// create server error channel
	svrErr := make(chan error)

	// start HTTP server.
	go func() {
		svrErr &lt;- http.ListenAndServe(&quot;:8080&quot;, serveSvr)
	}()

	// start TCP server
	go func() {
		lnr, err := net.Listen(&quot;tcp&quot;, &quot;:1111&quot;)
		if err != nil {
			svrErr &lt;- err
			return
		}
		defer lnr.Close()

		for {
			conn, err := lnr.Accept()
			if err != nil {
				log.Printf(&quot;connection error: %v&quot;, err)
				continue
			}
			// code to handle each connection
		}
	}()

	select {
	case err := &lt;-svrErr:
		log.Print(err)
	}
}

I run both servers in separate goroutines and I need a way to gracefully shut them both down if either of them fail. For example; if the HTTP server errors, how would I go back and shutdown the TCP server/perform any cleanup?

答案1

得分: 1

首先,保留对HTTP服务器和TCP监听器的引用,以便稍后关闭它们。

创建单独的错误通道,以便知道哪个路径返回了错误,并对它们进行缓冲,以便始终能够完成发送。

为了确保在退出之前完成任何想要尝试的清理工作,可以向服务器goroutine添加一个WaitGroup。

一个简单扩展你的示例可能如下所示:

var wg sync.WaitGroup

// 创建HTTP服务器。
serveSvr := http.NewServeMux()
serveSvr.HandleFunc("/", handler())
server := &http.Server{Addr: ":8080", Handler: serveSvr}

// 创建HTTP服务器错误通道。
httpErr := make(chan error, 1)

// 启动HTTP服务器。
wg.Add(1)
go func() {
	defer wg.Done()
	httpErr <- server.ListenAndServe()
	// HTTP清理
}()

tcpErr := make(chan error, 1)
listener, err := net.Listen("tcp", ":1111")
if err != nil {
	tcpErr <- err
} else {
	// 启动TCP服务器。
	wg.Add(1)
	go func() {
		defer wg.Done()
		defer listener.Close()
		for {
			conn, err := listener.Accept()
			if err != nil {
				if ne, ok := err.(net.Error); ok && ne.Temporary() {
					// 临时错误,等待并继续
					continue
				}
				tcpErr <- err

				// 清理TCP
				return
			}

			// 处理每个连接的代码
		}
	}()
}
select {
case err := <-httpErr:
	// 处理HTTP错误并关闭TCP监听
	if listener != nil {
		listener.Close()
	}
case err := <-tcpErr:
	// 处理TCP错误并关闭HTTP服务器
	server.Close()
}

// 你可能还想接收关闭的服务器的错误以进行日志记录

// 等待任何最终清理完成
wg.Wait()
英文:

Start by keeping a reference to the http server and the tcp listener so that you can later close them.

Create separate error channels so you know which path returned the error, and buffer them so that a send can always complete.

To make sure that whatever cleanup you want to attempt is complete before you exit, you can add a WaitGroup to the server goroutines.

I simple extension of your example might look like:

var wg sync.WaitGroup
// create  HTTP server.
serveSvr := http.NewServeMux()
serveSvr.HandleFunc(&quot;/&quot;, handler())
server := &amp;http.Server{Addr: &quot;:8080&quot;, Handler: serveSvr}
// create http server error channel
httpErr := make(chan error, 1)
// start HTTP server.
wg.Add(1)
go func() {
defer wg.Done()
httpErr &lt;- server.ListenAndServe()
// http cleanup
}()
tcpErr := make(chan error, 1)
listener, err := net.Listen(&quot;tcp&quot;, &quot;:1111&quot;)
if err != nil {
tcpErr &lt;- err
} else {
// start TCP server
wg.Add(1)
go func() {
defer wg.Done()
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
if ne, ok := err.(net.Error); ok &amp;&amp; ne.Temporary() {
// temp error, wait and continue
continue
}
tcpErr &lt;- err
// cleanup TCP
return
}
// code to handle each connection
}
}()
}
select {
case err := &lt;-httpErr:
// handle http error and close tcp listen
if listener != nil {
listener.Close()
}
case err := &lt;-tcpErr:
// handle tcp error and close http server
server.Close()
}
// you may also want to receive the error from the server
// you shutdown to log
// wait for any final cleanup to finish
wg.Wait()

huangapple
  • 本文由 发表于 2017年4月18日 21:19:28
  • 转载请务必保留本文链接:https://go.coder-hub.com/43473411.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定