Go使用unixSocket实现进程间传递文件描述符 - Go语言中文社区

Go使用unixSocket实现进程间传递文件描述符


描述

因为项目需要接触了相关实现, 以此补足了这方面的知识.

场景应用场景

  • TCP连接在进程间迁移(nginx)
  • 安卓vpnservice通过该方式向VPN进程传递虚拟网卡设备

实现代码

  • receiver先启动, 监听指定unix sock, 读取传递的文件并打开, 打印文件内容.
  • sender负责进程内打开文件,并发送到指定unix sock中.
  • 文件内容为简单一行文本: hello chris.

receiver实现

package main

import (
	"fmt"
	"net"
	"os"
	"syscall"
)

const (
	// socksPath unixsock文件所在地址
	socksPath = "./unix_sock"
)

func main() {
	// unlink删除已存在的unixSock文件
	syscall.Unlink(socksPath)
	laddr, err := net.ResolveUnixAddr("unix", socksPath)
	if err != nil {
		panic(err)
	}
	l, err := net.ListenUnix("unix", laddr)
	if err != nil {
		panic(err)
	}
	fmt.Printf("waiting for conn from unix socksn")
	conn, err := l.AcceptUnix()
	if err != nil {
		panic(err)
	}
	// msg分为两部分数据
	buf := make([]byte, 32)
	oob := make([]byte, 32)
	_, oobn, _, _, err := conn.ReadMsgUnix(buf, oob)
	if err != nil {
		panic(err)
	}
	// 解出SocketControlMessage数组
	scms, err := syscall.ParseSocketControlMessage(oob[:oobn])
	if err != nil {
		panic(err)
	}
	if len(scms) > 0 {
		// 从SocketControlMessage中得到UnixRights
		fds, err := syscall.ParseUnixRights(&(scms[0]))
		if err != nil {
			panic(err)
		}
		fmt.Printf("parse %d fds: %v n", len(fds), fds)
		// os.NewFile()将文件描述符转为 *os.File对象, 并不创建新文件, 通常很少使用到
		f := os.NewFile(uintptr(fds[0]), "")
		defer f.Close()
		// 从文件中读取文本内容
		buf := make([]byte, 1024)
		n, err := f.Read(buf)
		if err != nil {
			panic(err)
		}
		fmt.Printf("read %d data %s from file successn", n, string(buf[:n]))
		return
	}
	err = conn.Close()
	if err != nil{
		panic(err)
	}
}

sender实现

package main

import (
	"fmt"
	"net"
	"os"
	"syscall"
)

const (
	// 与receiver监听的unixsocks文件地址一致
	socksPath = "xxx"
)

func main() {
	file, err := os.Open("./temp")
	if err != nil {
		panic(err)
	}
	defer file.Close()
	fdnum := file.Fd()
	fmt.Printf("%b %b %b %bn", byte(fdnum), byte(fdnum >> 8), byte(fdnum >> 16), byte(fdnum >> 24))
	fmt.Printf("ready to send fd: %dn", fdnum)
	data := syscall.UnixRights(int(fdnum))
	raddr, err := net.ResolveUnixAddr("unix", socksPath)
	if err != nil{
		panic(err)
	}
	// 连接UnixSock
	conn, err := net.DialUnix("unix", nil, raddr)
	if err != nil{
		panic(err)
	}
	// 发送msg
	n, oobn, err := conn.WriteMsgUnix(nil, data, nil)
	if err != nil{
		panic(err)
	}
	fmt.Printf("WriteMsgUnix = %d, %d; want 1, %d", n, oobn, len(data))
	fmt.Printf("write %d data successn", n)
}

知识要点

  • "传递文件描述符"的说法是不正确的, 不同进程中的文件描述符(一个非负整型数)仅在进程内有意义.
  • 根据UnixRights的含义, 可以理解为在进程间传递文件权限
  • 发送时用户态仅通过在UnixRights中指定msg.type=SCM_RIGHTS, 其余由系统调用完成的事情总共有:
  1. 根据fd找到当前fd对应的内核中的文件项;
  2. 在接收方创建新的fd, 并把对应指针指向内核中的同一个文件项, 并返回新的fd.
  • 因此看起来就像发送方发送了一个fd, 接收方得到了一个fd.
  • 在<unix网络编程>中可以找到该linux control message的相关描述与例子
    unix网络编程
版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/m0_37422289/article/details/108454728
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