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信號
}
}