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