傳統IO的特點
- 阻塞點 server.accept(); inputStream.read(bytes);
- 單線程情況下只能有一個客戶端
- 用線程池可以有多個客戶端連接,但是非常消耗性能
- 無法作爲長連接服務器可以做短連接(舊版本Tomcat)
NIO的關鍵詞
ServerSocketChannel ServerSocket
SocketChannel Socket
Selector
SelectionKey
NIO的一些問題
1、客戶端關閉的時候會拋出異常,死循環
解決方案
int read = channel.read(buffer);
if(read > 0){
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服務端收到信息:" + msg);
//回寫數據
ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
channel.write(outBuffer);// 將消息回送給客戶端
}else{
System.out.println("客戶端關閉");
key.cancel();
}
2、selector.select();阻塞,那爲什麼說nio是非阻塞的IO?
selector.select()
selector.select(1000);不阻塞
selector.wakeup();也可以喚醒selector
selector.selectNow();也可以立馬返還,視頻裏忘了講了,哈,這裏補上
3、SelectionKey.OP_WRITE是代表什麼意思
OP_WRITE表示底層緩衝區是否有空間,是則響應返還true
NIOServer示例
/**
* NIO服務端
*
*/
public class NIOServer {
// 通道管理器
private Selector selector;
/**
* 獲得一個ServerSocket通道,並對該通道做一些初始化的工作
*
* @param port
* 綁定的端口號
* @throws IOException
*/
public void initServer(int port) throws IOException {
// 獲得一個ServerSocket通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
// 設置通道爲非阻塞
serverChannel.configureBlocking(false);
// 將該通道對應的ServerSocket綁定到port端口
serverChannel.socket().bind(new InetSocketAddress(port));
// 獲得一個通道管理器
this.selector = Selector.open();
// 將通道管理器和該通道綁定,併爲該通道註冊SelectionKey.OP_ACCEPT事件,註冊該事件後,
// 當該事件到達時,selector.select()會返回,如果該事件沒到達selector.select()會一直阻塞。
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
}
/**
* 採用輪詢的方式監聽selector上是否有需要處理的事件,如果有,則進行處理
*
* @throws IOException
*/
public void listen() throws IOException {
System.out.println("服務端啓動成功!");
// 輪詢訪問selector
while (true) {
// 當註冊的事件到達時,方法返回;否則,該方法會一直阻塞
selector.select();
// 獲得selector中選中的項的迭代器,選中的項爲註冊的事件
Iterator<?> ite = this.selector.selectedKeys().iterator();
while (ite.hasNext()) {
SelectionKey key = (SelectionKey) ite.next();
// 刪除已選的key,以防重複處理
ite.remove();
handler(key);
}
}
}
/**
* 處理請求
*
* @param key
* @throws IOException
*/
public void handler(SelectionKey key) throws IOException {
// 客戶端請求連接事件
if (key.isAcceptable()) {
handlerAccept(key);
// 獲得了可讀的事件
} else if (key.isReadable()) {
handelerRead(key);
}
}
/**
* 處理連接請求
*
* @param key
* @throws IOException
*/
public void handlerAccept(SelectionKey key) throws IOException {
ServerSocketChannel server = (ServerSocketChannel) key.channel();
// 獲得和客戶端連接的通道
SocketChannel channel = server.accept();
// 設置成非阻塞
channel.configureBlocking(false);
// 在這裏可以給客戶端發送信息哦
System.out.println("新的客戶端連接");
// 在和客戶端連接成功之後,爲了可以接收到客戶端的信息,需要給通道設置讀的權限。
channel.register(this.selector, SelectionKey.OP_READ);
}
/**
* 處理讀的事件
*
* @param key
* @throws IOException
*/
public void handelerRead(SelectionKey key) throws IOException {
// 服務器可讀取消息:得到事件發生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 創建讀取的緩衝區
ByteBuffer buffer = ByteBuffer.allocate(1024);
int read = channel.read(buffer);
if(read > 0){
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服務端收到信息:" + msg);
//回寫數據
ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
channel.write(outBuffer);// 將消息回送給客戶端
}else{
System.out.println("客戶端關閉");
key.cancel();
}
}
/**
* 啓動服務端測試
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
NIOServer server = new NIOServer();
server.initServer(8000);
server.listen();
}
}