如何在Go中停止一个监听服务器

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

How do I stop a Listening server in Go

问题

我一直在尝试找到一种优雅地停止Go中的监听服务器的方法。因为listen.Accept会阻塞,所以关闭监听套接字以发出结束信号是必要的,但是我无法将相关错误与其他错误区分开,因为相关错误没有被导出。

我能做得比这更好吗?请参见下面代码中的serve()中的FIXME

package main

import (
	"io"
	"log"
	"net"
	"time"
)

// Echo server struct
type EchoServer struct {
	listen net.Listener
	done   chan bool
}

// Respond to incoming connection
//
// Write the address connected to then echo
func (es *EchoServer) respond(remote *net.TCPConn) {
	defer remote.Close()
	_, err := io.Copy(remote, remote)
	if err != nil {
		log.Printf("Error: %s", err)
	}
}

// Listen for incoming connections
func (es *EchoServer) serve() {
	for {
		conn, err := es.listen.Accept()
		// FIXME I'd like to detect "use of closed network connection" here
		// FIXME but it isn't exported from net
		if err != nil {
			log.Printf("Accept failed: %v", err)
			break
		}
		go es.respond(conn.(*net.TCPConn))
	}
	es.done <- true
}

// Stop the server by closing the listening listen
func (es *EchoServer) stop() {
	es.listen.Close()
	<-es.done
}

// Make a new echo server
func NewEchoServer(address string) *EchoServer {
	listen, err := net.Listen("tcp", address)
	if err != nil {
		log.Fatalf("Failed to open listening socket: %s", err)
	}
	es := &EchoServer{
		listen: listen,
		done:   make(chan bool),
	}
	go es.serve()
	return es
}

// Main
func main() {
	log.Println("Starting echo server")
	es := NewEchoServer("127.0.0.1:18081")
	// Run the server for 1 second
	time.Sleep(1 * time.Second)
	// Close the server
	log.Println("Stopping echo server")
	es.stop()
}

这将打印:

2012/11/16 12:53:35 Starting echo server
2012/11/16 12:53:36 Stopping echo server
2012/11/16 12:53:36 Accept failed: accept tcp 127.0.0.1:18081: use of closed network connection

我想隐藏Accept failed消息,但显然我不想掩盖Accept可能报告的其他错误。我当然可以在错误测试中查找use of closed network connection,但那将非常丑陋。我可以设置一个标志,表示我即将关闭并忽略错误,如果设置了该标志的话 - 是否有更好的方法?

英文:

I've been trying to find a way to stop a listening server in Go gracefully. Because listen.Accept blocks it is necessary to close the listening socket to signal the end, but I can't tell that error apart from any other errors as the relevant error isn't exported.

Can I do better than this? See FIXME in the code below in serve()

package main
import (
&quot;io&quot;
&quot;log&quot;
&quot;net&quot;
&quot;time&quot;
)
// Echo server struct
type EchoServer struct {
listen net.Listener
done   chan bool
}
// Respond to incoming connection
//
// Write the address connected to then echo
func (es *EchoServer) respond(remote *net.TCPConn) {
defer remote.Close()
_, err := io.Copy(remote, remote)
if err != nil {
log.Printf(&quot;Error: %s&quot;, err)
}
}
// Listen for incoming connections
func (es *EchoServer) serve() {
for {
conn, err := es.listen.Accept()
// FIXME I&#39;d like to detect &quot;use of closed network connection&quot; here
// FIXME but it isn&#39;t exported from net
if err != nil {
log.Printf(&quot;Accept failed: %v&quot;, err)
break
}
go es.respond(conn.(*net.TCPConn))
}
es.done &lt;- true
}
// Stop the server by closing the listening listen
func (es *EchoServer) stop() {
es.listen.Close()
&lt;-es.done
}
// Make a new echo server
func NewEchoServer(address string) *EchoServer {
listen, err := net.Listen(&quot;tcp&quot;, address)
if err != nil {
log.Fatalf(&quot;Failed to open listening socket: %s&quot;, err)
}
es := &amp;EchoServer{
listen: listen,
done:   make(chan bool),
}
go es.serve()
return es
}
// Main
func main() {
log.Println(&quot;Starting echo server&quot;)
es := NewEchoServer(&quot;127.0.0.1:18081&quot;)
// Run the server for 1 second
time.Sleep(1 * time.Second)
// Close the server
log.Println(&quot;Stopping echo server&quot;)
es.stop()
}

This prints

2012/11/16 12:53:35 Starting echo server
2012/11/16 12:53:36 Stopping echo server
2012/11/16 12:53:36 Accept failed: accept tcp 127.0.0.1:18081: use of closed network connection

