Golang及python实现Unix Socket

我们都知道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()

猜你喜欢

转载自blog.csdn.net/lwc5411117/article/details/83018252
今日推荐