轉載請注明出處
http://blog.csdn.net/sinat_25295611/article/details/77871116
By Kay 2017.09.06
關于Java NIO 基礎,推薦 IBM developerWorks上的一篇文章,寫的非常的好,作者是Greg Travis 。
NIO入門
https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html
NIO Socket Demo
這里貼一個我練習的一個NIO例子,該例子是在閱讀學習《Netty權威指南 》這本書參考寫的,為的是理解NIO的思想。
關鍵點我都寫在注釋中,特別要注意 I/O多路復用模型,把大量的I/O請求連接復用到一個Selector線程中去處理。
以下為例子:
場景:客戶端向服務端發送一條請求當前時間的指令,服務端收到指令后返回當前時間給客戶端輸出。
客戶端:TimeClient
服務端:TimeServer
TimeServer
/**
* Created by kay on 2017/9/8.
*/
public class TimeServer {
public static void main(String[] args) {
int port=8888;
MultiplexerTimeServer timeServer = new MultiplexerTimeServer(port);
new Thread(timeServer,"多路復用TimeServer啟動").start();
}
}
MultiplexerTimeServer 多路復用類,也就是服務端的處理線程
package com.kay.concurrent.nio.timesocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
/**
* Created by kay on 2017/9/7.
* 多路復用類
* 處理多個客戶端的并發請求
* selector 多路復用器
*/
public class MultiplexerTimeServer implements Runnable{
private Selector selector;
private ServerSocketChannel servChannel;
private volatile boolean stop;
/**
* 初始化 綁定注冊監聽
* @param port
*/
public MultiplexerTimeServer(int port) {
try {
selector=Selector.open();
servChannel = ServerSocketChannel.open();
servChannel.configureBlocking(false); //設置非阻塞模式
servChannel.bind(new InetSocketAddress(port)); //綁定端口
servChannel.register(selector, SelectionKey.OP_ACCEPT); //監聽準備連接
System.out.println("TimeServer 正在監聽端口:"+port);
} catch (IOException e) {
e.printStackTrace();
//初始化失敗則退出,例如端口占用等
System.exit(1);
}
}
@Override
public void run() {
while (!stop) {
try {
//每隔一秒 輪詢一次
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
while (it.hasNext()) {
SelectionKey key=it.next();
it.remove();
try {
//處理準備好的事件
handleInput(key);
} catch (Exception e) {
if(key!=null){
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Throwable t) {
t.printStackTrace();
}
}
//關閉多路復用器,綁定在上面的channel也會被自動關閉
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 統一事件處理,根據key 的類型作出相應處理
* 1.對方請求連接->accept->注冊監聽對方發的消息
* 2.對方發送消息->讀取消息->做出響應
* @param key
* @throws IOException
*/
private void handleInput(SelectionKey key) throws IOException{
//判斷是否可用
if (key.isValid()) {
//判斷是否是accept事件
if (key.isAcceptable()) {
//拿到這個key上面綁定的Channel,然后獲取對面來的SocketChannel
//將這個channel注冊 監聽它的讀事件(因為它已經連接了,所以就等著它發消息了)
ServerSocketChannel ssc= (ServerSocketChannel) key.channel();
SocketChannel sc=ssc.accept();
sc.configureBlocking(false);
System.out.println("--新請求接入,開始監聽它發來的消息...");
sc.register(selector, SelectionKey.OP_READ);
}
//判斷對方是否放消息來了,是就讀取消息/作出響應
if (key.isReadable()) {
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String(bytes, "UTF-8");
System.out.println("Time Server 接收到消息:"+body);
String currentTime="";
if ("QUERY_TIME".equals(body)) {
currentTime = "現在時間是:" + new Date(System.currentTimeMillis()).toString();
} else {
currentTime="指令錯誤!";
}
//作出響應
doWrite(socketChannel,currentTime);
} else if (readBytes < 0) {
key.cancel();
socketChannel.close();
}else {
;//讀到0字節忽略
}
}
}
}
//響應消息
private void doWrite(SocketChannel socketChannel, String response) throws IOException {
if (response != null && response.trim().length() > 0) {
byte[] bytes=response.getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
writeBuffer.put(bytes);
writeBuffer.flip();
socketChannel.write(writeBuffer);
System.out.println("--已發送響應");
}
}
}
TimeClient 客戶端
/**
* Created by kay on 2017/9/8.
*/
public class TimeCient {
public static void main(String[] args) {
int port=8888;
TimeClientHandle clientHandle=new TimeClientHandle("127.0.0.1",port);
new Thread(clientHandle,"TimeClient").start();
}
}
TimeClientHandle 客戶端處理線程
package com.kay.concurrent.nio.timesocket;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
/**
* Created by kay on 2017/9/8.
*/
public class TimeClientHandle implements Runnable {
private String host;
private int port;
private SocketChannel socketChannel;
private Selector selector;
private volatile boolean stop=false;
public TimeClientHandle(String host,int port) {
this.host=(host==null )? "127.0.0.1":host;
this.port=port;
try {
selector=Selector.open();
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!stop) {
try {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> it = selectionKeys.iterator();
SelectionKey key=null;
while (it.hasNext()) {
key=it.next();
it.remove();
try {
handleInput(key);
} catch (Exception e) {
if (key != null) {
key.cancel();
if (key.channel() != null) {
key.channel().close();
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
}
if (selector != null) {
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleInput(SelectionKey key) throws IOException {
SocketChannel sc= (SocketChannel) key.channel();
if (key.isConnectable()) {
if (sc.finishConnect()) {
sc.register(selector, SelectionKey.OP_READ);
doWrite(sc);
}else
System.exit(1);
}
if (key.isReadable()) {
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
int readBytes = sc.read(readBuffer);
if (readBytes > 0) {
readBuffer.flip();
byte[] bytes = new byte[readBuffer.remaining()];
readBuffer.get(bytes);
String body = new String((bytes), "UTF-8");
System.out.println("--接收到消息:" + body);
this.stop=true;
} else if (readBytes < 0) {
//對面關掉了鏈接
key.cancel();
sc.close();
}else
; //沒有讀到東西
}
}
private void doConnect() throws IOException {
//客戶端去連接服務器,如果返回false,則說明發送了syn,但服務器沒有響應ack,三次握手沒有完成
if (socketChannel.connect(new InetSocketAddress(host, port))) {
socketChannel.register(selector, SelectionKey.OP_READ);
doWrite(socketChannel);
} else {
//如果沒有直連成功,則新建連接
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel sc) throws IOException {
byte[] req="QUERY_TIME".getBytes();
ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);
writeBuffer.put(req);
writeBuffer.flip();
sc.write(writeBuffer);
if (!writeBuffer.hasRemaining()) {
System.out.println("指令發送成功!");
}
}
}