Java入門系列-25-NIO(實現非阻塞網路通訊)
還記得之前介紹NIO時對比傳統IO的一大特點嗎?就是NIO是非阻塞式的,這篇文章帶大家來看一下非阻塞的網路操作。
補充:以陣列的形式使用緩衝區
package testnio;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class TestBufferArray {
public static void main(String[] args) throws IOException {
RandomAccessFile raf1=new RandomAccessFile("D:/1.txt","rw");
//1.獲取通道
FileChannel channel1=raf1.getChannel();
//2.建立緩衝區陣列
ByteBuffer buf1=ByteBuffer.allocate(512);
ByteBuffer buf2=ByteBuffer.allocate(512);
ByteBuffer[] bufs= {buf1,buf2};
//3.將資料讀入緩衝區陣列
channel1.read(bufs);
for (ByteBuffer byteBuffer : bufs) {
byteBuffer.flip();
}
System.out.println(new String(bufs[0].array(),0,bufs[0].limit()));
System.out.println("-----------");
System.out.println(new String(bufs[1].array(),0,bufs[1].limit()));
//寫入緩衝區陣列到通道中
RandomAccessFile raf2=new RandomAccessFile("D:/2.txt","rw");
FileChannel channel2=raf2.getChannel();
channel2.write(bufs);
}
}
使用NIO實現阻塞式網路通訊
TCP協議的網路通訊傳統實現方式是通過套接字程式設計(Socket和ServerSocket),NIO實現TCP網路通訊需要用到 Channel 介面的兩個實現類:SocketChannel和ServerSocketChannel
使用NIO實現阻塞式網路通訊
客戶端
package com.jikedaquan.blockingnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Client {
public static void main(String[] args) {
SocketChannel sChannel=null;
FileChannel inChannel=null;
try {
//1、獲取通道
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
//用於讀取檔案
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
//2、分配指定大小的緩衝區
ByteBuffer buf=ByteBuffer.allocate(1024);
//3、讀取本地檔案,傳送到伺服器端
while(inChannel.read(buf)!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//關閉通道
if (inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
new InetSocketAddress(“127.0.0.1”, 1666) 用於向客戶端套接字通道(SocketChannel)繫結要連線地址和埠
服務端
package com.jikedaquan.blockingnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Server {
public static void main(String[] args) {
ServerSocketChannel ssChannel=null;
FileChannel outChannel=null;
SocketChannel sChannel=null;
try {
//1、獲取通道
ssChannel = ServerSocketChannel.open();
//用於儲存檔案的通道
outChannel = FileChannel.open(Paths.get("F:/b.jpg"), StandardOpenOption.WRITE,StandardOpenOption.CREATE);
//2、繫結要監聽的埠號
ssChannel.bind(new InetSocketAddress(1666));
//3、獲取客戶端連線的通道
sChannel = ssChannel.accept();
//4、分配指定大小的緩衝區
ByteBuffer buf=ByteBuffer.allocate(1024);
//5、接收客戶端的資料,並儲存到本地
while(sChannel.read(buf)!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//6、關閉通道
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ssChannel!=null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服務端套接字僅繫結要監聽的埠即可
ssChannel.bind(new InetSocketAddress(1666));
上面的程式碼使用NIO實現的網路通訊,可能有同學會問,沒有看到阻塞效果啊,確實是阻塞式的看不到效果,因為客戶端傳送一次資料就結束了,服務端也是接收一次資料就結束了。那如果服務端接收完成資料後,再向客戶端反饋呢?
能夠看到阻塞效果的網路通訊
客戶端
package com.jikedaquan.blockingnio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Client {
public static void main(String[] args) {
SocketChannel sChannel=null;
FileChannel inChannel=null;
try {
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 1666));
inChannel = FileChannel.open(Paths.get("F:/a.jpg"), StandardOpenOption.READ);
ByteBuffer buf=ByteBuffer.allocate(1024);
while(inChannel.read(buf)!=-1) {
buf.flip();
sChannel.write(buf);
buf.clear();
}
//sChannel.shutdownOutput();//去掉註釋掉將不會阻塞
//接收伺服器端的反饋
int len=0;
while((len=sChannel.read(buf))!=-1) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
if(inChannel!=null) {
try {
inChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服務端
package com.jikedaquan.blockingnio2;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
public class Server {
public static void main(String[] args) {
ServerSocketChannel ssChannel=null;
FileChannel outChannel=null;
SocketChannel sChannel=null;
try {
ssChannel = ServerSocketChannel.open();
outChannel = FileChannel.open(Paths.get("F:/a.jpg"),StandardOpenOption.WRITE,StandardOpenOption.CREATE);
ssChannel.bind(new InetSocketAddress(1666));
sChannel = ssChannel.accept();
ByteBuffer buf=ByteBuffer.allocate(1024);
while(sChannel.read(buf)!=-1) {
buf.flip();
outChannel.write(buf);
buf.clear();
}
//傳送反饋給客戶端
buf.put("服務端接收資料成功".getBytes());
buf.flip();
sChannel.write(buf);
} catch (IOException e) {
e.printStackTrace();
}finally {
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(outChannel!=null) {
try {
outChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(ssChannel!=null) {
try {
ssChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服務端將向客戶端傳送兩次資料
選擇器(Selector)
想要實現非阻塞的IO,必須要先弄懂選擇器。Selector 抽象類,可通過呼叫此類的 open 方法建立選擇器,該方法將使用系統的預設選擇器提供者建立新的選擇器。
將通道設定為非阻塞之後,需要將通道註冊到選擇器中,註冊的同時需要指定一個選擇鍵的型別 (SelectionKey)。
選擇鍵(SelectionKey)可以認為是一種標記,標記通道的型別和狀態。
SelectionKey的靜態欄位:
OP_ACCEPT:用於套接字接受操作的操作集位
OP_CONNECT:用於套接字連線操作的操作集位
OP_READ:用於讀取操作的操作集位
OP_WRITE:用於寫入操作的操作集位
用於檢測通道狀態的方法:
方法名稱 | 說明 |
---|---|
isAcceptable() | 測試此鍵的通道是否已準備好接受新的套接字連線 |
isConnectable() | 測試此鍵的通道是否已完成其套接字連線操作 |
isReadable() | 測試此鍵的通道是否已準備好進行讀取 |
isWritable() | 測試此鍵的通道是否已準備好進行寫入 |
將通道註冊到選擇器:
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
IO操作準備就緒的通道大於0,輪詢選擇器
while(selector.select()>0) {
//獲取選擇鍵,根據不同的狀態做不同的操作
}
實現非阻塞式TCP協議網路通訊
非阻塞模式:channel.configureBlocking(false);
客戶端
package com.jikedaquan.nonblockingnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.Date;
import java.util.Scanner;
public class Client {
public static void main(String[] args) {
SocketChannel sChannel=null;
try {
//1、獲取通道
sChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",1666));
//2、切換非阻塞模式
sChannel.configureBlocking(false);
//3、分配指定大小的緩衝區
ByteBuffer buf=ByteBuffer.allocate(1024);
//4、傳送資料給服務端
Scanner scanner=new Scanner(System.in);
//迴圈從控制檯錄入資料傳送給服務端
while(scanner.hasNext()) {
String str=scanner.next();
buf.put((new Date().toString()+"
"+str).getBytes());
buf.flip();
sChannel.write(buf);
buf.clear();
}
} catch (IOException e) {
e.printStackTrace();
}finally {
//5、關閉通道
if(sChannel!=null) {
try {
sChannel.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
服務端
package com.jikedaquan.nonblockingnio;
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;
public class Server {
public static void main(String[] args) throws IOException {
//1、獲取通道
ServerSocketChannel ssChannel=ServerSocketChannel.open();
//2、切換非阻塞模式
ssChannel.configureBlocking(false);
//3、繫結監聽的埠號
ssChannel.bind(new InetSocketAddress(1666));
//4、獲取選擇器
Selector selector=Selector.open();
//5、將通道註冊到選擇器上,並指定“監聽接收事件”
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
//6、輪詢式的獲取選擇器上已經 “準備就緒”的事件
while(selector.select()>0) {
//7、獲取當前選擇器中所有註冊的“選擇鍵(已就緒的監聽事件)”
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
while(it.hasNext()) {
//8、獲取準備就緒的事件
SelectionKey sk=it.next();
//9、判斷具體是什麼事件準備就緒
if(sk.isAcceptable()) {
//10、若“接收就緒”,獲取客戶端連線
SocketChannel sChannel=ssChannel.accept();
//11、切換非阻塞模式
sChannel.configureBlocking(false);
//12、將該通道註冊到選擇器上
sChannel.register(selector, SelectionKey.OP_READ);
}else if(sk.isReadable()) {
//13、獲取當前選擇器上“讀就緒”狀態的通道
SocketChannel sChannel=(SocketChannel)sk.channel();
//14、讀取資料
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
while((len=sChannel.read(buf))>0) {
buf.flip();
System.out.println(new String(buf.array(),0,len));
buf.clear();
}
}
//15、取消選擇鍵 SelectionKey
it.remove();
}
}
}
}
服務端接收客戶端的操作需要在判斷 isAcceptable() 方法內將就緒的套接字通道以讀操作註冊到 選擇器中
在判斷 isReadable() 內從通道中獲取資料
實現非阻塞式UDP協議網路通訊
傳送端
package com.jikedaquan.nonblockingnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.util.Scanner;
public class TestDatagramSend {
public static void main(String[] args) throws IOException {
//獲取通道
DatagramChannel dChannel=DatagramChannel.open();
//非阻塞
dChannel.configureBlocking(false);
ByteBuffer buf=ByteBuffer.allocate(1024);
Scanner scanner=new Scanner(System.in);
while(scanner.hasNext()) {
String str=scanner.next();
buf.put(str.getBytes());
buf.flip();
//傳送資料到目標地址和埠
dChannel.send(buf,new InetSocketAddress("127.0.0.1", 1666));
buf.clear();
}
dChannel.close();
}
}
接收端
package com.jikedaquan.nonblockingnio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Iterator;
public class TestDatagramReceive {
public static void main(String[] args) throws IOException {
//獲取通道
DatagramChannel dChannel=DatagramChannel.open();
dChannel.configureBlocking(false);
//繫結監聽埠
dChannel.bind(new InetSocketAddress(1666));
//獲取選擇器
Selector selector=Selector.open();
//讀操作註冊通道
dChannel.register(selector, SelectionKey.OP_READ);
while(selector.select()>0) {
Iterator<SelectionKey> it=selector.selectedKeys().iterator();
//迭代選擇鍵
while(it.hasNext()) {
SelectionKey sk=it.next();
//通道可讀
if(sk.isReadable()) {
ByteBuffer buf=ByteBuffer.allocate(1024);
//接收資料存入緩衝區
dChannel.receive(buf);
buf.flip();
System.out.println(new String(buf.array(),0,buf.limit()));
buf.clear();
}
}
it.remove();
}
}
}
相關文章
- Thinking in Java--使用NIO實現非阻塞Socket通訊ThinkingJava
- Java Websocket實現即時通訊功能入門教程JavaWeb
- 網路通訊2:TCP通訊實現TCP
- Java 網路程式設計 —— 實現非阻塞式的伺服器Java程式設計伺服器
- MQTT物聯網通訊協議入門及Demo實現MQQT協議
- docker系列(五):網路通訊Docker
- 網路通訊單元NU系列
- java同步非阻塞IOJava
- IO通訊模型(二)同步非阻塞模式NIO(NonBlocking IO)模型模式BloC
- vue3 快速入門系列 —— 元件通訊Vue元件
- java實現UDP通訊JavaUDP
- [開源] gev (支援 websocket 啦): Go 實現基於 Reactor 模式的非阻塞網路庫WebGoReact模式
- python中非同步非阻塞如何實現Python非同步
- 網路通訊3:HTTP實現文字傳輸HTTP
- UDP協議網路Socket程式設計(java實現C/S通訊案例)UDP協議程式設計Java
- Java實現TCP通訊程式JavaTCP
- 使用Java實現WebSocket通訊JavaWeb
- [iptables] 基於iptables實現的跨網路通訊
- ES系列(三):網路通訊模組解析
- Java:基於TCP協議網路socket程式設計(實現C/S通訊)JavaTCP協議程式設計
- java多執行緒實現TCP網路Socket程式設計(C/S通訊)Java執行緒TCP程式設計
- Java中I/O流:阻塞和非阻塞範例Java
- Java入門到實踐系列(1)——Java簡介Java
- Java 網路程式設計 —— 非阻塞式程式設計Java程式設計
- WebRTC---網路實時通訊Web
- Linux 阻塞和非阻塞 IO 實驗學習Linux
- 使用Task實現非阻塞式的I/O操作
- JAVA通訊(三)——實現多人聊天Java
- Java實驗——基於GUI的網路通訊程式設計JavaGUI程式設計
- 網路通訊
- Java 非阻塞 IO 和非同步 IOJava非同步
- TensorFlow.NET機器學習入門【3】採用神經網路實現非線性迴歸機器學習神經網路
- bridge網路實現多個單主機進行通訊
- 網路通訊4:HTTP實現二進位制傳輸HTTP
- tornado原理介紹及非同步非阻塞實現方式非同步
- Java入門系列之finalJava
- 阻塞IO與非阻塞IO
- 同步非同步,阻塞非阻塞非同步