0%

Unix domain socket 跨进程通信

写这个的原因是打算,为以后的混合编程做准备。打算了解一下类 unix 平台下的通信方式。在综合了各种通信方式的优劣后最终选定了这一种通信方式,而且这种通信方式很容易的转化为 tcp 通信,实现更远距离的通信。至于为什么不直接选择 tcp 是因为 tcp 需要占用一个端口,而且目前也不打算实现更远距离的通信

关于 unix domain socket 的更多详情请查看:https://zh.m.wikipedia.org/wiki/Unix域套接字

本次展示的代码主要使用 go 进行编写。为了直观的展示跨进程能力,也使用到了 python3 编写的代码

首先用 go 实现一个 echo 服务,将发送过来的数据转发回去,因为多次运行的时候出现 通信文件已存在的情况所以需要删除它。为了简化逻辑使用了 goto ,合理的使用 goto 能少写代码而且逻辑更轻松

https://github.com/elissa2333/library/blob/master/echoServerUnix.go

package main

import (
    "fmt"
    "io"
    "log"
    "net"
    "os"
)

func main() {
    var file string = "test.sock" //用于 unix domain socket 的文件
start:
    lis, err := net.Listen("unix", file) //开始监听
    if err != nil {                      //如果监听失败,一般是文件已存在,需要删除它
            log.Println("UNIX Domain Socket 创 建失败,正在尝试重新创建 -> ", err)
            err = os.Remove(file)
            if err != nil { //如果删除文件失败 ,要么是权限问题,要么是之前监听不成功,不管是什么 都应该退出程序,不然后面 goto 就死循环了
                    log.Fatalln("删除 sock 文件失败!程序退出 -> ", err)
            }
            goto start //删除文件后重新执行一次创建
    } else { //监听成功会直接执行本分支
            fmt.Println("创建 UNIX Domain Socket 成功")
    }

    defer lis.Close() //虽然本次操作不会执行, 不过还是加上比较好

    for {
            conn, err := lis.Accept() //开始接 受数据
            if err != nil {
                    log.Println("请求接收错误 -> ", err)
                    continue //一个连接错误,不会影响整体的稳定性,忽略就好
            }
            go handle(conn) //开始处理数据
    }
}

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

    for {
            io.Copy(conn, conn) //把发送的数据 转发回去
    }
}

然后用 go 写一个发送和接收数据的客户端

https://github.com/elissa2333/library/blob/master/echoClientUnix.go

package main

import (
    "bufio"
    "fmt"
    "log"
    "net"
    "os"
)

func main() {
    file := "test.sock"
    conn, err := net.Dial("unix", file) //发起 请求
    if err != nil {
            log.Fatal(err) //如果发生错误,直接退出程序,因为请求失败所以不需要 close
    }
    defer conn.Close() //习惯性的写上

    input := bufio.NewScanner(os.Stdin) //创建 一个读取输入的处理器
    reader := bufio.NewReader(conn)     //创建 一个读取网络的处理器
    for {
            fmt.Print("请输入需要发送的数据: ")       //打印提示
            input.Scan()                    // 读取终端输入
            data := input.Text()            // 提取输入内容
            conn.Write([]byte(data + "\n")) // 将输入的内容发送出去,需要将 string 转 byte 加 \n  作为读取的分割符

            msg, err := reader.ReadString('\n') //读取对端的数据
            if err != nil {
                    log.Println(err)
            }
            fmt.Println(msg) //打印接收的消息
    }
}

为了直观的展现跨进程能力 再用 python3 写一个发送和接收数据的客户端

https://github.com/elissa2333/library/blob/master/echoClientUnix.py

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import socket


sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) #发起链接
sock.connect("test.sock") #指定进程文件

while True:
    data = input("请输入您需要发送的数据:") #读取用户输入
    sock.send(data.encode("utf-8")) #发送数据。因为只支持 byte 所以需要先编码成 utf-8
    receive_data = sock.recv(len(data)) #接收数据,因为 python3 的接收需要指定长度,所以这里发多少就接多少
    print(receive_data) #打印接收的数据

sock.close()

然后我们让它很有实际意义,对 go 的服务端进行改进,打印 python 客户端发送过来的信息,然后再回报我已收到。这样的话随便改进一下就能进行跨进程通信。

先上 go 的代码

https://github.com/elissa2333/library/blob/master/echoServerUnix2.go

package main

import (
    "bufio"
    "fmt"
    "io"
    "log"
    "net"
    "os"
)

func main() {
    var file string = "test.sock"
start:
    lis, err := net.Listen("unix", file)
    if err != nil {
            log.Println("UNIX Domain Socket 创 建失败,正在尝试重新创建 -> ", err)
            err = os.Remove(file)
            if err != nil {
                    log.Fatalln("删除 sock 文件失败!程序退出 -> ", err)
            }
            goto start
    } else {
            fmt.Println("创建 UNIX Domain Socket 成功")
    }

    defer lis.Close()

    for {
            conn, err := lis.Accept()
            if err != nil {
                    log.Println("请求接收错误 -> ", err)
                    continue
            }
            go handle(conn)
    }
}

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

    reader := bufio.NewReader(conn)

    for {
            msg, err := reader.ReadString('\n')
            if err == io.EOF { //当对端退出后会报这么一个错误
                    fmt.Println("go : 对端已接 收全部数据")
                    break
            } else if err != nil { //处理完客户端关闭的错误正常错误还是要处理的
                    log.Println(err)
                    break
            }
            fmt.Println("go : 对端发送数据 -> ", msg)
            conn.Write([]byte("服务端已接收数\n"))
    }
}

然后是 python3 的代码

https://github.com/elissa2333/library/blob/master/echoClientUnix2.py

#! /usr/bin/env python3
# -*- coding: utf-8 -*-

import socket


sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect("test.sock")

while True:
    data = input("请输入您需要发送的数据:")
    data = data + "\n" #\n 是给 go 服务端读的
    sock.send(data.encode("utf-8"))
    receive = sock.recv(1024) #多读一些数据,不影响的
    print("py : ",receive.decode("utf-8"))

sock.close()

最后祝你玩的开心——————(*^_^*)