根據網上的資料做些整理
Java NIO API詳解
http://www.aygfsteel.com/19851985lili/articles/93524.html
這篇文章對nio的api講解比較全,可以幫助在宏觀上把握nio。
BIO 方式使得整個處理過程和連接是綁定的,只要連接建立,無論客戶端是否有消息發送,都要進行等待處理,一定程度上浪費了服務器端的硬件資源,因此就有了NIO 方式。Java 對于 NIO 方式的支持是通過 Channel和 Selector 方式來實現,采用的方法為向 Channel注冊感興趣的事件,然后通過 Selector 來獲取到發生了事件的 key,如發生了相應的事件,則進行相應的處理,否則則不做任何處理,是典型的Reactor 模式,按照這樣的方式,就不用像 BIO 方式一樣,即使在沒有消息的情況下也需要占據一個線程來阻塞讀取消息,從而提升服務器的使用效率, 為實現 TCP/IP+NIO 方式的系統間通訊, Java 提供了 SocketChannel和 ServerSocketChannel兩個關鍵的類,網絡 IO 的操作則改為通過ByteBuffer 來實現,具體的基于 java實現TCP/IP+NIO 方式的通訊的方法如下所示。
服務器端:
package com.flyoung;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.nio.channels.SocketChannel;
public class NIOServer {
/*標志數字*/
private static int flag = 0;
/*定義緩沖區大小*/
private static int block = 4096;
/*接收緩沖區*/
private static ByteBuffer receiveBuffer = ByteBuffer.allocate(block);
/*發送緩沖區*/
private static ByteBuffer sendBuffer = ByteBuffer.allocate(block);
/*定義Selector*/
private Selector selector;
public NIOServer(int port) throws IOException{
//打開服務器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//服務器配置為非阻塞
serverSocketChannel.configureBlocking(false);
//檢索與此服務器套接字通道關聯的套接字
ServerSocket serverSocket = serverSocketChannel.socket();
//進行服務的綁定
serverSocket.bind(new InetSocketAddress(port));
//通過open()方法找到Selector
selector = Selector.open();
//注冊到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start -----8888:");
}
//監聽
public void listen() throws IOException{
while(true){
//監控所有注冊的 channel ,當其中有注冊的 IO 操作可以進行時,該函數返回,并將對應的 SelectionKey 加入 selected-key set
selector.select();
//Selected-key set 代表了所有通過 select() 方法監測到可以進行 IO 操作的 channel ,這個集合可以通過 selectedKeys() 拿到
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
handleKey(selectionKey);
iterator.remove();
}
}
}
//處理請求
public void handleKey(SelectionKey selectionKey) throws IOException{
//接受請求
ServerSocketChannel serverSocketChannel = null;
SocketChannel socketChannel = null;
String receiveText;
String sendText;
int count;
//測試此鍵的通道是否準備好接受新的套接字連接
if(selectionKey.isAcceptable()){
//返回創建此鍵的通道
serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
//接受客戶端建立連接的請求,并返回 SocketChannel 對象
socketChannel = serverSocketChannel.accept();
//配置為非阻塞
socketChannel.configureBlocking(false);
//注冊到selector
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
//返回為之創建此鍵的通道
socketChannel = (SocketChannel)selectionKey.channel();
//將緩沖區清空,以備下次讀取
receiveBuffer.clear();
//將發送來的數據讀取到緩沖區
count = socketChannel.read(receiveBuffer);
if(count>0){
receiveText = new String(receiveBuffer.array(),0,count);
System.out.println("服務器端接受到的數據---"+receiveText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}else if (selectionKey.isWritable()) {
//將緩沖區清空以備下次寫入
sendBuffer.clear();
// 返回為之創建此鍵的通道。
socketChannel = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向緩沖區中輸入數據
sendBuffer.put(sendText.getBytes());
//將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位
sendBuffer.flip();
//輸出到通道
socketChannel.write(sendBuffer);
System.out.println("服務器端向客戶端發送數據--:"+sendText);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
public static void main(String[] args) throws IOException {
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;
import java.util.Set;
import java.nio.channels.SocketChannel;
public class NIOServer {
/*標志數字*/
private static int flag = 0;
/*定義緩沖區大小*/
private static int block = 4096;
/*接收緩沖區*/
private static ByteBuffer receiveBuffer = ByteBuffer.allocate(block);
/*發送緩沖區*/
private static ByteBuffer sendBuffer = ByteBuffer.allocate(block);
/*定義Selector*/
private Selector selector;
public NIOServer(int port) throws IOException{
//打開服務器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//服務器配置為非阻塞
serverSocketChannel.configureBlocking(false);
//檢索與此服務器套接字通道關聯的套接字
ServerSocket serverSocket = serverSocketChannel.socket();
//進行服務的綁定
serverSocket.bind(new InetSocketAddress(port));
//通過open()方法找到Selector
selector = Selector.open();
//注冊到selector
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("Server Start -----8888:");
}
//監聽
public void listen() throws IOException{
while(true){
//監控所有注冊的 channel ,當其中有注冊的 IO 操作可以進行時,該函數返回,并將對應的 SelectionKey 加入 selected-key set
selector.select();
//Selected-key set 代表了所有通過 select() 方法監測到可以進行 IO 操作的 channel ,這個集合可以通過 selectedKeys() 拿到
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
handleKey(selectionKey);
iterator.remove();
}
}
}
//處理請求
public void handleKey(SelectionKey selectionKey) throws IOException{
//接受請求
ServerSocketChannel serverSocketChannel = null;
SocketChannel socketChannel = null;
String receiveText;
String sendText;
int count;
//測試此鍵的通道是否準備好接受新的套接字連接
if(selectionKey.isAcceptable()){
//返回創建此鍵的通道
serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
//接受客戶端建立連接的請求,并返回 SocketChannel 對象
socketChannel = serverSocketChannel.accept();
//配置為非阻塞
socketChannel.configureBlocking(false);
//注冊到selector
socketChannel.register(selector, SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
//返回為之創建此鍵的通道
socketChannel = (SocketChannel)selectionKey.channel();
//將緩沖區清空,以備下次讀取
receiveBuffer.clear();
//將發送來的數據讀取到緩沖區
count = socketChannel.read(receiveBuffer);
if(count>0){
receiveText = new String(receiveBuffer.array(),0,count);
System.out.println("服務器端接受到的數據---"+receiveText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}else if (selectionKey.isWritable()) {
//將緩沖區清空以備下次寫入
sendBuffer.clear();
// 返回為之創建此鍵的通道。
socketChannel = (SocketChannel) selectionKey.channel();
sendText="message from server--" + flag++;
//向緩沖區中輸入數據
sendBuffer.put(sendText.getBytes());
//將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位
sendBuffer.flip();
//輸出到通道
socketChannel.write(sendBuffer);
System.out.println("服務器端向客戶端發送數據--:"+sendText);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
public static void main(String[] args) throws IOException {
int port = 8888;
NIOServer server = new NIOServer(port);
server.listen();
}
}
客戶端
package com.flyoung;
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.Set;
public class NIOClient {
/*標識數字*/
private static int flag = 0;
/*緩沖區大小*/
private static int BLOCK = 4096;
/*接受數據緩沖區*/
private static ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
/*發送數據緩沖區*/
private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
/*服務器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 8888);
public static void main(String[] args) throws IOException {
// 打開socket通道
SocketChannel clientChannel = SocketChannel.open();
// 設置為非阻塞方式
clientChannel.configureBlocking(false);
// 打開選擇器
Selector selector = Selector.open();
// 注冊連接服務端socket動作
clientChannel.register(selector, SelectionKey.OP_CONNECT);
// 連接
clientChannel.connect(SERVER_ADDRESS);
SocketChannel socketChannel;
Set<SelectionKey> selectionKeys;
String receiveText;
String sendText;
int count=0;
while (true) {
//選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。
//監控所有注冊的 channel ,當其中有注冊的 IO 操作可以進行時,該函數返回,并將對應的 SelectionKey 加入 selected-key set
selector.select();
//返回此選擇器的已選擇鍵集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
for(SelectionKey selectionKey:selectionKeys){
//判斷是否為建立連接的事件
if (selectionKey.isConnectable()) {
System.out.println("client connect");
socketChannel = (SocketChannel) selectionKey.channel(); //
// 判斷此通道上是否正在進行連接操作。
// 完成套接字通道的連接過程。
if (socketChannel.isConnectionPending()) {
//完成連接的建立(TCP三次握手)
socketChannel.finishConnect();
System.out.println("完成連接!");
sendBuffer.clear();
sendBuffer.put("Hello,Server".getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
}
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
socketChannel = (SocketChannel) selectionKey.channel();
//將緩沖區清空以備下次讀取
receiveBuffer.clear();
//讀取服務器發送來的數據到緩沖區中
count=socketChannel.read(receiveBuffer);
if(count>0){
receiveText = new String( receiveBuffer.array(),0,count);
System.out.println("客戶端接受服務器端數據--:"+receiveText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendBuffer.clear();
socketChannel = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendBuffer.put(sendText.getBytes());
//將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位
sendBuffer.flip();
socketChannel.write(sendBuffer);
System.out.println("客戶端向服務器端發送數據--:"+sendText);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}
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.Set;
public class NIOClient {
/*標識數字*/
private static int flag = 0;
/*緩沖區大小*/
private static int BLOCK = 4096;
/*接受數據緩沖區*/
private static ByteBuffer sendBuffer = ByteBuffer.allocate(BLOCK);
/*發送數據緩沖區*/
private static ByteBuffer receiveBuffer = ByteBuffer.allocate(BLOCK);
/*服務器端地址*/
private final static InetSocketAddress SERVER_ADDRESS = new InetSocketAddress(
"localhost", 8888);
public static void main(String[] args) throws IOException {
// 打開socket通道
SocketChannel clientChannel = SocketChannel.open();
// 設置為非阻塞方式
clientChannel.configureBlocking(false);
// 打開選擇器
Selector selector = Selector.open();
// 注冊連接服務端socket動作
clientChannel.register(selector, SelectionKey.OP_CONNECT);
// 連接
clientChannel.connect(SERVER_ADDRESS);
SocketChannel socketChannel;
Set<SelectionKey> selectionKeys;
String receiveText;
String sendText;
int count=0;
while (true) {
//選擇一組鍵,其相應的通道已為 I/O 操作準備就緒。
//監控所有注冊的 channel ,當其中有注冊的 IO 操作可以進行時,該函數返回,并將對應的 SelectionKey 加入 selected-key set
selector.select();
//返回此選擇器的已選擇鍵集。
selectionKeys = selector.selectedKeys();
//System.out.println(selectionKeys.size());
for(SelectionKey selectionKey:selectionKeys){
//判斷是否為建立連接的事件
if (selectionKey.isConnectable()) {
System.out.println("client connect");
socketChannel = (SocketChannel) selectionKey.channel(); //
// 判斷此通道上是否正在進行連接操作。
// 完成套接字通道的連接過程。
if (socketChannel.isConnectionPending()) {
//完成連接的建立(TCP三次握手)
socketChannel.finishConnect();
System.out.println("完成連接!");
sendBuffer.clear();
sendBuffer.put("Hello,Server".getBytes());
sendBuffer.flip();
socketChannel.write(sendBuffer);
}
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (selectionKey.isReadable()) {
socketChannel = (SocketChannel) selectionKey.channel();
//將緩沖區清空以備下次讀取
receiveBuffer.clear();
//讀取服務器發送來的數據到緩沖區中
count=socketChannel.read(receiveBuffer);
if(count>0){
receiveText = new String( receiveBuffer.array(),0,count);
System.out.println("客戶端接受服務器端數據--:"+receiveText);
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
} else if (selectionKey.isWritable()) {
sendBuffer.clear();
socketChannel = (SocketChannel) selectionKey.channel();
sendText = "message from client--" + (flag++);
sendBuffer.put(sendText.getBytes());
//將緩沖區各標志復位,因為向里面put了數據標志被改變要想從中讀取數據發向服務器,就要復位
sendBuffer.flip();
socketChannel.write(sendBuffer);
System.out.println("客戶端向服務器端發送數據--:"+sendText);
socketChannel.register(selector, SelectionKey.OP_READ);
}
}
selectionKeys.clear();
}
}
}