我们都知道socket本来是为网络通讯设计,可以通过socket方便的实现不同机器之间的通信。当然通过socket也可以实现同一台主机之间的进程通信。但是通过socket网络协议实现进程通信效率太低,后来就出现了IPC通信协议,UNIX Domain Socket (UDS)就是其中之一,而且用于IPC更有效率,比如:数据不再需要经过网络协议栈,也不需要进行打包拆包、计算校验和、维护序列号和及实现应答机制等,要做的只是将应用层数据从一个进程拷贝到另一个进程。
类似于Socket的TCP和UDP,UNIX Domain Socket也提供面向流和面向数据包两种实现,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。UNIX Domain Socket是全双工的,在实现的时候可以明显的发现客户端与服务端通信是通过文件读写进行的通信,而且可以同时读写。相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制,如果用过supervisor的同学会发现在配置supervisor的时候需要设置一个supervisor.sock的问题地址,不难猜出,supervisor即使用unix socket来进行通信的。
使用UNIX Domain Socket的过程和网络socket十分相似,也要先调用socket()创建一个socket文件描述符,address family指定为AF_UNIX,type可以选择SOCK_DGRAM或SOCK_STREAM,protocol参数仍然指定为0即可。但是我们通过Golang或者其他高级语言实现的时候,会掩盖很多底层信息。
UNIX Domain Socket与网络socket编程最明显的不同在于地址格式不同,用结构体sockaddr_un表示,网络编程的socket地址是IP地址加端口号,而UNIX Domain Socket的地址是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。所以在实现的时候,可以再启动的时候删掉sock文件,也可以在程序的signal捕获到退出时删除sock文件。
定义Client 代码和Server端代码,方便测试。分别build,然后先启动Server,再启动Client即可。Golang语言实现与python实现的客户端与服务器端可以相互调用。
Golang Server端代码如下:
package main
import (
"encoding/binary"
"bytes"
"io"
"os"
"fmt"
"net"
"time"
)
const (
UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" // socket file path
)
func main() {
// Remove socket file
os.Remove(UNIX_SOCK_PIPE_PATH)
// Get unix socket address based on file path
uaddr, err := net.ResolveUnixAddr("unix", UNIX_SOCK_PIPE_PATH)
if err != nil {
fmt.Println(err)
return
}
// Listen on the address
unixListener, err := net.ListenUnix("unix", uaddr)
if err != nil {
fmt.Println(err)
return
}
// Close listener when close this function, you can also emit it because this function will not terminate gracefully
defer unixListener.Close()
fmt.Println("Waiting for asking questions ...")
// Monitor request and process
for {
uconn, err := unixListener.AcceptUnix()
if err != nil {
fmt.Println(err)
continue
}
// Handle request
go handleConnection(uconn)
}
}
/*******************************************************
* Handle connection and request
* conn: conn handler
*******************************************************/
func handleConnection(conn *net.UnixConn) {
// Close connection when finish handling
defer func() {
conn.Close()
}()
// Read data and return response
data, err := parseRequest(conn)
if err != nil {
fmt.Println(err)
return
}
fmt.Printf("%+v\tReceived from client: %s\n", time.Now(), string(data))
time.Sleep(time.Duration(1) * time.Second) // sleep to simulate request process
// Send back response
sendResponse(conn, []byte(time.Now().String()))
}
/*******************************************************
* Parse request of unix socket
* conn: conn handler
*******************************************************/
func parseRequest(conn *net.UnixConn) ([]byte, error) {
var reqLen uint32
lenBytes := make([]byte, 4)
if _, err := io.ReadFull(conn, lenBytes); err != nil {
return nil, err
}
lenBuf := bytes.NewBuffer(lenBytes)
if err := binary.Read(lenBuf, binary.BigEndian, &reqLen); err != nil {
return nil, err
}
reqBytes := make([]byte, reqLen)
_, err := io.ReadFull(conn, reqBytes)
if err != nil {
return nil, err
}
return reqBytes, nil
}
/*******************************************************
* Send response to client
* conn: conn handler
*******************************************************/
func sendResponse(conn *net.UnixConn, data []byte) {
buf := new(bytes.Buffer)
msglen := uint32(len(data))
binary.Write(buf, binary.BigEndian, &msglen)
data = append(buf.Bytes(), data...)
conn.Write(data)
}
Golang Client端代码如下:
package main
import (
"time"
"io"
"encoding/binary"
"bytes"
"fmt"
"net"
)
const (
UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" // socket file path
)
var (
exitSemaphore chan bool
)
func main() {
// Get unix socket address based on file path
uaddr, err := net.ResolveUnixAddr("unix", UNIX_SOCK_PIPE_PATH)
if err != nil {
fmt.Println(err)
return
}
// Connect server with unix socket
uconn, err := net.DialUnix("unix", nil, uaddr)
if err != nil {
fmt.Println(err)
return
}
// Close unix socket when exit this function
defer uconn.Close()
// Wait to receive response
go onMessageReceived(uconn)
// Send a request to server
// you can define your own rules
msg := "tell me current time\n"
sendRequest(uconn, []byte(msg))
// Wait server response
// change this duration bigger than server sleep time to get correct response
exitSemaphore = make(chan bool)
select {
case <- time.After(time.Duration(2) * time.Second):
fmt.Println("Wait response timeout")
case <-exitSemaphore:
fmt.Println("Get response correctly")
}
close(exitSemaphore)
}
/*******************************************************
* Send request to server, you can define your own proxy
* conn: conn handler
*******************************************************/
func sendRequest(conn *net.UnixConn, data []byte) {
buf := new(bytes.Buffer)
msglen := uint32(len(data))
binary.Write(buf, binary.BigEndian, &msglen)
data = append(buf.Bytes(), data...)
conn.Write(data)
}
/*******************************************************
* Handle connection and response
* conn: conn handler
*******************************************************/
func onMessageReceived(conn *net.UnixConn) {
//for { // io Read will wait here, we don't need for loop to check
// Read information from response
data, err := parseResponse(conn)
if err != nil {
fmt.Println(err)
} else {
fmt.Printf("%v\tReceived from server: %s\n", time.Now(), string(data))
}
// Exit when receive data from server
exitSemaphore <- true
//}
}
/*******************************************************
* Parse request of unix socket
* conn: conn handler
*******************************************************/
func parseResponse(conn *net.UnixConn) ([]byte, error) {
var reqLen uint32
lenBytes := make([]byte, 4)
if _, err := io.ReadFull(conn, lenBytes); err != nil {
return nil, err
}
lenBuf := bytes.NewBuffer(lenBytes)
if err := binary.Read(lenBuf, binary.BigEndian, &reqLen); err != nil {
return nil, err
}
reqBytes := make([]byte, reqLen)
_, err := io.ReadFull(conn, reqBytes)
if err != nil {
return nil, err
}
return reqBytes, nil
}
python Server端代码实现
import socket
import sys
import os
import time
UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" # socket file path
def serverSocket():
# crate socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if sock == 0:
print('socket create error')
return
# bind socket
if os.path.exists(UNIX_SOCK_PIPE_PATH):
os.unlink(UNIX_SOCK_PIPE_PATH)
if sock.bind(UNIX_SOCK_PIPE_PATH):
print('socket bind error')
return
# listen
if sock.listen(5):
print("socket listen error")
return
print("waiting for asking question ...")
while True:
conn, clientAddr = sock.accept()
try:
data = parseRequest(conn)
print("{0}\tReceived from client: {1}".format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), bytes.decode(data)))
time.sleep(2) # simulate request process
sendResponse(conn, str.encode(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time()))))
finally:
conn.close()
# close socket
sock.close()
# unlink socket file
os.unlink(UNIX_SOCK_PIPE_PATH)
# parse request and get data
def parseRequest(conn):
lenStr = conn.recv(4)
length = int.from_bytes(lenStr, byteorder="big") # "big"
data = conn.recv(length)
return data
# send response to client
def sendResponse(conn, data):
length = len(data)
buf = length.to_bytes(4, byteorder="big") # "big"
conn.sendall(buf+data)
if __name__ == "__main__":
serverSocket()
python Client端实现
import socket
import sys
import os
import time
import threading
UNIX_SOCK_PIPE_PATH = "/var/run/unixsock_test.sock" # socket file path
def clientSocket():
# crate socket
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
if sock == 0:
print('socket create error')
return
# connect server
sock.connect(UNIX_SOCK_PIPE_PATH)
# send message
msg = 'tell me current time\n'
sendRequest(sock, str.encode(msg))
t = threading.Thread(target=onMessageReceived, args=(sock,))
t.start()
# Send request to server, you can define your own proxy
# conn: conn handler
def sendRequest(sock, data):
length = len(data)
buf = length.to_bytes(4, byteorder="big") # "big"
sock.sendall(buf+data)
def onMessageReceived(sock):
#while True:
data = parseResponse(sock)
print("{0}\tReceived from client: {1}".format(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(time.time())), bytes.decode(data)))
sock.close()
# break
# Parse request of unix socket
# conn: conn handler
def parseResponse(sock):
lenStr = sock.recv(4)
length = int.from_bytes(lenStr, byteorder="big") # "big"
data = sock.recv(length)
return data
if __name__=="__main__":
clientSocket()