发布于 2026-01-06 1 阅读
0

理解 Golang 中的 Unix 域套接字

理解 Golang 中的 Unix 域套接字

在 Go 语言中,套接字是一种通信端点,它允许程序通过网络发送和接收数据。Go 语言中主要有两种类型的套接字:Unix 域套接字 (AF_UNIX) 和网络套接字 (AF_INET|AF_INET6)。本文将探讨这两种套接字之间的一些区别。

Unix 域套接字(又称本地套接字)用于同一台机器上进程间的通信。它们使用基于文件的接口,可以像访问普通文件一样通过文件系统路径进行访问。在某些情况下,Unix 域套接字比网络套接字更快更高效,因为它们不需要网络协议和通信的开销。它们通常用于进程间通信 (IPC) 以及同一台机器上不同服务之间的通信。数据通过文件系统作为通信通道在进程间传输。

文件系统为进程间数据传输提供了一种可靠高效的机制。进程间的通信仅由内核参与。进程通过读写同一个套接字文件进行通信,该文件由内核管理。内核负责处理通信细节,例如同步、缓冲和错误处理,并确保数据可靠且按正确顺序传递。

这与网络套接字不同,网络套接字通信涉及内核、网络协议栈和网络硬件。进程使用网络套接字中的网络协议(例如 TCP/UDP)来建立连接并通过网络传输数据。内核和网络协议栈负责处理通信细节,例如路由、寻址和纠错。网络硬件则负责数据在网络上的物理传输。

net.Dial在 Go 语言中,可以使用`client` 或net.Listen`server` 函数并指定网络类型来创建 Unix 域套接字unix。例如,以下代码创建一个 Unix 域套接字并监听传入的连接:

// Create a Unix domain socket and listen for incoming connections.
socket, err := net.Listen("unix", "/tmp/mysocket.sock")
if err != nil {
    panic(err)
}

Enter fullscreen mode Exit fullscreen mode

我们来看一个如何在 Golang 中使用 Unix 域套接字的例子。以下代码创建了一个简单的回显服务器,它使用 Unix 域套接字:

package main
import (...)

func main() {
    // Create a Unix domain socket and listen for incoming connections.
    socket, err := net.Listen("unix", "/tmp/echo.sock")
    if err != nil {
        log.Fatal(err)
    }

    // Cleanup the sockfile.
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        os.Remove("/tmp/echo.sock")
        os.Exit(1)
    }()

    for {
        // Accept an incoming connection.
        conn, err := socket.Accept()
        if err != nil {
            log.Fatal(err)
        }

        // Handle the connection in a separate goroutine.
        go func(conn net.Conn) {
            defer conn.Close()
            // Create a buffer for incoming data.
            buf := make([]byte, 4096)

            // Read data from the connection.
            n, err := conn.Read(buf)
            if err != nil {
                log.Fatal(err)
            }

            // Echo the data back to the connection.
            _, err = conn.Write(buf[:n])
            if err != nil {
                log.Fatal(err)
            }
        }(conn)
    }
}

Enter fullscreen mode Exit fullscreen mode

让我们使用上述回显服务器进行测试netcat,您可以使用该-U选项指定套接字文件。这样您就可以连接到套接字并通过它发送和接收数据。

$ echo "I'm a Kungfu Dev" | nc -U /tmp/echo.sock
I'm a Kungfu Dev
Enter fullscreen mode Exit fullscreen mode

另一方面,网络套接字用于不同机器上的进程之间进行通信。它们使用诸如 TCP 和 UDP 之类的网络协议。网络套接字比 Unix 域套接字更通用,因为它们可以与连接到网络的任何机器上的进程进行通信。它们通常用于客户端-服务器通信,例如 Web 服务器和客户端应用程序。

net.Dial在 Go 语言中,网络套接字使用 ` create` 或 ` set` 函数创建net.Listen,网络类型可以是 TCPTCP或 UDP。例如,以下代码创建一个 TCP 套接字并监听传入的连接:

// Create a TCP socket and listen for incoming connections.
socket, err := net.Listen("tcp", ":8000")
if err != nil {
    panic(err)
}
Enter fullscreen mode Exit fullscreen mode

从客户角度进行基本分析

网络套接字的客户端 pprof

Type: cpu
...
(pprof) list main.main
...
ROUTINE ======================== main.main in /Users/douglasmakey/go/src/github.com/douglasmakey/go-sockets-uds-network-pprof/server_echo_network_socket/client/main.go
         0      530ms (flat, cum) 70.67% of Total 
         .          .     16: 
         .          .     17:   pprof.StartCPUProfile(f) 
         .          .     18:   defer pprof.StopCPUProfile() 
         .          .     19: 
         .          .     20:   for i := 0; i < 10000; i++ { 
         .      390ms     21:          conn, err := net.Dial("tcp", "localhost:3000") 
         .          .     22:          if err != nil { 
         .          .     23:                  log.Fatal(err) 
         .          .     24:          } 
         .          .     25: 
         .          .     26:          msg := "Hello" 
         .       40ms     27:          if _, err := conn.Write([]byte(msg)); err != nil { 
         .          .     28:                  log.Fatal(err) 
         .          .     29:          } 
         .          .     30: 
         .          .     31:          b := make([]byte, len(msg)) 
         .      100ms     32:          if _, err := conn.Read(b); err != nil { 
         .          .     33:                  log.Fatal(err) 
         .          .     34:          } 
         .          .     35:   } 
         .          .     36:}
Enter fullscreen mode Exit fullscreen mode

客户端 pprof 用于 Unix 套接字

Type: cpu
...
(pprof) list main.main
...
ROUTINE ======================== main.main in /Users/douglasmakey/go/src/github.com/douglasmakey/go-sockets-uds-network-pprof/server_echo_unix_domain_socket/client/main.go
         0      210ms (flat, cum) 80.77% of Total 
         .          .     16: 
         .          .     17:   pprof.StartCPUProfile(f) 
         .          .     18:   defer pprof.StopCPUProfile() 
         .          .     19: 
         .          .     20:   for i := 0; i < 10000; i++ { 
         .      130ms     21:          conn, err := net.Dial("unix", "/tmp/echo.sock") 
         .          .     22:          if err != nil { 
         .          .     23:                  log.Fatal(err) 
         .          .     24:          } 
         .          .     25: 
         .          .     26:          msg := "Hello" 
         .       40ms     27:          if _, err := conn.Write([]byte(msg)); err != nil { 
         .          .     28:                  log.Fatal(err) 
         .          .     29:          } 
         .          .     30: 
         .          .     31:          b := make([]byte, len(msg)) 
         .       40ms     32:          if _, err := conn.Read(b); err != nil { 
         .          .     33:                  log.Fatal(err) 
         .          .     34:          } 
         .          .     35:   } 
         .          .     36:}
Enter fullscreen mode Exit fullscreen mode

这些基本概况中需要注意的事项包括:

  • 打开 Unix 套接字比打开网络套接字快得多。
  • 从 Unix 套接字读取数据比从网络套接字读取数据快得多。

GitHub 仓库

使用 Unix 域套接字连接 HTTP 服务器

要在 Go 中使用 Unix 域套接字和 HTTP 服务器,可以使用该server.Serve函数并指定net.Listener要监听的套接字。

package main
import (...)

const socketPath = "/tmp/httpecho.sock"
func main() {
    // Create a Unix domain socket and listen for incoming connections.
    socket, err := net.Listen("unix", socketPath)
    if err != nil {
        panic(err)
    }

    // Cleanup the sockfile.
    c := make(chan os.Signal, 1)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        os.Remove(socketPath)
        os.Exit(1)
    }()

    m := http.NewServeMux()
    m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello kung fu developer! "))
    })

    server := http.Server{
        Handler: m,
    }

    if err := server.Serve(socket); err != nil {
        log.Fatal(err)
    }
}

Enter fullscreen mode Exit fullscreen mode

测试一下

$ curl -s -N --unix-socket /tmp/httpecho.sock http://localhost/
Hello kung fu developer!
Enter fullscreen mode Exit fullscreen mode

在 Go 中使用带有 Unix 域套接字的 HTTP 服务器可以带来诸多优势,例如本文中提到的安全性、性能、易用性和互操作性提升。这些优势能够使 HTTP 服务器的实现更加高效、可靠和可扩展,尤其适用于需要在同一台机器上进行通信的进程。

安全方面如何保障?

Unix 域套接字和网络套接字具有不同的安全特性。一般来说,Unix 域套接字比网络套接字更安全,因为它们不暴露在网络上,并且只能由同一台机器上的进程访问。

Unix 域套接字的主要安全特性之一是利用文件系统权限来控制访问。套接字文件以特定用户和组的身份创建,并且只有拥有该用户和组相应权限的进程才能访问。这意味着只有授权的进程才能连接到套接字并交换数据。

相比之下,网络套接字暴露在网络中,任何连接到该网络的计算机都可以访问它们。这使得它们容易受到恶意攻击者(例如黑客和恶意软件)的攻击。网络套接字使用网络协议,例如 TCP/UDP。这些协议本身具有安全机制,例如加密和身份验证。然而,这些机制并非总能抵御所有类型的攻击,网络套接字仍然可能被攻破。

该选哪个?

在 Go 语言中,选择使用 Unix 域套接字还是网络套接字时,必须考虑应用程序的需求和限制。Unix 域套接字速度更快、效率更高,但仅限于同一台机器上的进程间通信。网络套接字用途更广泛,但开销更大,并且容易受到网络延迟和可靠性问题的影响。一般来说,Unix 域套接字适用于进程间通信 (IPC) 和同一台机器上的服务间通信,而网络套接字适用于通过网络进行的客户端-服务器通信。

当需要在同一主机上的进程之间进行通信时,应选择 Unix 域套接字而非网络套接字。Unix 域套接字为同一主机上的进程提供安全高效的通信通道,适用于进程需要频繁或实时交换数据的场景。

例如,假设你有一个 K8s pod,其中有两个容器,它们必须尽快相互通信!

总之,Unix 域套接字和网络套接字是 Go 语言中两种重要的套接字类型,它们用于不同的目的和场景。理解这两种套接字类型之间的差异和优缺点对于在 Go 语言中设计和实现高效可靠的网络应用程序至关重要。

请记住,Unix 套接字进程之间的通信完全由内核处理,而网络套接字通信则涉及内核、网络协议栈和网络硬件。

文章来源:https://dev.to/douglasmakey/understanding-unix-domain-sockets-in-golang-32n8