上篇文章中寫了一些NIO相關的知識以及簡單的NIO實現示例,但是示例中,客戶端連接以及讀取、寫入、處理客戶端數據時都是在一個線程中,單個線程處理客戶端的數據,性能會很差,而且不能充分利用服務器的性能,這篇文章主要介紹Reactor線程模型,NIO的多路複用知識,用以提供服務端的性能。
單Reactor線程模型
單Reactor線程模型,最終還是使用了一個線程,和上篇文章最後的示例基本上沒啥差別,只是拆分了三個類來進行處理。
基於工作線程的Reactor線程模型
這裏的線程模型,是在客戶端連接後,業務數據的部分抽出來,放到線程池中處理,這樣做的好處是,如果處理業務數據時間很長,也不會影響客戶端的讀寫操作。
上面圖示是簡單的基於工作線程的工作模式,把業務數據處理單獨抽出來,在線程池處理。
多Reactor線程模型
最終的線程模型,是多Reactor線程模型。
客戶端數據的讀寫也是在多個線程中進行處理,充分提高了性能。
多Reactor線程模型示例
MainReactor-Thread和Acceptor代碼:
/**
* mainReactor-Thread
* 接收客戶端連接,然後交給Acceptor處理
*/
class MainReactor extends Thread {
//創建一個Selector
public Selector selector;
AtomicInteger integer = new AtomicInteger(0);
public MainReactor() throws IOException {
selector = Selector.open();
}
@Override
public void run() {
while (true) {
try {
//啓動Selector
selector.select();
//獲取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍歷事件
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()) {
//獲取客戶端通道
SocketChannel socketChannel = ((ServerSocketChannel)selectionKey.channel()).accept();
Acceptor acceptor = new Acceptor();
//把客戶端通道交給Acceptor去處理
acceptor.register(socketChannel);
}
//處理完之後要移除
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void register(ServerSocketChannel serverSocketChannel) throws ClosedChannelException {
//註冊OP_ACCEPT事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 處理客戶端的連接
* 給每個客戶端連接分配一個subReactor-Thread
*/
class Acceptor {
public void register(SocketChannel socketChannel) throws IOException {
//設置爲非阻塞模式
socketChannel.configureBlocking(false);
int index = integer.getAndIncrement() % subReactors.length;
SubReactor subReactor = subReactors[index];
//給客戶端連接分配一個subReactor線程
subReactor.register(socketChannel);
//啓動subReactor線程
subReactor.start();
System.out.println("收到新連接:" + socketChannel.getRemoteAddress());
}
}
}
SubReactor-Threads代碼:
/**
* 一個線程負責多個客戶端連接
* 從channel中讀數據、寫數據
*/
class SubReactor extends Thread {
//創建一個Selector
public Selector selector;
//用於判斷SubReactor線程是否已經啓動
public volatile boolean isRunning = false;
public SubReactor() throws IOException {
selector = Selector.open();
}
@Override
public void start() {
//判斷SubReactor線程是否已經啓動
//如果沒有啓動,就啓動SubReactor線程
if (!isRunning) {
isRunning = true;
super.start();
}
}
@Override
public void run() {
while (isRunning) {
try {
//啓動Selector
selector.select();
//獲取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍歷事件
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
if (selectionKey.isReadable()) {
try {
SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
new Handler(socketChannel);
} catch (Exception e) {
e.printStackTrace();
selectionKey.cancel();
}
}
//處理完之後要移除
iterator.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void register(SocketChannel socketChannel) throws IOException {
//註冊OP_READ事件
socketChannel.register(selector, SelectionKey.OP_READ);
}
/** 讀取或者寫入數據 */
class Handler {
//用來讀取或者寫入數據
public Handler(SocketChannel socketChannel) throws IOException {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
//如果有數據可讀,簡單的判斷一下大於0
if (readBuffer.position() > 0) {
break;
}
}
//沒有數據可讀,就直接返回
if (readBuffer.position() == 0) {
return;
}
//轉換爲讀取模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.limit()];
readBuffer.get(bytes);
System.out.println("獲取到新的數據:" + new String(bytes));
System.out.println("獲取到新的數據,來自:" + socketChannel.getRemoteAddress());
//線程池,用來處理業務數據
threadPool.execute(new Runnable() {
@Override
public void run() {
}
});
//向客戶端寫數據
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Length: 11\r\n\r\n" +
"hello world";
ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
while (writeBuffer.hasRemaining()) {
socketChannel.write(writeBuffer);
}
}
}
}
初始化代碼:
/** 服務端通道 */
public ServerSocketChannel serverSocketChannel;
/** 用來接收客戶端連接 */
public MainReactor mainReactor;
/** 用來處理客戶端連接的讀取、寫入 */
public SubReactor[] subReactors = new SubReactor[10];
/** 線程池,用來處理客戶端連接後的業務邏輯 */
public ExecutorService threadPool = Executors.newCachedThreadPool();
public static void main(String[] args) throws IOException {
NioReactor nioReactor = new NioReactor();
nioReactor.initAndRegister();
nioReactor.init();
nioReactor.bind();
}
/** 初始化服務端 */
public void init() throws IOException {
//創建一個服務端通道
serverSocketChannel = ServerSocketChannel.open();
//設置爲非阻塞模式
serverSocketChannel.configureBlocking(false);
//註冊到mainReactor-Thread
mainReactor.register(serverSocketChannel);
//啓動mainReactor-Thread線程
mainReactor.start();
}
/** 服務端綁定端口 */
public void bind() throws IOException {
serverSocketChannel.socket().bind(new InetSocketAddress(8056));
System.out.println("服務端啓動成功");
}
/** 初始化MainReactor和SubReactor */
public void initAndRegister() throws IOException {
mainReactor = new MainReactor();
for (int i=0; i<subReactors.length; i++) {
subReactors[i] = new SubReactor();
}
}
上面代碼mainReactorThread和subReactorThread中有大量的重複代碼,可以提取出來處理:
/** 多路複用,reactor線程模型 */
public class NioReactor2 {
abstract class ReactorThread extends Thread {
//創建一個Selector
public Selector selector;
//用於判斷線程是否已經啓動
public volatile boolean isRunning = false;
/** 有事件發生,就調用這個方法 */
public abstract void handler(SelectableChannel channel) throws IOException;
public ReactorThread() throws IOException {
selector = Selector.open();
}
@Override
public void run() {
while (isRunning) {
//啓動Selector
try {
selector.select();
//獲取事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
//遍歷事件
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
int readyOps = key.readyOps();
//只關注是否有OP_ACCEPT和OP_READ事件
if ((readyOps & (SelectionKey.OP_ACCEPT | SelectionKey.OP_READ)) != 0 || readyOps == 0) {
try {
//獲取channel
SelectableChannel channel = key.channel();
//設置爲非阻塞模式
channel.configureBlocking(false);
//有事件,就調用handler方法
handler(channel);
if (!channel.isOpen()) {
//如果channel關閉,就取消key
key.cancel();
}
} catch (Exception e) {
e.printStackTrace();
//如果有異常,就取消key
key.cancel();
}
}
//處理完之後要移除
iterator.remove();
}
selector.selectNow();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public SelectionKey register(SelectableChannel channel) throws ClosedChannelException {
//先註冊到Selector,並沒有註冊任何事件
return channel.register(selector, 0);
}
@Override
public void start() {
//判斷SubReactor線程是否已經啓動
//如果沒有啓動,就啓動SubReactor線程
if (!isRunning) {
isRunning = true;
super.start();
}
}
}
/** 服務端通道 */
public ServerSocketChannel serverSocketChannel;
/** 用來接收客戶端連接 */
public ReactorThread[] mainReactors = new ReactorThread[1];;
/** 用來處理客戶端連接的讀取、寫入 */
public ReactorThread[] subReactors = new ReactorThread[10];
/** 線程池,用來處理客戶端連接後的業務邏輯 */
public ExecutorService threadPool = Executors.newCachedThreadPool();
/**
* 初始化mainReactors和subReactors
*/
public void initAndRegister() throws IOException {
//subReactors線程,用來客戶端連接後的讀寫
for (int i=0; i<subReactors.length; i++) {
subReactors[i] = new ReactorThread() {
@Override
public void handler(SelectableChannel channel) throws IOException {
SocketChannel socketChannel = (SocketChannel)channel;
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
while (socketChannel.isOpen() && socketChannel.read(readBuffer) != -1) {
//如果有數據可讀,簡單的判斷一下大於0
if (readBuffer.position() > 0) {
break;
}
}
//沒有數據可讀,就直接返回
if (readBuffer.position() == 0) {
return;
}
//轉換爲讀取模式
readBuffer.flip();
byte[] bytes = new byte[readBuffer.limit()];
readBuffer.get(bytes);
System.out.println("獲取到新的數據:" + new String(bytes));
System.out.println("獲取到新的數據,來自:" + socketChannel.getRemoteAddress());
//線程池,用來處理業務數據
threadPool.execute(new Runnable() {
@Override
public void run() {
}
});
//向客戶端寫數據
String response = "HTTP/1.1 200 OK\r\n" +
"Content-Length: 11\r\n\r\n" +
"hello world";
ByteBuffer writeBuffer = ByteBuffer.wrap(response.getBytes());
while (writeBuffer.hasRemaining()) {
socketChannel.write(writeBuffer);
}
}
};
}
//mainReactors線程,用於客戶端的連接
for (int i=0; i<mainReactors.length; i++) {
mainReactors[i] = new ReactorThread() {
AtomicInteger integer = new AtomicInteger(0);
@Override
public void handler(SelectableChannel channel) throws IOException {
//獲取客戶端通道
SocketChannel socketChannel = ((ServerSocketChannel)channel).accept();
//設置爲非阻塞模式
socketChannel.configureBlocking(false);
int index = integer.getAndIncrement() % subReactors.length;
ReactorThread subReactor = subReactors[index];
//啓動線程
subReactor.start();
//註冊事件
SelectionKey key = subReactor.register(socketChannel);
key.interestOps(SelectionKey.OP_READ);
System.out.println("收到新連接:" + socketChannel.getRemoteAddress());
}
};
}
}
/** 初始化服務端 */
public void init() throws IOException {
//創建一個服務端通道
serverSocketChannel = ServerSocketChannel.open();
//設置爲非阻塞模式
serverSocketChannel.configureBlocking(false);
//註冊到mainReactor-Thread
int index = new Random().nextInt(mainReactors.length);
SelectionKey keys = mainReactors[index].register(serverSocketChannel);
keys.interestOps(SelectionKey.OP_ACCEPT);
//啓動mainReactor-Thread線程
mainReactors[index].start();
}
/** 服務端綁定端口 */
public void bind() throws IOException {
serverSocketChannel.socket().bind(new InetSocketAddress(8056));
System.out.println("服務端啓動成功");
}
public static void main(String[] args) throws IOException {
NioReactor2 nioReactor = new NioReactor2();
nioReactor.initAndRegister();
nioReactor.init();
nioReactor.bind();
}
}
結束語
到此,NIO中的Reactor線程模型就結束了,上面的示例可以拆分幾個類進行處理,還可以根據HTTP協議的部分,解析請求,做一個簡單的tomcat服務器。