英文:
How to reliably unlink() a Unix domain socket in Go programming language
问题
我有一个Go程序,在localhost:8080
上托管一个简单的HTTP服务,所以我可以通过proxy_pass
指令将我的公共nginx
主机连接到它,作为反向代理来为我的网站的部分请求提供服务。这一切都很好,没有问题。
我想将Go程序转换为在Unix域套接字上托管HTTP服务,而不是在本地TCP套接字上,以提高安全性并减少TCP的不必要的协议开销。
问题是Unix域套接字一旦被bind()
绑定后就无法重用,即使在程序终止后也是如此。第二次(以及之后的每一次)运行Go程序时,它都会因为致命错误"address already in use"
而退出。
常见的做法是在服务器关闭时unlink()
Unix域套接字(即删除文件)。然而,在Go中这是棘手的。我的第一次尝试是在主函数中使用defer
语句(见下文),但是如果我用CTRL-C之类的信号中断进程,它就不会运行。我想这是可以预料到的。令人失望,但并不意外。
有没有关于在服务器进程关闭时如何unlink()
套接字的最佳实践?
这是我func main()
的一部分,用于启动服务器监听的参考代码:
// 创建在请求的套接字上监听的HTTP服务器:
l, err := net.Listen("unix", "/tmp/mysocket")
if err != nil {
log.Fatal(err)
} else {
// Unix套接字在再次使用之前必须被unlink()。
// 不幸的是,当接收到信号时,比如CTRL-C,这个defer不会运行。
defer func() {
os.Remove("/tmp/mysocket")
}()
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
}
英文:
I have a Go program hosting a simple HTTP service on localhost:8080
so I can connect my public nginx
host to it via the proxy_pass
directive, as a reverse proxy to serve part of my site's requests. This is all working great, no problems there.
I want to convert the Go program to host the HTTP service on a Unix domain socket instead of a local TCP socket for improved security and to reduce the unnecessary protocol overhead of TCP.
PROBLEM:
The problem is that Unix domain sockets cannot be reused once they are bind()
to, even after program termination. The second time (and every time after) I run the Go program it exits with a fatal error "address already in use"
.
Common practice is to unlink()
Unix domain sockets (i.e. remove the file) when the server shuts down. However, this is tricky in Go. My first attempt was to use the defer
statement in my main func (see below), but it is not getting run if I interrupt the process with a signal like CTRL-C. I suppose this is to be expected. Disappointing, but not unexpected.
QUESTION: Is there a best practice on how to unlink()
the socket when the server process shuts down (either gracefully or ungracefully)?
Here's part of my func main()
that starts the server listening for reference:
// Create the HTTP server listening on the requested socket:
l, err := net.Listen("unix", "/tmp/mysocket")
if err != nil {
log.Fatal(err)
} else {
// Unix sockets must be unlink()ed before being reused again.
// Unfortunately, this defer is not run when a signal is received, e.g. CTRL-C.
defer func() {
os.Remove("/tmp/mysocket")
}()
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
}
答案1
得分: 8
这是我使用的完整解决方案。我在问题中发布的代码是一个简化版本,用于清晰地演示目的。
// 创建要监听的套接字:
l, err := net.Listen(socketType, socketAddr)
if err != nil {
log.Fatal(err)
return
}
// Unix套接字在再次重用之前必须unlink()。
// 处理常见的进程终止信号,以便我们可以优雅地关闭:
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM)
go func(c chan os.Signal) {
// 等待SIGINT或SIGKILL:
sig := <-c
log.Printf("捕获到信号%s:正在关闭。", sig)
// 停止监听(如果是unix类型,则unlink套接字):
l.Close()
// 完成:
os.Exit(0)
}(sigc)
// 启动HTTP服务器:
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
我真希望这是一段好的、有效的Go代码,能让Go的作者们感到自豪。对我来说,它看起来确实如此。如果不是这样的话,那将是我个人的尴尬。:)
对于任何好奇的人,这是https://github.com/JamesDunne/go-index-html的一部分,它是一个简单的HTTP目录列表生成器,具有一些Web服务器默认不提供的额外功能。
英文:
Here is the complete solution I used. The code I posted in my question was a simplified version for clear demonstration purposes.
// Create the socket to listen on:
l, err := net.Listen(socketType, socketAddr)
if err != nil {
log.Fatal(err)
return
}
// Unix sockets must be unlink()ed before being reused again.
// Handle common process-killing signals so we can gracefully shut down:
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, os.Interrupt, os.Kill, syscall.SIGTERM)
go func(c chan os.Signal) {
// Wait for a SIGINT or SIGKILL:
sig := <-c
log.Printf("Caught signal %s: shutting down.", sig)
// Stop listening (and unlink the socket if unix type):
l.Close()
// And we're done:
os.Exit(0)
}(sigc)
// Start the HTTP server:
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
I sure hope this is good and effective Go code that would make the Go authors proud. It certainly looks so to me. If it is not, that would be embarrassing on my part.
For anyone curious, this is part of https://github.com/JamesDunne/go-index-html which is a simple HTTP directory listing generator with some extra features that web servers don't give you out of the box.
答案2
得分: 2
您可以使用信号处理程序结束主函数,并为其他任务生成单独的Go协程。这样,您可以利用延迟机制,并清理所有(基于信号或非基于信号的)关闭操作:
func main() {
// 创建在请求的套接字上监听的HTTP服务器:
l, err := net.Listen("unix", "/tmp/mysocket")
if err != nil {
log.Fatal(err)
return
}
// 只需在此处使用延迟;只要信号处理程序在主Go协程中执行即可。
defer l.Close()
// 确保服务器不会阻塞主函数
go func() {
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
}()
// 使用缓冲通道以便不会错过任何信号
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
// 阻塞直到接收到信号。
s := <-c
fmt.Println("收到信号:", s)
// ...然后退出,运行所有延迟语句
}
英文:
You can end your main func with the signal handler and spawn separate go routines for your other tasks instead. That way, you can leverage the defer mechanism and handle all (signal-based or not) shut downs cleanly:
func main() {
// Create the HTTP server listening on the requested socket:
l, err := net.Listen("unix", "/tmp/mysocket")
if err != nil {
log.Fatal(err)
return
}
// Just work with defer here; this works as long as the signal handling
// happens in the main Go routine.
defer l.Close()
// Make sure the server does not block the main
go func() {
log.Fatal(http.Serve(l, http.HandlerFunc(indexHtml)))
}()
// Use a buffered channel so we don't miss any signals
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt, os.Kill, syscall.SIGTERM)
// Block until a signal is received.
s := <-c
fmt.Println("Got signal:", s)
// ...and exit, running all the defer statements
}
答案3
得分: 2
在现代Go中,您可以使用syscall.Unlink()
函数 - 文档在这里:
import (
"net"
"syscall"
...
)
...
socketpath := "/tmp/somesocket"
// 继续创建您的套接字:
addr, err := net.ResolveUnixAddr("unixgram", socketpath)
if err != nil {
return err;
}
// 如果存在,始终从文件系统中删除命名套接字
err = syscall.Unlink(socketpath)
if err != nil {
// 如果失败,这并不是非常重要
log.Error("Unlink()", err)
}
// 继续进行套接字绑定()
conn, err := net.ListenUnixgram("unixgram", addr);
if err != nil {
return err;
}
英文:
In modern Go, you may use the syscall.Unlink()
- docs here:
import (
"net"
"syscall"
...
)
...
socketpath := "/tmp/somesocket"
// carry on with your socket creation:
addr, err := net.ResolveUnixAddr("unixgram", socketpath)
if err != nil {
return err;
}
// always remove the named socket from the fs if its there
err = syscall.Unlink(socketpath)
if err != nil {
// not really important if it fails
log.Error("Unlink()",err)
}
// carry on with socket bind()
conn, err := net.ListenUnixgram("unixgram", addr);
if err != nil {
return err;
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论