使用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) } }