Java?NIO通信基礎(chǔ)示例詳解
Java NIO 通信基礎(chǔ)介紹
高性能的 Java 通信,絕對(duì)離不開(kāi) Java NIO 技術(shù),現(xiàn)在主流的技術(shù)框架或中間件服務(wù)器,都使 用了 Java NIO 技術(shù),譬如:Tomcat、Jetty、Netty。
Java NIO 由以下三個(gè)核心組件組成:
- Channel(通道)
- Buffer(緩沖區(qū))
- Selector(選擇器)
NIO 和 OIO 的對(duì)比
在 Java 中,NIO 和 OIO 的區(qū)別,主要體現(xiàn)在三個(gè)方面:
- OIO 是面向流(Stream Oriented)的,NIO 是面向緩沖區(qū)(Buffer Oriented)的。 何謂面向流,何謂面向緩沖區(qū)呢? OIO 是面向字節(jié)流或字符流的,在一般的 OIO 操作中,我們以流式的方式順序地從一個(gè)流中讀取一個(gè)或多個(gè)字節(jié),因此,我們不能隨意地改變讀取指針的位置。而在 NIO 操作中則不同,NIO 中引入了 Channel(通道)和 Buffer(緩沖區(qū))的概念。讀取和寫(xiě)入,只需要從通道中讀取數(shù)據(jù)到緩沖區(qū)中,或?qū)?shù)據(jù)從緩沖區(qū)中寫(xiě)入到通道中。NIO 不像 OIO 那樣是順序操作,可以隨意地讀取 Buffer 中任意位置的數(shù)據(jù)。
- OIO 的操作是阻塞的,而 NIO 的操作是非阻塞的。 NIO 如何做到非阻塞的呢?大家都知道,OIO 操作都是阻塞的,例如,我們調(diào)用一個(gè) read 方法讀取一個(gè)文件的內(nèi)容,那么調(diào)用 read 的線(xiàn)程會(huì)被阻塞住,直到 read 操作完成。 而在 NIO 的非阻塞模式中,當(dāng)我們調(diào)用 read 方法時(shí),如果此時(shí)有數(shù)據(jù),則 read 讀取數(shù)據(jù)并返回;如果此時(shí)沒(méi)有數(shù)據(jù),則 read 直接返回,而不會(huì)阻塞當(dāng)前線(xiàn)程。NIO 的非阻塞,是如何做到的呢?NIO 使用了通道和通道的多路復(fù)用技術(shù)。
- OIO 沒(méi)有選擇器(Selector)概念,而 NIO 有選擇器的概念。 NIO 的實(shí)現(xiàn),是基于底層的選擇器的系統(tǒng)調(diào)用。NIO 的選擇器,需要底層操作系統(tǒng)提供支持。 而 OIO 不需要用到選擇器。
使用 FileChannel 完成文件復(fù)制的實(shí)踐案例
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class MyCopyFile {
private File inFile;
private File outFile;
private FileInputStream fis = null;
private FileOutputStream fos = null;
private FileChannel fisChannel = null;
private FileChannel fosChannel = null;
//復(fù)制文件
public void copyFile(String srcPath, String destPath) throws IOException {
try {
inFile = new File(srcPath);
outFile = new File(destPath);
fis = new FileInputStream(inFile);
fos = new FileOutputStream(outFile);
fisChannel = fis.getChannel();
fosChannel = fos.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = -1;
while ((length = fisChannel.read(buffer)) != -1) {
buffer.flip();
int outLenth = 0;
while ((outLenth = fosChannel.write(buffer)) != 0) {
System.out.println("讀取的字節(jié)數(shù)為:" + outLenth);
}
buffer.clear();
}
//強(qiáng)制刷新磁盤(pán)
fosChannel.force(true);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
fosChannel.close();
fos.close();
fisChannel.close();
fis.close();
}
}
public static void main(String[] args) throws IOException {
MyCopyFiletest = new MyCopyFile();
String s1 = "D:\\maze.txt";
String s2 = "D:\\maze1.txt";
MyCopyFile.copyFile(s1, s2);
}
}使用 DatagramChannel 數(shù)據(jù)包通道發(fā)送數(shù)據(jù)的實(shí)踐案例
功能:
獲取用戶(hù)的輸入數(shù)據(jù),通過(guò) DatagramChannel 數(shù)據(jù)報(bào)通道,將數(shù)據(jù)發(fā)送到遠(yuǎn)程的服務(wù)器。
客戶(hù)端代碼:
public class Client {
//Client發(fā)送信息
public void send() throws IOException {
DatagramChannel dChannel = DatagramChannel.open();
dChannel.configureBlocking(false);
ByteBuffer buf = ByteBuffer.allocate(1024);
Scanner sc = new Scanner(System.in);
while (sc.hasNext()) {
String s = sc.nextLine();
buf.put(s.getBytes());
buf.flip();
dChannel.send(buf, new InetSocketAddress("127.0.0.1", 9999));
buf.clear();
}
dChannel.close();
}
public static void main(String[] args) throws IOException {
new Client().send();
}
}
服務(wù)端代碼:
public class Server {
//服務(wù)端接收 用戶(hù)發(fā)來(lái)的信息
public void receive() throws IOException {
DatagramChannel serverChannel = DatagramChannel.open();
//設(shè)置成非阻塞模式
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress("127.0.0.1", 9999));
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_READ);
while (selector.select() > 0) {
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
if (next.isReadable()) {
SocketAddress receive = serverChannel.receive(buffer);
buffer.flip();
String s = new String(buffer.array(), 0, buffer.limit());
System.out.println(s);
buffer.clear();
}
}
iterator.remove();
}
//關(guān)閉選擇器和通道
selector.close();
serverChannel.close();
}
public static void main(String[] args) throws IOException {
new Server().receive();
}
}
使用 NIO 實(shí)現(xiàn) Discard 服務(wù)器的實(shí)踐案例
功能:
僅僅讀取客戶(hù)端通道的輸入數(shù)據(jù),讀取完成后直接關(guān)閉客戶(hù)端通道;并且讀取到的數(shù)據(jù)直接拋棄掉
Discard 服務(wù)器代碼:
public class SocketServerDemo {
public void receive() throws IOException {
//創(chuàng)建服務(wù)器的通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//設(shè)置為非阻塞模式
serverSocketChannel.configureBlocking(false);
//開(kāi)啟選擇器
Selector selector = Selector.open();
//綁定鏈接
serverSocketChannel.bind(new InetSocketAddress(9999));
//將通道的某個(gè)IO事件 注冊(cè)到選擇器上
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//輪詢(xún)所有就緒的IO事件
while (selector.select() > 0) {
//逐個(gè)獲取IO事件
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
//逐個(gè)判斷該IO事件是否為想要的
while (iterator.hasNext()) {
SelectionKey next = iterator.next();
if (next.isAcceptable()) {
//如果為該事件為“連接就緒”事件,就獲取客戶(hù)端的鏈接
SocketChannel clientSocket = serverSocketChannel.accept();
//將客戶(hù)端的鏈接設(shè)置為非阻塞模式
clientSocket.configureBlocking(false);
//將新的通道的可讀事件,注冊(cè)到選擇器上
clientSocket.register(selector, SelectionKey.OP_READ);
} else if (next.isReadable()) {
//若IO事件為“可讀事件”,讀取數(shù)據(jù)
SocketChannel clientSocket = (SocketChannel) next.channel();
//創(chuàng)建緩沖區(qū)
ByteBuffer buffer = ByteBuffer.allocate(1024);
int length = 0;
//讀取事件 讓后丟棄
while ((length = clientSocket.read(buffer)) > 0) {
buffer.flip();
String s = new String(buffer.array(), 0, length);
System.out.println(s);
buffer.clear();
}
clientSocket.close();
}
//移除選擇鍵
iterator.remove();
}
}
serverSocketChannel.close();
}
public static void main(String[] args) throws IOException {
new SocketServerDemo().receive();
}
}
客戶(hù)端的 DiscardClient 代碼:
public class SocketClientDemo {
public void socketClient() throws IOException {
SocketChannel clientSocket = SocketChannel.open(new InetSocketAddress(9999));
//切換成非阻塞模式
clientSocket.configureBlocking(false);
//如果沒(méi)有連接完成 就一直鏈接
while (!clientSocket.finishConnect()){
}
//執(zhí)行到這里說(shuō)明已經(jīng)連接完成了
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("Hello SocketService".getBytes());
buffer.flip();
clientSocket.write(buffer);
clientSocket.shutdownInput();
clientSocket.close();
}
public static void main(String[] args) throws IOException {
new SocketClientDemo().socketClient();
}
}
與 Java OIO 相比,Java NIO 編程大致的特點(diǎn)如下:
(1)在 NIO 中,服務(wù)器接收新連接的工作,是異步進(jìn)行的。不像 Java 的 OIO 那樣,服務(wù)器監(jiān)聽(tīng)連接,是同步的、阻塞的。NIO 可以通過(guò)選擇器(也可以說(shuō)成:多路復(fù)用器),后續(xù)不斷地輪詢(xún)選擇器的選擇鍵集合,選擇新到來(lái)的連接。
(2)在 NIO 中,SocketChannel 傳輸通道的讀寫(xiě)操作都是異步的。如果沒(méi)有可讀寫(xiě)的數(shù)據(jù),負(fù)責(zé) IO 通信的線(xiàn)程不會(huì)同步等待。這樣,線(xiàn)程就可以處理其他連接的通道;不需要像 OIO 那樣,線(xiàn)程一直阻塞,等待所負(fù)責(zé)的連接可用為止。
(3)在 NIO 中,一個(gè)選擇器線(xiàn)程可以同時(shí)處理成千上萬(wàn)個(gè)客戶(hù)端連接,性能不會(huì)隨著客戶(hù)端的增加而線(xiàn)性下降。
以上就是Java NIO通信基礎(chǔ)示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java NIO通信基礎(chǔ)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
IDEA如何將Java項(xiàng)目打包成可執(zhí)行的Jar包
在Java開(kāi)發(fā)中,我們通常會(huì)將我們的項(xiàng)目打包成可執(zhí)行的Jar包,以便于在其他環(huán)境中部署和運(yùn)行,本文將介紹如何使用IDEA集成開(kāi)發(fā)環(huán)境將Java項(xiàng)目打包成可執(zhí)行的Jar包,感興趣的朋友一起看看吧2023-07-07
Java基礎(chǔ)之引用相關(guān)知識(shí)總結(jié)
今天聊聊Java的引用,大多數(shù)時(shí)候我們說(shuō)引用都是強(qiáng)引用,只有在對(duì)象不使用的情況下才會(huì)釋放內(nèi)存,其實(shí)Java 內(nèi)存有四種不同的引用.一起看看吧,,需要的朋友可以參考下2021-05-05
JAVA序列化和反序列化的底層實(shí)現(xiàn)原理解析
這篇文章主要介紹了JAVA序列化和反序列化的底層實(shí)現(xiàn)原理解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11
在?Spring?Boot?中使用?Quartz?調(diào)度作業(yè)的示例詳解
這篇文章主要介紹了在?Spring?Boot?中使用?Quartz?調(diào)度作業(yè)的示例詳解,在本文中,我們將看看如何使用Quartz框架來(lái)調(diào)度任務(wù),Quartz支持在特定時(shí)間運(yùn)行作業(yè)、重復(fù)作業(yè)執(zhí)行、將作業(yè)存儲(chǔ)在數(shù)據(jù)庫(kù)中以及Spring集成,需要的朋友可以參考下2022-07-07
Java實(shí)現(xiàn)簡(jiǎn)單雙色球搖獎(jiǎng)功能過(guò)程解析
這篇文章主要介紹了Java實(shí)現(xiàn)簡(jiǎn)單雙色球搖獎(jiǎng)功能過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09

