【问题标题】:How do I stop a Listening server in Go如何在 Go 中停止监听服务器
【发布时间】:2012-11-05 04:59:52
【问题描述】:

我一直在尝试找到一种方法来优雅地停止 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 的错误测试,但这真的很难看。我可以设置一个标志,说我即将关闭并忽略错误,如果设置了我想 - 有更好的方法吗?

【问题讨论】:

标签: sockets networking go listen


【解决方案1】:

accept() 调用后立即检查循环中的一些“是时候停止”标志,然后将其从main 翻转,然后连接 到您的侦听端口以获取服务器套接字“不卡住”。这与旧的"self-pipe trick" 非常相似。

【讨论】:

  • 这是个好主意!有一个真正的连接进入的竞争条件,你需要解决这个问题。
  • 是的,没错。您可以尝试与之相反的方式 - 从一开始就始终拥有单个内部连接并将其用作控制通道。
  • 伟大而简单!
【解决方案2】:

我希望这些行中的某些内容可能适用于这种情况:

// Listen for incoming connections
func (es *EchoServer) serve() {
        for {
                conn, err := es.listen.Accept()
                if err != nil {
                    if x, ok := err.(*net.OpError); ok && x.Op == "accept" { // We're done
                            log.Print("Stoping")
                            break
                    }

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

【讨论】:

  • 好主意,谢谢!我不确定它是否能区分Syscall.Accept() 的正式停止返回错误(我最近看到的一个示例是进程耗尽套接字)会不会?
  • 不知道,我必须尝试/试验一下。
  • 很好的例子,但不应将 x.Opclose 进行比较,例如&amp;&amp; x.Op == "close" 如果我们使用listener.Close() 停止监听器,它似乎把close 放在错误操作里面。 godoc.org/net#TCPListener.Close
【解决方案3】:

我会通过使用 es.done 在关闭连接之前发送信号来处理这个问题。除了以下代码之外,您还需要使用 make(chan bool, 1) 创建 es.done,以便我们可以在其中放入单个值而不会阻塞。

// Listen for incoming connections
func (es *EchoServer) serve() {
  for {
    conn, err := es.listen.Accept()
    if err != nil {
      select {
      case <-es.done:
        // If we called stop() then there will be a value in es.done, so
        // we'll get here and we can exit without showing the error.
      default:
        log.Printf("Accept failed: %v", err)
      }
      return
    }
    go es.respond(conn.(*net.TCPConn))
  }
}

// Stop the server by closing the listening listen
func (es *EchoServer) stop() {
  es.done <- 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
}

【讨论】:

  • 嗯,好主意。我认为bool 与使用频道一样好,但这几乎是我想出的解决方案。你仍然需要频道来同步stopserve,这样你就知道它什么时候停止了。
  • 不要费心缓冲通道或沿通道发送消息。关闭它。
【解决方案4】:

这里有一个简单的方法,对于本地开发来说已经足够好了。

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)
}

【讨论】:

  • 退出整个进程不是关闭服务器的有效方式!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-14
  • 2013-08-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多