I'd like to hide the Accept failed message, but obviously I don't want to mask other errors Accept can report. I could of course look in the error test for use of closed network connection but that would be really ugly. I could set a flag saying I'm about to close and ignore errors if that was set I suppose - Is there a better way?

答案1

得分: 16

我会使用es.done来发送一个信号,在关闭连接之前处理它。除了下面的代码,你需要使用make(chan bool, 1)来创建es.done,这样我们就可以在不阻塞的情况下放入一个单一的值。

// 监听传入的连接
func (es *EchoServer) serve() {
for {
conn, err := es.listen.Accept()
if err != nil {
select {
case <-es.done:
// 如果我们调用了stop(),那么es.done中将会有一个值,所以我们会到达这里,可以退出而不显示错误。
default:
log.Printf("Accept failed: %v", err)
}
return
}
go es.respond(conn.(*net.TCPConn))
}
}

// 通过关闭监听来停止服务器
func (es *EchoServer) stop() {
es.done <- true // 我们可以继续执行,因为我们给它一个缓冲区为1
es.listen.Close() // 现在Accept将会有一个错误
}

英文:

I would handle this by using es.done to send a signal before it closes the connection. In addition to the following code you'd need to create es.done with make(chan bool, 1) so that we can put a single value in it without blocking.

// Listen for incoming connections
func (es *EchoServer) serve() {
for {
conn, err := es.listen.Accept()
if err != nil {
select {
case &lt;-es.done:
// If we called stop() then there will be a value in es.done, so
// we&#39;ll get here and we can exit without showing the error.
default:
log.Printf(&quot;Accept failed: %v&quot;, err)
}
return
}
go es.respond(conn.(*net.TCPConn))
}
}
// Stop the server by closing the listening listen
func (es *EchoServer) stop() {
es.done &lt;- true   // We can advance past this because we gave it buffer of 1
es.listen.Close() // Now it the Accept will have an error above
}

答案2

得分: 8

accept()调用之后,在你的循环中检查一些“是否是停止的时间”标志,然后从你的main中翻转它,然后连接到你的监听端口以解除服务器套接字的“卡住”状态。这与旧的“自管道技巧”非常相似。

英文:

Check some "is it time to stop" flag in your loop right after the accept() call, then flip it from your main, then connect to your listening port to get server socket "un-stuck". This is very similar to the old "self-pipe trick".

答案3

得分: 2

func (es *EchoServer) serve() {
for {
conn, err := es.listen.Accept()
if err != nil {
if x, ok := err.(*net.OpError); ok && x.Op == "accept" { // 我们完成了
log.Print("停止")
break
}

        log.Printf("接受失败:%v", err)
continue
}
go es.respond(conn.(*net.TCPConn))
}
es.done <- true

}

英文:

Something among these lines might work in this case, I hope:

// Listen for incoming connections
func (es *EchoServer) serve() {
for {
conn, err := es.listen.Accept()
if err != nil {
if x, ok := err.(*net.OpError); ok &amp;&amp; x.Op == &quot;accept&quot; { // We&#39;re done
log.Print(&quot;Stoping&quot;)
break
}
log.Printf(&quot;Accept failed: %v&quot;, err)
continue
}
go es.respond(conn.(*net.TCPConn))
}
es.done &lt;- true
}

答案4

得分: -7

这是一个足够简单的方法,适用于本地开发。

http://www.sergiotapia.me/how-to-stop-your-go-http-server/


package main
import (
"net/http"
"os"
"github.com/bmizerany/pat"
)
var mux = pat.New()
func main() {
mux.Get("/kill", http.HandlerFunc(kill))
http.Handle("/", mux)
http.ListenAndServe(":8080", nil)
}
func kill(w http.ResponseWriter, r *http.Request) {
os.Exit(0)
}
英文:

Here's a simple way that's good enough for local development.

http://www.sergiotapia.me/how-to-stop-your-go-http-server/


package main
import (  
&quot;net/http&quot;
&quot;os&quot;
&quot;github.com/bmizerany/pat&quot;
)
var mux = pat.New()
func main() {  
mux.Get(&quot;/kill&quot;, http.HandlerFunc(kill))
http.Handle(&quot;/&quot;, mux)
http.ListenAndServe(&quot;:8080&quot;, nil)
}
func kill(w http.ResponseWriter, r *http.Request) {  
os.Exit(0)
}

huangapple
  • 本文由 发表于 2012年11月16日 20:57:53
  • 转载请务必保留本文链接:https://go.coder-hub.com/13417095.html
匿名

发表评论

匿名网友

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

确定