這塊還是挺復雜的,挺難理解,但是多練幾遍,多看看研究研究其實也就那樣,就是一個Selector輪詢的過程,這里想要雙向通信,客戶端和服務端都需要一個Selector,並一直輪詢,
直接貼代碼:
Server:服務端:
package cn.hou.socket01._03nio01;
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.Iterator;
//nio 服務端
public class Server implements Runnable {
//1 多路復用器
private Selector selector;
//2 建立緩沖區
private ByteBuffer readBuf=ByteBuffer.allocate(1024);
private ByteBuffer writeBuf=ByteBuffer.allocate(1024);
//構造函數
public Server(int port){
try {
//1 打開多路復用器
this.selector=Selector.open();
//2 打開服務器通道
ServerSocketChannel ssc = ServerSocketChannel.open();
//3 設置服務器通道為非阻塞方式
ssc.configureBlocking(false);
//4 綁定ip
ssc.bind(new InetSocketAddress(port));
//5 把服務器通道注冊到多路復用器上,只有非阻塞信道才可以注冊選擇器.並在注冊過程中指出該信道可以進行Accept操作
ssc.register(this.selector, SelectionKey.OP_ACCEPT);
System.out.println("服務器已經啟動.....");
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
while(true){//一直循環
try {
this.selector.select();//多路復用器開始監聽
//獲取已經注冊在多了復用器上的key通道集
Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
//遍歷
while (keys.hasNext()) {
SelectionKey key = keys.next();//獲取key
//如果是有效的
if(key.isValid()){
// 如果為阻塞狀態,一般是服務端通道
if(key.isAcceptable()){
this.accept(key);
}
// 如果為可讀狀態,一般是客戶端通道
if(key.isReadable()){
this.read(key);
}
}
//從容器中移除處理過的key
keys.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
//從客戶端通道獲取數據並進行處理
private void read(SelectionKey key) {
try {
//1 清空緩沖區舊的數據
this.readBuf.clear();
//2 獲取之前注冊的socket通道對象
SocketChannel sc = (SocketChannel) key.channel();
//3 讀取數據
int count = sc.read(this.readBuf);
//4 如果沒有數據
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位)
this.readBuf.flip();
//6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
byte[] bytes = new byte[this.readBuf.remaining()];
//7 接收緩沖區數據
this.readBuf.get(bytes);
//8 打印結果
String body = new String(bytes).trim();
System.out.println("服務端接受到客戶端請求的數據: " + body);
//9 告訴客戶端已收到數據
writeBuf.put("你好,客戶端,我已收到數據".getBytes());
//對緩沖區進行復位
writeBuf.flip();
//寫出數據到服務端
sc.write(writeBuf);
//清空緩沖區數據
writeBuf.clear();
} catch (IOException e) {
e.printStackTrace();
}
}
//接受一個客戶端socket進行處理
private void accept(SelectionKey key) {
try {
//1 獲取服務通道
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
//2 執行阻塞方法,當有客戶端請求時,返回客戶端通信通道
SocketChannel sc = ssc.accept();
//3 設置阻塞模式
sc.configureBlocking(false);
//4 注冊到多路復用器上,並設置可讀標識
sc.register(this.selector, SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//啟動服務器
new Thread(new Server(9527)).start();
}
}
Client客戶端:
package cn.hou.socket01._03nio01;
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;
//nio 客戶端
public class Client{
//客戶端信道選擇器,輪詢讀取服務端返回數據
private Selector selector;
//連接信道
private SocketChannel sc;
public Client(){
try {
this.sc=SocketChannel.open();//打開信道
sc.connect(new InetSocketAddress("127.0.0.1",9527));////連接服務端
sc.configureBlocking(false);//設置非阻塞
selector = Selector.open();//必須打開
//將當前客戶端注冊到多路復用器上,並設置為可讀狀態
sc.register(this.selector, SelectionKey.OP_READ);
//開啟線程,一直輪詢
new Thread(()->{
while(true){//一直循環
try {
this.selector.select();//多路復用器開始監聽
//獲取已經注冊在多了復用器上的key通道集
Iterator<SelectionKey> keys = this.selector.selectedKeys().iterator();
//遍歷
while (keys.hasNext()) {
SelectionKey key = keys.next();//獲取key
//如果是有效的
if(key.isValid()){
// 如果為可讀狀態,讀取服務端返回的數據
if(key.isReadable()){
this.read(key);
}
}
//從容器中移除處理過的key
keys.remove();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
//客戶端獲取服務端返回的數據
private void read(SelectionKey key) {
try {
//建立寫緩沖區
ByteBuffer readBuf = ByteBuffer.allocate(1024);
//2 獲取之前注冊的socket通道對象
SocketChannel sc = (SocketChannel) key.channel();
//3 讀取數據
int count = sc.read(readBuf);
//4 如果沒有數據
if(count == -1){
key.channel().close();
key.cancel();
return;
}
//5 有數據則進行讀取 讀取之前需要進行復位方法(把position 和limit進行復位)
readBuf.flip();
//6 根據緩沖區的數據長度創建相應大小的byte數組,接收緩沖區的數據
byte[] bytes = new byte[readBuf.remaining()];
//7 接收緩沖區數據
readBuf.get(bytes);
//8 打印結果
String body = new String(bytes).trim();
System.out.println("客戶端已接受到服務端返回的數據: " + body);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
//建立寫緩沖區
ByteBuffer writebuf = ByteBuffer.allocate(1024);
Client client = new Client();
try {
while(true){
//定義一個字節數組,然后使用系統錄入功能:
byte[] bytes = new byte[1024];
System.in.read(bytes);
//把數據放到緩沖區中
writebuf.put(bytes);
//對緩沖區進行復位
writebuf.flip();
//寫出數據到服務端
client.sc.write(writebuf);
//清空緩沖區數據
writebuf.clear();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(client.sc != null){
try {
client.sc.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
,先啟動服務端,然后再啟動客戶端:
效果如下:
Server:

Client:

