Unix Domain Socket简介

Unix Domain Socket简介

Unix socket(也称为 Unix 域套接字)是一种用于同一台主机上进程间通信(IPC)的机制。与常规网络套接字不同,Unix socket 不依赖于网络协议,并且只能用于在同一台机器上的进程之间通信。这使得 Unix socket 比网络套接字更快和更有效,因为数据不需要通过网络协议栈发送。

Unix socket 通常被创建为文件系统上的特殊文件,并使用文件描述符来管理通信通道。它们通常在服务器应用程序中使用,用于不同部分之间的通信,例如在同一台机器上运行的 Web 服务器和数据库服务器之间的通信。

在 Unix 类操作系统中,Unix socket 受到大多数编程语言的支持,并且可以与各种协议(如 TCP/IP、UDP 和 SCTP)一起使用。

比如MySQL本地连接,可以使用sock文件连接:

import pymysql

# 创建一个连接对象
cnx = pymysql.connect(user='root', password='pwd', database='dt', unix_socket='/tmp/mysql.sock')

# 创建一个游标对象并执行查询
cursor = cnx.cursor()
query = "SELECT * FROM user;"
cursor.execute(query)

# 获取查询结果并打印
for row in cursor:
    print(row)

# 关闭游标和连接
cursor.close()
cnx.close()

再比如 docker:

Docker 在 Linux 环境下使用 Unix socket 进行本地通信。Docker 客户端和 Docker 守护进程之间的通信是通过 Unix socket 进行的。Docker 守护进程会在 /var/run/docker.sock 路径下创建一个 Unix socket 文件,Docker 客户端可以通过连接到该 socket 文件来与 Docker 守护进程通信。

Docker 客户端在与 Docker 守护进程通信时,可以通过使用 Docker Remote API 进行各种操作,例如启动和停止容器、创建镜像和获取容器和镜像的信息。Docker Remote API 可以通过 HTTP 或 HTTPS 协议进行通信,也可以通过 Unix socket 进行通信。使用 Unix socket 进行通信可以提高性能并提供更好的安全性,因为数据不需要通过网络传输,并且 Unix socket 文件默认只能被本地用户访问。

在 Docker 中,可以通过在 Docker 客户端中指定 -H unix:///var/run/docker.sock 参数来连接到 Docker 守护进程的 Unix socket。例如,以下命令将使用 Unix Domain socket 连接到 Docker 守护进程:

docker -H unix:///var/run/docker.sock ps

和一般的socket一样,可以进行数据的读写,下面是一个Python的实例:

# 服务器端代码

import socket
import os

# 创建一个 Unix socket
server_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# 如果该 socket 文件已存在,则删除
socket_file = '/tmp/my_unix_socket'
if os.path.exists(socket_file):
    os.remove(socket_file)

# 绑定 socket 文件到 socket
server_socket.bind(socket_file)

# 监听 socket
server_socket.listen(1)

# 等待连接
print('等待客户端连接...')
connection, client_address = server_socket.accept()
# 这里的client_address获取到的是''
print(f'客户端已连接:{client_address} {connection} {type(client_address)}')

# 从客户端接收数据并发送响应
while True:
    data = connection.recv(1024)
    if not data:
        break
    response = data.decode().upper().encode()
    connection.sendall(response)

# 关闭连接和 socket
connection.close()
server_socket.close()
os.remove(socket_file)

客户端代码:

import socket

# 创建一个 Unix socket
client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

# 连接到服务器
socket_file = '/tmp/my_unix_socket'
client_socket.connect(socket_file)

# 发送数据
data = b'hello world'
client_socket.sendall(data)

# 接收响应并打印
response = client_socket.recv(1024)
print(response.decode())

# 关闭连接和 socket
client_socket.close()

运行实例:

等待客户端连接...
客户端已连接: <socket.socket fd=4, family=AddressFamily.AF_UNIX, type=SocketKind.SOCK_STREAM, proto=0, laddr=/tmp/my_unix_socket> <class 'str'>

HELLO WORLD

Unix Domain socket 主要用于同一主机上的进程间通信。与主机间的进程通信不同,它不是通过 “IP地址 + TCP或UDP端口号” 的方式进程通信,而是使用 socket 类型的文件来完成通信,因此在稳定性、可靠性以及效率方面的表现都很不错。

Unix Domain socket和 localhost的不同

Unix Domain socket 的 IPC 看起来与使用环回接口(localhost或127.0.0.1)的常规 TCP 套接字的 IPC 非常相似,但有一个关键区别:性能。虽然 TCP 环回接口可以跳过完整 TCP/IP 网络堆栈的一些复杂性,但它保留了许多其他(ACK、TCP 流量控制等)功能。这些复杂性是为可靠的跨机器通信而设计的,但在单个主机上它们是不必要的负担。、

还有一些额外的区别。例如,由于 Unix Domain socket使用文件系统中的路径作为其地址,我们可以使用目录和文件权限来控制对套接字的访问,从而简化身份验证。

当然,与 TCP 套接字相比,Unix Domain socket的一大缺点是单主机限制。对于为使用 TCP 套接字而编写的代码,我们只需将地址从本地更改为远程,一切都会继续工作。也就是说,Unix Domain socket的性能优势足够显着,而且 API 与 TCP 套接字非常相似,因此很可能编写同时支持两者的代码(单个主机上的 Unix Domain socket,用于远程 IPC 的 TCP),而且难度很小。

Golang中使用 Unix Domain Socket

简单的echo服务:

package main

import (
        "io"
        "log"
        "net"
        "os"
)

const SockAddr = "/tmp/echo.sock"

func echoServer(c net.Conn) {
        log.Printf("Client connected [%s]", c.RemoteAddr().Network())
        io.Copy(c, c)
        c.Close()
}

func main() {
        if err := os.RemoveAll(SockAddr); err != nil {
                log.Fatal(err)
        }

        l, err := net.Listen("unix", SockAddr)
        if err != nil {
                log.Fatal("listen error:", err)
        }
        defer l.Close()

        log.Println("listen at", SockAddr)
        for {
                conn, err := l.Accept()
                if err != nil {
                        log.Fatal("accept error:", err)
                }
                // 每个连接新建一个go协程处理
                go echoServer(conn)
        }
}

一个rpc服务

package main

import (
        "fmt"
        "log"
        "net"
        "net/http"
        "net/rpc"
        "os"
)

const SockAddr = "/tmp/rpc.sock"

type Greeter struct {
}

func (g Greeter) Greet(name *string, reply *string) error {
        *reply = "Hello, " + *name
        return nil
}

func main() {
        if err := os.RemoveAll(SockAddr); err != nil {
                log.Fatal(err)
        }

        greeter := new(Greeter)
        rpc.Register(greeter)
        rpc.HandleHTTP()
        l, e := net.Listen("unix", SockAddr)
        if e != nil {
                log.Fatal("listen error:", e)
        }
        fmt.Println("Serving...")
        http.Serve(l, nil)
}

参考:

https://blog.csdn.net/u012206617/article/details/85616349

https://github.com/eliben/code-for-blog/tree/master/2019/unix-domain-sockets-go

  • 1
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值