UDS进程通信

897 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第2天,点击查看活动详情

UDS是什么?

IPC(InterProcess Communication)是Unix系统进程之间相互通信的技术,有多种选择类型如下图:管道、FIFO、消息队列、信号量、共享存储、套接字等。套接字即UDS(Unix Domain Socket)是IPC的类型之一:

image.png

上图前7种IPC通常用于同一台主机的各个进程间的IPC,最后两种,即套接字和STREAMS,是仅有的两种支持不同主机上各个进程间的IPC的类型。

image.png “无连接”:无需先调用某种形式打开函数就能发送消息的能力。“可靠”:所有这些形式的IPC都限制在单主机上,所以都是可靠的。当消息通过网络传达时,丢失消息消息的可能性就要加以考虑。“流控制“:如果系统资源(缓冲区)短缺或者如果接收进程不能再接收更多消息,则发送进程就要休眠。当流控制条件消失时,发送进程应自动地被唤醒。

套接字网络IPC接口,进程能够使用该接口和其他进程通信。通过该接口,其他进程运行位置是透明的,进程可以在同个计算机上或者不同计算机上,UDS在同一主机操作系统上执行进程之间数据交换。

创建和销毁:

套接字是通信端点的抽象,访问套接字需要用套接字描述符。套接字描述符在Unix系统是用文件描述符实现。许多处理文件描述符的函数(如read和write)都可以直接处理套接字描述符,创建套接字,需要调用socket函数:

image.png

参数domain(域)确定通信特性,取值域(以AF_开头)如下:

image.png

参数type确定套接字类型,取值如下:

image.png AF_INET通信域中套接字类型为SOCK_STREAM的默认协议是有连接的TCP(传输控制协议),是有序、可靠双向的面向连接的字节流;在AF_INET通信域中套接字类型SOCK_DGRAM的默认协议是无连接的UDP(用户数据报协议),是长度固定、不保序、不可靠报文传递。SOCK_SEQPACKET与SOCK_STREAM类似有序、可靠、面向连接的长度固定的报文传递,流传输控制协议SCTP提供顺序数据包服务。SOCK_RAW套接字提供一个数据报接口用于直接访问下面的网络层(在因特网域中为IP),绕过传输协议(TCP和UDP等),应用程序使用此接口构造自己的协议首部。

操作套接字函数有哪些?

image.png

如何禁止套接字上的输入/输出:

image.png shutdown允许套接字不直接关闭,以某种不活动状态存在。当参数how=SHUT_RD,无法从套接字读取数据;当how=SHUT_WR时,无法使用套接字发送数据;当how=SHUT_RDWR时,将同时无法读取和发送数据。当关闭了套接字最后一个活动引用时,才调用close函数关闭套接字,释放网络端点。

golang example

Server:

package main

import (
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"os/signal"
	"strings"

	"github.com/devlights/go-unix-domain-socket-example/pkg/message"
)

const (
	protocol = "unix"
	sockAddr = "/tmp/echo.sock"
)

func main() {
	cleanup := func() {
		if _, err := os.Stat(sockAddr); err == nil {
			if err := os.RemoveAll(sockAddr); err != nil {
				log.Fatal(err)
			}
		}
	}

	cleanup()

	listener, err := net.Listen(protocol, sockAddr)
	if err != nil {
		log.Fatal(err)
	}

	quit := make(chan os.Signal)
	signal.Notify(quit, os.Interrupt)

	go func() {
		<-quit
		fmt.Println("ctrl-c pressed!")
		close(quit)
		cleanup()
		os.Exit(0)
	}()

	fmt.Println("> Server launched")
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println(">>> accepted: ", conn.RemoteAddr().Network())
		go echo(conn)
	}
}

func echo(conn net.Conn) {
	defer conn.Close()

	for {
		m := &message.Echo{}
		err := m.Read(conn)
		if err != nil {
			if err == io.EOF {
				fmt.Println("=== closed by client")
				break
			}

			log.Println(err)
			break
		}

		fmt.Println("[READ ] ", m)

		s := strings.ToUpper(string(m.Data))
		m.Length = len(s)
		m.Data = []byte(s)

		err = m.Write(conn)
		if err != nil {
			log.Println(err)
			break
		}

		fmt.Println("[WRITE] ", m)
	}
}

Client:

package main

import (
	"fmt"
	"log"
	"net"
	"time"

	"github.com/devlights/go-unix-domain-socket-example/pkg/message"
)

const (
	protocol = "unix"
	sockAddr = "/tmp/echo.sock"
)

func main() {
	values := []string{
		"hello world",
		"golang",
		"goroutine",
		"this program runs on crostini",
	}

	conn, err := net.Dial(protocol, sockAddr)
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()

	for _, v := range values {
		time.Sleep(1 * time.Second)

		m := &message.Echo{
			Length: len(v),
			Data:   []byte(v),
		}

		err = m.Write(conn)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("[WRITE] ", m)

		err = m.Read(conn)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("[READ ] ", m)
	}
}