使用unixSocket实现文件描述符传递

应用场景

tcp连接迁移,比如应用程序要实现平滑重启,就需要将现有的tcp连接迁移到新进程。

 

先介绍下实现过程,后面的文章会具体介绍这种场景的实现方式。

  • 临时文件tmp.txt的内容为“hello world”。
  • 客户端与服务端建立unix连接后,通过UnixRights将一组打开的文件描述符编码为套接字控制消息。
  • 服务端收到这个消息后,进行解码,解析出文件描述符,并通过os.NewFile()将文件描述符转为 *os.File对象,并进行读取,最终可以读到“hello world”

 

Client端:

package main

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

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

func main() {
    file, err := os.Open("./tmp.txt")
    if err != nil {
        panic(err)
    }
    defer file.Close()
    fdnum := file.Fd()
    fmt.Printf("%b %b %b %b\n", byte(fdnum), byte(fdnum >> 8), byte(fdnum >> 16), byte(fdnum >> 24))
    fmt.Printf("ready to send fd: %d\n", fdnum)
    //UnixRights将一组打开的文件描述符编码为套接字控制消息,以便发送到另一个进程。
    data := syscall.UnixRights(int(fdnum))
    fmt.Println(111, data)
    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([]byte("boy"), data, nil)
    if err != nil{
        panic(err)
    }
    fmt.Printf("WriteMsgUnix = %d, %d, %d\n", n, oobn, len(data))
    fmt.Printf("write %d data success\n", n)
}

 

 

Server端:

 

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 socks\n")
    conn, err := l.AcceptUnix()
    if err != nil {
        panic(err)
    }
    // msg分为两部分数据
    buf := make([]byte, 32)
    oob := make([]byte, 32)
    b, 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 success\n", n, string(buf[:n]))
        return
    }
    err = conn.Close()
    if err != nil{
        panic(err)
    }
}

 

posted @ 2021-09-16 11:45  独揽风月  阅读(345)  评论(0编辑  收藏  举报