Julia进程间通信
Julia与Electron进程间通信
Julia进程间通信采用命名管道的方式,主要使用Sockets。Julia 提供了一个丰富的接口处理终端、管道、tcp套接字等等I/O流对象,并且接口在系统层的实现是异步的。
定义一个命名管道
使用一个字符串即可命名一个管道,如:
julia> using Sockets
julia> name_pipe = "\\\\.\\pipe\\mypipe-1"
"\\\\.\\pipe\\mypipe-1"
异步监听服务器端
#一定要异步监听,否则将会阻塞
@async begin
server = listen(name_pipe)
while true
#等待有客户端连接
sock = accept(server)
end
end
使用Sockets下面的listen(name_pipe)建立服务器端的监听,返回PipeServer类型。accpet函数调用后,会调用wait函数,等待一个客户端的连接,如果不采用异步形式,此步会阻塞。sock为PipeEndPoint类型,此类型为Base下的不导出的类型。
julia> isa(sock,Base.PipeEndpoint)
true
建立客户端与服务器端连接
clientside = connect(name_pipe)
true
julia> clientside.status
3
使用connnect(name_pipe)返回PipeEndPoint类型的客户端,查看clientside.status=3,代表StatusOpen,各状态码的意义如下:
const StatusUninit = 0 # handle is allocated, but not initialized
const StatusInit = 1 # handle is valid, but not connected/active
const StatusConnecting = 2 # handle is in process of connecting
const StatusOpen = 3 # handle is usable
const StatusActive = 4 # handle is listening for read/write/connect events
const StatusClosing = 5 # handle is closing / being closed
const StatusClosed = 6 # handle is closed
const StatusEOF = 7 # handle is a TTY that has seen an EOF event
const StatusPaused = 8 # handle is Active, but not consuming events, and will transition to Open if it receives an event
可用read/write向管道中读写数据
管道也是IO的子类型
julia> isa(sock,IO)
true
读也一定要采用异步的方式,否则管道中无数据中read函数将阻塞起来,知道管道中有数据出现,read出来之后,管道中的数据将减少。
@async while isopen(sock)
#将从客户端读出的数据+"from the Echo Server"后再直接发给客户端
write(sock, readline(sock,keep=true)*" from the Echo Server")
end
@async while isopen(clientside)
#客户端从管道中读取数据后直接在标准输出设备输出
write(stdout, readline(clientside,keep=true))
end
#向服务端写一个字符串
julia> println(clientside,"Hello World from the Echo Server")
Hello World from the Echo Server
使用完成后记得关闭管道close(clientside);
控制打开其他可执行程序并传递参数
主要使用Sockets下的open函数,open(command, mode::AbstractString=“r”, stdio=devnull),或者
open(command, other=devnull; write::Bool = false, read::Bool = !write)
command是可以解析为执行命令的抽象字符串。
using Sockets
nodepad_path="C:\\WINDOWS\\system32\\notepad.exe"
filepath=joinpath(@__DIR__,"test.txt")
open(`$nodepad_path $filepath`,"w",stdout)
open函数会生成一个Process类型的对象,同时会解析command命令,command命令中包含需要打开的可执行程序及传递的参数,如:nodepad_path为记事本的路径,filepath为传递的参数,此处为要加载的txt的路径。上述命令执行完毕后notepad被打开并加载了test.txt中的内容。
如果在julia中建立一个命名管道,julia作用服务器端,再用open控制打开electron,并将管道的地址作为参数传递给electron,那么就可以再electron端建立客户端并与服务器端通过管道连接,这样就到达了两者通信的目的。
解析Electron.jl源码
Electron.jl这个包正是通过上述原理实现julia与Elctron通信的。解析Electron.jl的源码,在Application()函数中
#获取electon二进制文件的路径
electron_path = get_electron_binary_cmd()
#获取mainjs的路径,作为传递给electon的第一个参数,electron启动后加载的js文件
mainjs = joinpath(@__DIR__, "main.js")
#一个命名管道,负责julia向electron发送数据
main_pipe_name = generate_pipe_name("juliaelectron-$process_id-$id")
#一个命名管道,负责electron向Julia发送数据
sysnotify_pipe_name = generate_pipe_name("juliaelectron-sysnotify-$process_id-$id")
#验证码,确保管道在两者之间准确无误的连通
secure_cookie = rand(UInt8, 128)
secure_cookie_encoded = base64encode(secure_cookie)
#启动electron,并向electon传递两个管道的地址及一个验证码
proc = open(`$electron_path $mainjs $main_pipe_name $sysnotify_pipe_name $secure_cookie_encoded`, "w", stdout)
electron启动后加载main.js,main.js的源码解析:
app.on('ready', function() {
//从进程中读取传递过来的第四个参数(验证码),并转换成64为编码格式
var secure_cookie = Buffer.from(process.argv[4], 'base64');
//该函数建立管道连接,并向管道中写入验证码,供服务器端验证
function secure_connect(addr, secure_cookie) {
var connection = net.connect(addr);
connection.setEncoding('utf8')
connection.write(secure_cookie);
return connection;
}
//分别建立两个管道的连接,并向管道中写入验证码
var connection = secure_connect(process.argv[2], secure_cookie)
sysnotify_connection = secure_connect(process.argv[3], secure_cookie)
}
在julia端收到来自electron的管道连接,并受到验证码后,首先验证“验证码是否正确”
#下面两段就是验真命名管道是否正常运行了,accept会等待连接
sock = accept(server)
if read!(sock, zero(secure_cookie)) != secure_cookie
close(server)
close(sysnotify_server)
close(sock)
error("Electron failed to authenticate with the proper security token")
end
let sysnotify_sock = accept(sysnotify_server)
if read!(sysnotify_sock, zero(secure_cookie))!= secure_cookie
close(server)
close(sysnotify_server)
close(sysnotify_sock)
close(sock)
error("Electron failed to authenticate with the proper security token")
end
如果验证成功,则表示管道连接成功,可以正常通信了。在julia段一定要异步的读取数据,数据是以JSON的格式传递的,用key表示属性
let app = _Application(Window, sock, proc, secure_cookie)
#@async 的arg 就是要执行的exp
@async begin
try
try
while true
try
# 注意这里是系统管道
line_json = readline(sysnotify_sock)
isempty(line_json) && break # EOF
cmd_parsed = JSON.parse(line_json)
#如果渲染窗口向主窗口传递过来了关闭的消息
if cmd_parsed["cmd"] == "windowclosed"
win_index = findfirst(w -> w.id == cmd_parsed["winid"], app.windows)
app.windows[win_index].exists = false
#关闭两者的通信通道
close(app.windows[win_index].msg_channel)
#从app.windows数组中删除对应窗口Item
deleteat!(app.windows, win_index)
#渲染窗口的回收是由electron负责的
#closing暂时没用,估计是作者预留的
elseif cmd_parsed["cmd"] == "appclosing"
break
#如果是消息通过channel传递过来了
elseif cmd_parsed["cmd"] == "msg_from_window"
win_index = findfirst(w -> w.id == cmd_parsed["winid"], app.windows)
#解析出来,放到msg_channel里面。
put!(app.windows[win_index].msg_channel, cmd_parsed["payload"])
end
catch er
bt = catch_backtrace()
io = PipeBuffer()
print_with_color(Base.error_color(), io, "Electron ERROR: "; bold = true)
Base.showerror(IOContext(io, :limit => true), er, bt)
println(io)
write(stderr, io)
end
end
finally
# Cleanup all the windows that are associated with this application
for w in app.windows
w.exists = false
end
empty!(app.windows)
end
finally
# Cleanup the application instance
app.exists = false
close(sysnotify_sock)
app_index = findfirst(a -> a === app, _global_applications)
deleteat!(_global_applications, app_index)
#deleteapp之后就会closeapp ,init()中已经定义
end
end
return app
end