1. 简介
IO多路复用(multiplexing)属于同步IO网络模型
是以Reactor模式实现
常见的IO多路复用应用有:select、poll、epoll
本篇文章采用Java的NIO框架来实现单线程的IO多路复用
2. Reactor模式的组成角色
1. Reactor:负责派发IO事件给对应的角色处理。为了监听IO事件,select必须实现在Reactor中。
2. Acceptor:负责接受client的连线,然后给client绑定一个Handler并注册IO事件到Reactor上监听。
3. Handler:负责处理与client交互的事件或行为。通常因为Handler要处理与所对应client交互的多个事件或行为,为了简化设计,会以状态模式来实现Handler。
3. 代码实现
[TCPReactor.java]
// Reactor線程
packageserver;
importjava.io.IOException;
importjava.net.InetSocketAddress;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.nio.channels.ServerSocketChannel;
importjava.util.Iterator;
importjava.util.Set;
publicclassTCPReactorimplementsRunnable {
privatefinalServerSocketChannel ssc;
privatefinalSelector selector;
publicTCPReactor(intport)throwsIOException {
selector = Selector.open();
ssc = ServerSocketChannel.open();
InetSocketAddress addr = newInetSocketAddress(port);
ssc.socket().bind(addr); // 在ServerSocketChannel綁定監聽端口
ssc.configureBlocking(false);// 設置ServerSocketChannel為非阻塞
SelectionKey sk = ssc.register(selector, SelectionKey.OP_ACCEPT); // ServerSocketChannel向selector註冊一個OP_ACCEPT事件,然後返回該通道的key
sk.attach(newAcceptor(selector, ssc));// 給定key一個附加的Acceptor對象
}
@Override
publicvoidrun() {
while(!Thread.interrupted()) {// 在線程被中斷前持續運行
System.out.println("Waiting for new event on port: "+ ssc.socket().getLocalPort() +"...");
try{
if(selector.select() ==0)// 若沒有事件就緒則不往下執行
continue;
} catch(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Set selectedKeys = selector.selectedKeys(); // 取得所有已就緒事件的key集合
Iterator it = selectedKeys.iterator();
while(it.hasNext()) {
dispatch((SelectionKey) (it.next())); // 根據事件的key進行調度
it.remove();
}
}
}
/*
* name: dispatch(SelectionKey key)
* description: 調度方法,根據事件綁定的對象開新線程
*/
privatevoiddispatch(SelectionKey key) {
Runnable r = (Runnable) (key.attachment()); // 根據事件之key綁定的對象開新線程
if(r !=null)
r.run();
}
}
[Acceptor.java]
// 接受連線請求線程
packageserver;
importjava.io.IOException;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.Selector;
importjava.nio.channels.ServerSocketChannel;
importjava.nio.channels.SocketChannel;
publicclassAcceptorimplementsRunnable {
privatefinalServerSocketChannel ssc;
privatefinalSelector selector;
publicAcceptor(Selector selector, ServerSocketChannel ssc) {
this.ssc=ssc;
this.selector=selector;
}
@Override
publicvoidrun() {
try{
SocketChannel sc= ssc.accept(); // 接受client連線請求
System.out.println(sc.socket().getRemoteSocketAddress().toString() + " is connected.");
if(sc!=null) {
sc.configureBlocking(false);// 設置為非阻塞
SelectionKey sk = sc.register(selector, SelectionKey.OP_READ); // SocketChannel向selector註冊一個OP_READ事件,然後返回該通道的key
selector.wakeup(); // 使一個阻塞住的selector操作立即返回
sk.attach(newTCPHandler(sk, sc));// 給定key一個附加的TCPHandler對象
}
} catch(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
我们先来简单点的,Handler不以状态模式实现,只以比较直觉的方式实现。
[TCPHandler.java]
// Handler線程
packageserver;
importjava.io.IOException;
importjava.nio.ByteBuffer;
importjava.nio.channels.SelectionKey;
importjava.nio.channels.SocketChannel;
importjava.util.concurrent.LinkedBlockingQueue;
importjava.util.concurrent.ThreadPoolExecutor;
importjava.util.concurrent.TimeUnit;
publicclassTCPHandlerimplementsRunnable {
privatefinalSelectionKey sk;
privatefinalSocketChannel sc;
intstate;
publicTCPHandler(SelectionKey sk, SocketChannel sc) {
this.sk = sk;
this.sc = sc;
state = 0;// 初始狀態設定為READING
}
@Override
publicvoidrun() {
try{
if(state ==0)
read(); // 讀取網絡數據
else
send(); // 發送網絡數據
} catch(IOException e) {
System.out.println("[Warning!] A client has been closed.");
closeChannel();
}
}
privatevoidcloseChannel() {
try{
sk.cancel();
sc.close();
} catch(IOException e1) {
e1.printStackTrace();
}
}
privatesynchronizedvoidread()throwsIOException {
// non-blocking下不可用Readers,因為Readers不支援non-blocking
byte[] arr =newbyte[1024];
ByteBuffer buf = ByteBuffer.wrap(arr);
intnumBytes = sc.read(buf);// 讀取字符串
if(numBytes == -1)
{
System.out.println("[Warning!] A client has been closed.");
closeChannel();
return;
}
String str = newString(arr);// 將讀取到的byte內容轉為字符串型態
if((str !=null) && !str.equals(" ")) {
process(str); // 邏輯處理
System.out.println(sc.socket().getRemoteSocketAddress().toString()
+ " > "+ str);
state = 1;// 改變狀態
sk.interestOps(SelectionKey.OP_WRITE); // 通過key改變通道註冊的事件
sk.selector().wakeup(); // 使一個阻塞住的selector操作立即返回
}
}
privatevoidsend()throwsIOException {
// get message from message queue
String str = "Your message has sent to "
+ sc.socket().getLocalSocketAddress().toString() + "\r\n";
ByteBuffer buf = ByteBuffer.wrap(str.getBytes()); // wrap自動把buf的position設為0,所以不需要再flip()
while(buf.hasRemaining()) {
sc.write(buf); // 回傳給client回應字符串,發送buf的position位置 到limit位置為止之間的內容
}
state = 0;// 改變狀態
sk.interestOps(SelectionKey.OP_READ); // 通過key改變通道註冊的事件
sk.selector().wakeup(); // 使一個阻塞住的selector操作立即返回
}
voidprocess(String str) {
// do process(decode, logically process, encode)..
// ..
}
}
最后是主程序代码
[Main.java]
packageserver;
importjava.io.IOException;
publicclassMain {
publicstaticvoidmain(String[] args) {
// TODO Auto-generated method stub
try{
TCPReactor reactor = newTCPReactor(1333);
reactor.run();
} catch(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
下面附上客戶端代碼:
[Client.java]
packagemain.pkg;
importjava.io.BufferedReader;
importjava.io.IOException;
importjava.io.InputStreamReader;
importjava.io.PrintWriter;
importjava.net.Socket;
importjava.net.UnknownHostException;
publicclassClient {
/**
* @param args
*/
publicstaticvoidmain(String[] args) {
// TODO Auto-generated method stub
String hostname=args[0];
intport = Integer.parseInt(args[1]);
//String hostname="127.0.0.1";
//int port=1333;
System.out.println("Connecting to "+ hostname +":"+port);
try{
Socket client = newSocket(hostname, port);// 連接至目的地
System.out.println("Connected to "+ hostname);
PrintWriter out = newPrintWriter(client.getOutputStream());
BufferedReader in = newBufferedReader(newInputStreamReader(client.getInputStream()));
BufferedReader stdIn = newBufferedReader(newInputStreamReader(System.in));
String input;
while((input=stdIn.readLine()) !=null) {// 讀取輸入
out.println(input); // 發送輸入的字符串
out.flush(); // 強制將緩衝區內的數據輸出
if(input.equals("exit"))
{
break;
}
System.out.println("server: "+in.readLine());
}
client.close();
System.out.println("client stop.");
} catch(UnknownHostException e) {
// TODO Auto-generated catch block
System.err.println("Don't know about host: "+ hostname);
} catch(IOException e) {
// TODO Auto-generated catch block
System.err.println("Couldn't get I/O for the socket connection");
}
}
}