问题背景
在项目的beta阶段,我们团队提出了一个新的内容,就是要为每个用户提供一个真正的打开即用的简易开发环境。为了真正实现快捷这一亮点,我决定在这个功能的实现上不再使用docker容器技术,从而免去了项目启动的时间开销,由此可以大大减少白屏时间。
而面临的一个问题就是输入输出问题,在alpha阶段,我们项目的输入输出是依赖于每个项目对应的容器的真实终端来实现的,而现在没有了docker技术,首要解决的就是输入输出问题。
解决过程
考虑到用户的输入和输出是完全异步的,我第一时间想到了WebSocket技术。
WebSocket技术
WebSocket技术作为HTTP技术的补充被提出,主要解决了HTTP协议中服务端不能主动向客户端发送数据的问题。正如其名,该技术就像是WEB应用中的Socket技术,支持客户端和服务端的全双工通信。
在这个项目中,因为输出输入都是异步的,客户端随时可能发送输入数据到服务器,服务器也随时可能返回运行输出到客户端,所以采用这个技术十分合适。
下面首先说一下WebSocketSession相关的内容
WebSocketSession
类似HTTP技术中的Session,WebSocketSession代表了一个客户端和服务端之间的连接会话,WebSocket采用长连接技术,所以客户端在获取了WebSocket对象后,可以长时间使用该对象和服务端进行数据的收发,相应的,在服务端也会有一个相应的WebSocketSession对象用于数据的读取和发送。
在这个项目中,采用了一个线程处理一个WebSocketSession的处理方式,通过线程池技术来实现线程的复用,提高资源的利用率
public class Task implement Runnable {
private final WebSocketSession session;
public Task(WebSocketSession session) {
this.session = session;
}
@Override
pubilc void run() {
// handle data output
}
}
通过这样的方式,实时将从管道中读取的新数据通过WebSocketSession发送回客户端。
而输入的处理则需要在Handler中完成,而这就涉及WebSocket的四种事件
WebSocket的四种事件
- 开启 onOpen
- 消息接收 onMessage
- 错误发生 onError
- 关闭 onClose
在上述事件发生时,WebSocket协议规定会发送相应的消息使得连接双方都得知该事件
由此,可以利用onMessage事件,在收到客户端发来的新输入时,将该输入重定向到输入管道中,从而使得运行的代码可以读取到相应的内容。
相应的,在收到onClose事件时,可以进行一系列的资源回收,如线程的中断、文件的删除、代码执行进程的销毁等。
具名管道技术
接下来就是考虑如何将用户的输入重定向到程序,再将程序运行的输出重定向到网络的输出。
我知道Linux下有管道,于是写了这样一段代码
# main.py
a = input()
print(a)
echo 1 | python main.py
发现是可以正常输出的,这也验证了我的想法。
但是紧接着另一个问题出现了,用户的输入不是一次性的,因此不能像上面那样一口气将输入内容送入程序
通过网上搜索,了解到还有具名管道,平时使用的|
是临时管道,而与之相对应的有具名管道,它会以一个文件的形式存在,可以复用。
mkfifo input.pipe
python main.py < input.pipe
# can write to input.pipe much times
相应的,输出也可以使用具名管道,使得服务器可以持续从输出管道中获得程序运行的输出内容。
mkfifo input.pipe
mkfifo output.pipe
python main.py < input.py > output.py
# can write to input.pipe much times
# can read from output.pipe much times
这是主体的实现思路,下面介绍具名管道使用的一些细节
具名管道的EOF产生机制
对于一个具名管道,当没有任何写入者时,从该管道中可以读取到EOF
因此,在这个项目的应用场景中,显然不能每次收到一个输入,就开启一个Writer,写入后就关闭,因为这样会导致管道输入端没有写入程序,从而产生EOF,使得应用程序的读取提前终止,产生错误。
解决的办法也十分简单暴力,可以使用一个Writer一直作为管道的输入端,保证管道不会产生EOF,当程序执行完成或是用户主动终止程序时,再关闭该Writer即可
具名管道的生命周期
一个具名管道分别有输入端和输出端,整个管道的声明周期如下
-
写入程序执行写入
echo 1 > input.pipe
在读取端有程序前,该过程会一直阻塞,不返回
-
读取程序执行读取
cat < input.pipe
在写入端有程序前,该过程会一直阻塞,不返回
-
写入端所有程序执行完毕
当写入端没有执行中的程序时,具名管道在最后追加EOF,表示写入的结尾
-
读取端所有程序执行完毕
若还有写入端程序在执行,则写入端会继续阻塞,否则执行完成,具名管道内的内容被丢弃,管道生命周期结束
至此,通过WebSocket技术和具名管道技术,就很好地解决了异步输入输出问题,使得软件的白屏时间大大减少。