
在Go语言中,实现一个监听网络连接的服务器通常涉及一个循环,不断调用net.Listener.Accept()来接受新连接。当需要实现服务的优雅关闭时,一个常见的思路是使用select语句结合一个关闭通道(closeChan)来接收关闭信号。然而,Accept()是一个阻塞操作,如果直接将其放入select的default分支,会导致CPU空转。为了避免这种情况,有时会配合net.Listener.SetDeadline()设置一个超时,使得Accept()在指定时间后返回错误,从而允许select有机会检查closeChan。
以下是这种模式的一个示例:
type Server struct {
listener net.Listener
closeChan chan struct{} // 使用空结构体更节省内存
routines sync.WaitGroup
}
func (s *Server) Serve() {
s.routines.Add(1)
defer s.routines.Done()
defer s.listener.Close() // 确保listener在协程退出时关闭
for {
select {
case <-s.closeChan:
// 收到关闭信号,准备退出
fmt.Println("Server received close signal, shutting down...")
return // 退出Serve协程
default:
// 设置Accept的超时,以避免长时间阻塞
s.listener.SetDeadline(time.Now().Add(2 * time.Second))
conn, err := s.listener.Accept()
if err != nil {
// 检查是否是超时错误,如果是则继续循环
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
continue
}
// 其他错误(如listener已关闭),则退出
fmt.Printf("Error accepting connection: %v\n", err)
return
}
// 处理连接的逻辑,通常在一个新的goroutine中
s.routines.Add(1)
go func(conn net.Conn) {
defer s.routines.Done()
defer conn.Close()
// handle conn logic
fmt.Printf("Handling connection from %s\n", conn.RemoteAddr())
time.Sleep(1 * time.Second) // 模拟处理
}(conn)
}
}
}
func (s *Server) Close() {
close(s.closeChan) // 发送关闭信号
s.routines.Wait() // 等待所有协程完成
fmt.Println("All server routines finished.")
}这种实现方式的缺点在于,当调用Close()函数发送关闭信号时,Serve()协程并不会立即退出。它必须等待当前的SetDeadline超时(例如2秒)结束后,Accept()返回错误或超时,select语句才能再次执行并检查closeChan。这意味着服务的关闭时间至少会比实际需要的时间多出这个超时时长,影响了服务的响应性和优雅性。
Go语言提供了一种更符合其并发哲学且更高效的优雅关闭机制。其核心思想是利用net.Listener.Close()方法的一个关键特性:当一个net.Listener被关闭时,所有当前正在阻塞等待Accept()调用的协程都会立即解除阻塞,并返回一个错误(通常是net.ErrClosed或类似“use of closed network connection”的错误)。我们可以利用这个特性来作为监听协程的退出信号,从而避免设置人工超时。
立即学习“go语言免费学习笔记(深入)”;
以下是改进后的惯用模式:
import (
"fmt"
"net"
"sync"
"time"
)
type ImprovedServer struct {
listener net.Listener
closeOnce sync.Once // 确保Close操作只执行一次
routines sync.WaitGroup
// closeChan用于在外部触发关闭,但Serve内部不再直接监听它
// 相反,它用于通知一个专门的goroutine来关闭listener
closeChan chan struct{}
}
// NewImprovedServer 创建一个新的服务器实例
func NewImprovedServer(addr string) (*ImprovedServer, error) {
lis, err := net.Listen("tcp", addr)
if err != nil {
return nil, fmt.Errorf("failed to listen: %w", err)
}
return &ImprovedServer{
listener: lis,
closeChan: make(chan struct{}),
}, nil
}
func (s *ImprovedServer) Serve() {
s.routines.Add(1)
defer s.routines.Done()
// 启动一个独立的goroutine来监听关闭信号并关闭listener
go func() {
<-s.closeChan // 阻塞直到接收到关闭信号
fmt.Println("Closing listener...")
s.listener.Close() // 关闭listener,这将使Accept()立即返回错误
}()
fmt.Printf("Server listening on %s\n", s.listener.Addr())
for {
conn, err := s.listener.Accept()
if err != nil {
// 检查错误是否是由于listener关闭引起的
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {
fmt.Println("Listener closed, exiting Serve routine.")
return // Listener已关闭,退出Serve协程
}
// 针对其他非关闭引起的错误,进行日志记录或处理
fmt.Printf("Error accepting connection: %v\n", err)
// 根据实际情况,可能需要决定是继续循环还是退出
// 这里我们假设其他错误也应导致退出,或者在重试策略后退出
return
}
// 处理连接的逻辑,通常在一个新的goroutine中
s.routines.Add(1)
go func(conn net.Conn) {
defer s.routines.Done()
defer conn.Close()
// handle conn logic
fmt.Printf("Handling connection from %s\n", conn.RemoteAddr())
time.Sleep(1 * time.Second) // 模拟处理
}(conn)
}
}
func (s *ImprovedServer) Close() {
s.closeOnce.Do(func() {
fmt.Println("Initiating server shutdown...")
close(s.closeChan) // 发送关闭信号给专门的goroutine
s.routines.Wait() // 等待所有协程完成,包括Serve和所有连接处理协程
fmt.Println("Improved server gracefully shut down.")
})
}
func main() {
server, err := NewImprovedServer(":8080")
if err != nil {
fmt.Fatalf("Failed to create server: %v", err)
}
go server.Serve()
// 模拟服务器运行一段时间后关闭
time.Sleep(5 * time.Second)
server.Close()
// 确保main协程不会立即退出,以便观察输出
time.Sleep(1 * time.Second)
}在这个改进的模式中:
这种方法的优势在于:
在Go语言中,实现一个高效且优雅的事件监听器并支持快速关闭,应充分利用net.Listener.Close()方法在Accept()调用中触发错误返回的特性。通过将Accept()循环与关闭listener的逻辑分离到不同的协程中,可以实现零延迟的服务关闭,避免了传统方案中因SetDeadline带来的不必要等待。这种模式不仅符合Go的并发哲学,也使得代码更加简洁、健壮和易于维护。
以上就是Go语言中实现优雅且高效的事件监听与服务关闭的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号