写这个的原因是打算,为以后的混合编程做准备。打算了解一下类 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()
最后祝你玩的开心——————(*^_^*)