I/O模型有阻塞I/O模型、非阻塞I/O模型、多路复用I/O模型、信号驱动I/O模型、异步I/O模型。这次我将使用Java实现信号驱动I/O模型。下图是原理图
写之前我在网上搜了很久,才发现一篇C/C++语言实现的信号驱动I/O模型。
后来发现Java提供的信号机制在sun.misc包下,属于非标准包。
其中Signal
类可以创建哪些信号和操作系统有关。Signal.handle()
方法用户注册信号处理器,Signal.raise
用于激发信号。
SignalHandler
是一个函数式接口,其中的handle方法用于处理收到的信号。当然可以收到系统发送的信号,如linux下的kill -15 Java进程的ID(可是使用top | grep java查找到)。
我写的示例说明:用户线程在听《歌手·当打之年 第9期》华晨宇的好想爱这个世界啊歌曲,它发起一个I/O请求下载我的github上存放的一首歌,周杰伦的歌曲琴伤。将信号注册到信号处理器。下载完毕后,发送INT中断信号,信号处理接收到信号后加以判断,用户线程将数据从内核拷到用户空间(拷贝过程阶段用播放琴伤模拟)。
感想:我觉得这个信号就像我以前写安卓时的广播。信号驱动I/O模型方便在进程间通信,复杂的线程间通信。信号驱动I/O的特色、吸引人的地方好像在于信号上,抢了I/O的风头。书中提到”系统会为请求对应的socket注册一个信号函数“,不知道如何实现。
代码:WateForDataThread.java准备数据的线程,SignalDrivenIOTest.java体验信号驱动I/O的类。其中使用到了jlayer-1.0.1包,这个包中播放音乐的方法里面创建的有守卫进程,小心了(其他播放音乐的包有jmf,解析歌曲的包jaudiotagger,这里没有用到它们)。
package signaldriven;
import sun.misc.Signal;
import sun.misc.SignalHandler;
import javax.sound.sampled.*;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
public class SignalDrivenIOTest {
public static void main(String[] args) {
SignalDrivenIOTest signalDrivenIOTest = new SignalDrivenIOTest();
signalDrivenIOTest.run();
}
private String localSong = "华晨宇 - 好想爱这个世界啊.wav";
private String remoteDownloadLink = "https://github.com/zzuwenjie/ucaslife/raw/master/sources/%E5%91%A8%E6%9D%B0%E4%BC%A6%20-%20%E7%90%B4%E4%BC%A4.wav";
private String storePath = "周杰伦 - 琴伤.wav";
private String SIGIO = "INT";
private Clip clip;
private Clip preferClip;
public void run() {
new Thread(new WaitForDataThread(remoteDownloadLink, storePath)).start(); // 我不知道怎么让内核准备数据,故下载一首歌
Signal.handle(new Signal(SIGIO), signalHandler); // 下载好了通知我执行handler
File file = new File(localSong);
try {
AudioInputStream audioInput = AudioSystem.getAudioInputStream(file);
preferClip = AudioSystem.getClip();
clip = AudioSystem.getClip();
clip.open(audioInput);
clip.loop(Clip.LOOP_CONTINUOUSLY);
clip.start();
} catch (IOException | UnsupportedAudioFileException | LineUnavailableException e) {
e.printStackTrace();
}
}
private SignalHandler signalHandler = (signal)->{
if (signal.getName().equals(SIGIO)) {
handleSIGIO();
}
};
private void handleSIGIO() {
File file = new File(storePath);
long pausePosition = 0;
if (!file.exists())
return; // 不应该没准备好数据
if (clip != null) {
pausePosition = clip.getMicrosecondPosition();
clip.stop();
}
try {
AudioInputStream inputStream = AudioSystem.getAudioInputStream(file);
preferClip.open(inputStream);
preferClip.start();
JOptionPane.showMessageDialog(null, "应用进程复制数据中。点击复制完毕,返回成功指示");
preferClip.close();
// 假设将数据拷到内核用户空间,阻塞主线程。
} catch (UnsupportedAudioFileException | IOException | LineUnavailableException e) {
e.printStackTrace();
}
clip.setMicrosecondPosition(pausePosition);
clip.start();
JOptionPane.showMessageDialog(null, "接着听音乐中...不如学习会儿?");
clip.close();
}
}
package signaldriven;
import sun.misc.Signal;
import javax.swing.*;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class WaitForDataThread implements Runnable {
private String urlString;
private String filename;
public WaitForDataThread(String url, String filename) {
urlString = url;
this.filename = filename;
}
@Override
public void run() {
// try {
// URL url = new URL(urlString);
// HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// conn.setConnectTimeout(1000 * 3);
// conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");
// BufferedInputStream bis = new BufferedInputStream(conn.getInputStream());
// BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filename));
// byte[] buf = new byte[1024 * 20];
// int length = -1;
// while ((length = bis.read(buf)) != -1) {
// bos.write(buf, 0, length);
// }
// bis.close();
// bos.close();
// conn.disconnect();
// Signal.raise(new Signal("INT")); // 下载完时发送INT信号
// } catch (IOException e){
// e.printStackTrace();
// }
//github下载数据太慢了,几乎连接不上
JOptionPane.showMessageDialog(null, "内核准备数据,应用进程播放歌曲。点击发送信号通知应用进程准备好了");
Signal.raise(new Signal("INT")); // 下载完时发送INT信号
}
}