最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Netty實(shí)戰(zhàn)源碼解析NIO編程

 更新時(shí)間:2022年12月21日 10:46:12   作者:Zhongger  
這篇文章主要為大家介紹了Netty實(shí)戰(zhàn)源碼解析NIO編程的核心組件及關(guān)系詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

1 前言

很久之前就想寫與Netty相關(guān)的博客了,但由于個(gè)人時(shí)間安排的問題一直拖到了現(xiàn)在,借助這個(gè)機(jī)會(huì),重新溫習(xí)Java高級(jí)編程的同時(shí),也把Netty實(shí)戰(zhàn)以及源碼剖析分享給各位讀者。

2 Netty是什么?

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server. 'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

摘自官網(wǎng),翻譯過來就是:Netty是一個(gè)基于NIO的客戶端-服務(wù)端框架,能過快速而簡單地開發(fā)像客戶端-服務(wù)端協(xié)議的網(wǎng)絡(luò)應(yīng)用。它極大地精簡了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程。“快速而簡單”并不意味著生成的應(yīng)用程序會(huì)受到可維護(hù)性或性能問題的影響。Netty 是根據(jù)從許多協(xié)議(如 FTP、SMTP、HTTP 以及各種二進(jìn)制和基于文本的遺留協(xié)議)的實(shí)現(xiàn)中獲得的經(jīng)驗(yàn)精心設(shè)計(jì)的。結(jié)果,Netty 成功地找到了一種方法,可以在不妥協(xié)的情況下實(shí)現(xiàn)易于開發(fā)、性能、穩(wěn)定性和靈活性。

3 Java I/O模型簡介

要說到網(wǎng)絡(luò)通信,就離不開I/O模型,可以把I/O模型簡單理解為使用什么通道進(jìn)行數(shù)據(jù)的發(fā)送和接收。

Java共支持三種網(wǎng)絡(luò)編程模型:BIO、NIO、AIO

  • BIO,同步阻塞IO,服務(wù)器實(shí)現(xiàn)模式為一個(gè)連接一個(gè)線程,即客戶端有一個(gè)請(qǐng)求連接服務(wù)器時(shí),服務(wù)器就會(huì)啟動(dòng)一個(gè)線程進(jìn)行處理,可見當(dāng)有多個(gè)客戶端發(fā)出請(qǐng)求時(shí),服務(wù)器需要啟動(dòng)等量的線程,而且當(dāng)客戶端沒有響應(yīng)時(shí),線程也必須一直等待,長期下來需要大量的線程且線程利用率低,會(huì)造成浪費(fèi)。
  • NIO,同步非阻塞,服務(wù)器用一個(gè)線程來處理多個(gè)請(qǐng)求,客戶端發(fā)送的請(qǐng)求會(huì)注冊(cè)到多路復(fù)用器(selector選擇器)上,有I/O請(qǐng)求的客戶端分配線程處理。
  • AIO,異步非阻塞,AIO引入了異步通道的概念,采用Proactor模式,簡化程序編寫,有效的請(qǐng)求才啟動(dòng)線程,特點(diǎn)是要先由操作系統(tǒng)完成后才通知服務(wù)的程序啟動(dòng)線程去處理,一般適用于連接數(shù)較多且連接時(shí)間較長的應(yīng)用??蛻舳税l(fā)送的請(qǐng)求先交給操作系統(tǒng)處理,OS處理后再通知線程

Netty其實(shí)就是基于Java的NIO的。接下來,我們通過編寫代碼來體驗(yàn)一下這三種IO模型吧

3.1 BIO代碼實(shí)現(xiàn)

package com.Zhongger;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * @author zhongmingyi
 * @date 2021/9/15 1:29 下午
 */
public class BIOServer {
    public static void main(String[] args) throws IOException {
        ExecutorService threadPool = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(8989);
        System.out.println("服務(wù)端已啟動(dòng)");
        while (true) {
            Socket socket = serverSocket.accept();
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    handle(socket);
                }
            });
        }
    }
    public static void handle(Socket socket) {
        byte[] bytes = new byte[1024];
        try {
            InputStream inputStream = socket.getInputStream();
            int read = 0;
            while (true) {
                read = inputStream.read(bytes);
                if (read != -1) {
                    System.out.println("客戶端發(fā)送給服務(wù)端的數(shù)據(jù):" + new String(bytes, 0, read));
                } else {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

上述代碼中:

  • 首先是服務(wù)器開啟了一個(gè)ServerSocket,綁定在8989端口上,循環(huán)等待接受客戶端的連接
  • 當(dāng)客戶端連接到了服務(wù)器后,ServerSocket.accept方法可以獲取到客戶端的Socket
  • 每當(dāng)有一個(gè)客戶端連接了服務(wù)器,線程池就會(huì)啟動(dòng)一個(gè)線程去處理Socket中的IO數(shù)據(jù)流,通過InputStream的read方法讀取客戶端發(fā)給服務(wù)器的數(shù)據(jù),并輸出打?。划?dāng)InputStream沒有數(shù)據(jù)了,最后將Socket關(guān)閉,該線程會(huì)回收到線程池中

可以看到,BIO模型里,服務(wù)器的實(shí)現(xiàn)模式為一個(gè)Socket連接對(duì)應(yīng)一個(gè)線程。 BIO的知識(shí)點(diǎn)就介紹到這里,相信大家在【計(jì)算機(jī)網(wǎng)絡(luò)】課程的學(xué)習(xí)中,肯定有接觸過Socket編程,實(shí)現(xiàn)一個(gè)簡易版的聊天工具。

4 Java NIO

4.1 基本介紹

JDK 1.4中的java.nio.*包中引入新的Java I/O庫,NIO其實(shí)有兩種解釋:

  • New I/O:原因在于它相對(duì)于之前的I/O類庫是新增的。
  • Non-block I/O:由于之前老的I/O類庫是阻塞I/O,New I/O類庫的目標(biāo)就是要讓Java支持非阻塞I/O,所以,更多的人喜歡稱之為非阻塞I/O。

NIO有三個(gè)核心組件:

  • Buffer緩沖區(qū) (相當(dāng)于運(yùn)載了貨物的火車)
  • Channel管道(相當(dāng)于軌道,負(fù)責(zé)運(yùn)輸Buffer)
  • Selector選擇器(相當(dāng)于車票,用來選擇火車應(yīng)該通過哪個(gè)Channel去運(yùn)輸)

NIO是面向塊(緩沖區(qū))的處理,數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可以在緩沖區(qū)里前后移動(dòng),增加了在處理過程中的靈活性,使用NIO可以提供非阻塞式的高伸縮性網(wǎng)絡(luò)。這使得一個(gè)線程可以在Buffer里有數(shù)據(jù)的時(shí)候去讀取,沒有可用數(shù)據(jù)時(shí)就可以去做其他事情,不會(huì)阻塞讀;線程也可以寫入一些數(shù)據(jù)到Buffer中,無需等待寫入所有的數(shù)據(jù),也可以去做別的事情,不會(huì)阻塞寫。

4.2 三大核心組件的關(guān)系

三大核心組件的關(guān)系簡單描述圖如下:

如圖所示:

  • 每個(gè)Thread對(duì)應(yīng)一個(gè)Selector,每個(gè)Selector對(duì)應(yīng)多個(gè)Channel
  • 每個(gè)Channel都會(huì)有一個(gè)對(duì)應(yīng)的Buffer,Channel是雙向的,可以返回底層操作系統(tǒng)的情況(比如Linux,通道就是雙向的),這與BIO中流是單向的不同
  • Selector切換到哪個(gè)Channel進(jìn)行處理是由事件Event決定的
  • Buffer是一個(gè)內(nèi)存塊,底層就是數(shù)組,可以寫入數(shù)據(jù),可以通過flip方法來切換成讀取數(shù)據(jù),Buffer也是雙向的

4.3 Buffer緩沖區(qū)

Buffer:緩沖區(qū)本質(zhì)上是一個(gè)可以讀寫數(shù)據(jù)的內(nèi)存塊,可以理解為一個(gè)容器對(duì)象(含數(shù)組),該對(duì)象提供了一組方法,可以更輕松地使用內(nèi)存塊,緩沖區(qū)對(duì)象內(nèi)置了一些機(jī)制,能過跟蹤和記錄緩沖區(qū)的變化情況。Channel提供了從網(wǎng)絡(luò)、文件讀取數(shù)據(jù)的渠道,但讀取或者寫入數(shù)據(jù)都需要經(jīng)過Buffer。

Buffer是一個(gè)頂層的抽象類,它的子類有多種實(shí)現(xiàn),常用的子類如下:

  • ByteBuffer:用于操作字節(jié)緩沖區(qū)
  • CharBuffer:用于操作字符緩沖區(qū)
  • ShortBuffer:用于操作短整型緩沖區(qū)
  • IntBuffer:用于操作整型緩沖區(qū)
  • LongBuffer:用于操作長整型緩沖區(qū)
  • FloatBuffer:用于操作浮點(diǎn)型緩沖區(qū)
  • DoubleBuffer:用于操作雙精度浮點(diǎn)型緩沖區(qū)

上述緩沖區(qū)的管理方式基本上一致,都可以用類的allocate(int capacity) 方法去獲取緩沖區(qū)對(duì)象。前面說到,Buffer是和數(shù)據(jù)打交道的載體,也就是讀取緩沖區(qū)的數(shù)據(jù)或者把寫數(shù)據(jù)到緩沖區(qū)中。所以,Buffer緩沖區(qū)的核心方法就是 put()方法和get()方法以及對(duì)應(yīng)的重載方法、擴(kuò)展方法等。

Buffer類中有以下四個(gè)屬性:

// Invariants: mark <= position <= limit <= capacity
private int mark = -1; 
private int position = 0;
private int limit;
private int capacity;
  • Capacity:Buffer緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量,容量在緩沖區(qū)創(chuàng)建時(shí)被設(shè)定,中途無法被修改。Capacity也就規(guī)定了Buffer中底層的數(shù)組的大小
  • Limit:Buffer緩沖區(qū)里的當(dāng)前的終點(diǎn),不能對(duì)緩沖區(qū)超過Limit的位置進(jìn)行讀寫操作,Limit是可以被修改的
  • Position:Buffer緩沖區(qū)中下一個(gè)要被讀或?qū)懙脑氐乃饕恢?,Position會(huì)自動(dòng)由相應(yīng)的 get( )和 put( )函數(shù)更新,為下一次讀/寫做準(zhǔn)備
  • 標(biāo)記Mark一個(gè)備忘位置。用于記錄上一次讀寫的位置。

簡單看下ByteBuffer的使用,體會(huì)下往其中寫入數(shù)據(jù)、切換成讀模式后上面這四個(gè)值的變換:

		ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        System.out.println("初始時(shí)-->limit--->" + byteBuffer.limit());
        System.out.println("初始時(shí)-->position--->" + byteBuffer.position());
        System.out.println("初始時(shí)-->capacity--->" + byteBuffer.capacity());
        System.out.println("初始時(shí)-->mark--->" + byteBuffer.mark());
        System.out.println("--------------------------------------");
        // 添加一些數(shù)據(jù)到緩沖區(qū)中
        String s = "后端Dancer";
        byteBuffer.put(s.getBytes());
        // 看一下初始時(shí)4個(gè)核心變量的值
        System.out.println("put完之后-->limit--->" + byteBuffer.limit());
        System.out.println("put完之后-->position--->" + byteBuffer.position());
        System.out.println("put完之后-->capacity--->" + byteBuffer.capacity());
        System.out.println("put完之后-->mark--->" + byteBuffer.mark());
        System.out.println("--------------------------------------");
        byteBuffer.flip();
        System.out.println("flip完之后-->limit--->" + byteBuffer.limit());
        System.out.println("flip完之后-->position--->" + byteBuffer.position());
        System.out.println("flip完之后-->capacity--->" + byteBuffer.capacity());
        System.out.println("flip完之后-->mark--->" + byteBuffer.mark());

這里介紹一個(gè)比較高效的ByteBuffer,MappedByteBuffer 它可以實(shí)現(xiàn)文件在堆外內(nèi)存(非JVM內(nèi)存的系統(tǒng)內(nèi)存)的修改:

	public static void mappedByteBufferTest() throws IOException {
        RandomAccessFile file = new RandomAccessFile("/Users/bytedance/Desktop/file.txt", "rw");
        FileChannel fileChannel = file.getChannel();
        //0~3的位置是直接映射到內(nèi)存中的,可以修改文件中的這部分內(nèi)容
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 3);
        mappedByteBuffer.put(0, (byte) 'Y');
        mappedByteBuffer.put(2, (byte) 'K');
        file.close();
    }

4.4 Channel通道

BIO中流是單向的,要么是輸入流,要么是輸出流。然后NIO中,Channel作為運(yùn)輸數(shù)據(jù)的通道,是雙向的。Channel是一個(gè)抽象類,常用的實(shí)現(xiàn)有:ServerSocketChannel、SocketChannel、FileChannel、DatagramChannel,其中DatagramChannel是用于UDP數(shù)據(jù),而其他三者用于TCP數(shù)據(jù)。

FileChannel類主要用于對(duì)本地文件進(jìn)行IO操作,常用的方法有:

  • public int read(ByteBuffer var1) 從Channel讀取數(shù)據(jù)并放到ByteBuffer中
  • public int write(ByteBuffer var1) 從把ByteBuffer中的數(shù)據(jù)寫入到Channel中
  • public long transferFrom(ReadableByteChannel var1, long var2, long var4) 從ReadableByteChannel復(fù)制數(shù)據(jù)到Channel中
  • public long transferTo(long var1, long var3, WritableByteChannel var5) 從Channel中把數(shù)據(jù)復(fù)制到WritableByteChannel

下面簡單看下這個(gè)例子,將字符串輸出到文件:

import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
/**
 * @author zhongmingyi
 * @date 2021/9/16 10:38 下午
 */
public class FileChannelTest {
    public static void main(String[] args) throws IOException {
        String text = "Hello, Zhongger!";
        //創(chuàng)建一個(gè)文件輸出流
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/bytedance/Desktop/file.txt");
        //通過文件輸出流獲取到FileChannel
        FileChannel fileChannel = fileOutputStream.getChannel();
        //創(chuàng)建一個(gè)ByteBuffer,將數(shù)據(jù)寫入ByteBuffer中
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(text.getBytes());
        //切換成讀模式
        buffer.flip();
        //把ByteBuffer里到數(shù)據(jù)寫入到FileChannel中
        fileChannel.write(buffer);
        //關(guān)閉文件輸出流
        fileOutputStream.close();
    }
}

再看下從本地文件讀取數(shù)據(jù)的例子

 	public static void readFromFile() throws IOException {
        //創(chuàng)建一個(gè)文件輸入流
        FileInputStream fileInputStream = new FileInputStream("/Users/bytedance/Desktop/file.txt");
        //通過文件輸入流獲取到FileChannel
        FileChannel fileChannel = fileInputStream.getChannel();
        //創(chuàng)建一個(gè)ByteBuffer,將Channel的數(shù)據(jù)讀取到ByteBuffer中
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        fileChannel.read(buffer);
        //輸出ByteBuffer中的數(shù)據(jù)
        System.out.println(new String(buffer.array()));
        //關(guān)閉文件輸入流
        fileInputStream.close();
    }

從一個(gè)文件讀取數(shù)據(jù)到Buffer,再把Buffer寫入到另外一個(gè)文件

 	public static void readFromOneFileWriteToOtherFile() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("/Users/bytedance/Desktop/file.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/bytedance/Desktop/file2.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        while (true) {
        	//Buffer復(fù)位,防止越界
            buffer.clear();
            int read = fileInputStreamChannel.read(buffer);
            if (read == -1) {
                break;
            }
            //切讀
            buffer.flip();
            fileOutputStreamChannel.write(buffer);
        }
        fileInputStream.close();
        fileOutputStream.close();
    }

把一個(gè)文件復(fù)制到另一個(gè)文件:

 	public static void transferFrom() throws IOException {
        FileInputStream fileInputStream = new FileInputStream("/Users/bytedance/Desktop/file.txt");
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/bytedance/Desktop/file_copy.txt");
        FileChannel fileInputStreamChannel = fileInputStream.getChannel();
        FileChannel fileOutputStreamChannel = fileOutputStream.getChannel();
        fileOutputStreamChannel.transferFrom(fileInputStreamChannel, 0, fileInputStreamChannel.size());
        fileInputStream.close();
        fileOutputStream.close();
    }

再簡單介紹下:FileChannel提供了map方法把文件映射到虛擬內(nèi)存,通常情況可以映射整個(gè)文件,如果文件比較大,可以進(jìn)行分段映射。簡單地說就是通過映射的方式來減少一次內(nèi)核態(tài)到用戶態(tài)之間的拷貝,因此可以提高復(fù)制的性能。

	public static void mappedByteBufferTest() throws IOException {
        RandomAccessFile file = new RandomAccessFile("/Users/bytedance/Desktop/file.txt", "rw");
        FileChannel fileChannel = file.getChannel();
        MappedByteBuffer mappedByteBuffer = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0, 3);
        mappedByteBuffer.put(0, (byte) 'Y');
        mappedByteBuffer.put(2, (byte) 'K');
        file.close();
    }

4.5 Selector選擇器

Selector選擇器是NIO中的多路復(fù)用器,一個(gè)線程對(duì)應(yīng)一個(gè)Selector,而Selector中可以注冊(cè)多個(gè)Channel,當(dāng)Channel中有事件發(fā)生時(shí),線程就可以去處理這個(gè)事件,若沒有事件發(fā)生時(shí),線程可以空出來去做其他事情。使用Selector的好處在于: 使用更少的線程來就可以來處理通道了, 相比使用多個(gè)線程,避免了線程上下文切換帶來的開銷。

4.5.1 Selector的創(chuàng)建

通過調(diào)用Selector.open()方法創(chuàng)建一個(gè)Selector對(duì)象,如下:

Selector selector = Selector.open();

4.5.2 注冊(cè)Channel到Selector

Channel必須是非阻塞的,否則會(huì)拋出IllegalBlockingModeException 異常

channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_ACCEPT);

該方法可以將Channel設(shè)置為非阻塞的

abstract SelectableChannel configureBlocking(boolean block)  

注意: SelectableChannel抽象類的configureBlocking()方法是由AbstractSelectableChannel抽象類實(shí)現(xiàn)的,SocketChannel、ServerSocketChannel、DatagramChannel都是直接繼承了AbstractSelectableChannel抽象類,因此它們可以調(diào)用configureBlocking方法設(shè)置為非阻塞的模式。

register() 方法的第二個(gè)參數(shù),是一個(gè)“ interest集合 ”,意思是在通過Selector監(jiān)聽Channel時(shí)對(duì)什么事件感興趣??梢员O(jiān)聽四種不同類型的事件:

SelectionKey.OP_ACCEPT
SelectionKey.OP_WRITE
SelectionKey.OP_READ
SelectionKey.OP_CONNECT

通道觸發(fā)了一個(gè)事件意思是該事件已經(jīng)就緒。比如某個(gè)Channel成功連接到另一個(gè)服務(wù)器稱為“連接就緒(connect)”。一個(gè)ServerSocketChannel準(zhǔn)備好接收新進(jìn)入的連接稱為“接收就緒(accept)”。一個(gè)有數(shù)據(jù)可讀的通道可以說是“ 讀就緒(read)”。等待寫數(shù)據(jù)的通道可以說是“ 寫就緒(write) ”。

4.5.3 SelectionKey

一個(gè)SelectionKey鍵表示了一個(gè)特定的通道對(duì)象(Channel)和一個(gè)特定的選擇器對(duì)象(Selector)之間的注冊(cè)關(guān)系。

key.attachment(); //返回SelectionKey的attachment,attachment可以在注冊(cè)channel的時(shí)候指定。
key.channel(); // 返回該SelectionKey對(duì)應(yīng)的channel。
key.selector(); // 返回該SelectionKey對(duì)應(yīng)的Selector。
key.interestOps(); //返回代表需要Selector監(jiān)控的IO操作的bit mask
key.readyOps(); // 返回一個(gè)bit mask,代表在相應(yīng)channel上可以進(jìn)行的IO操作。```

4.5.4 從Selector中選擇Channel

Selector維護(hù)注冊(cè)過的Channel集合,并且這種注冊(cè)關(guān)系都被封裝在SelectionKey當(dāng)中。 Selector維護(hù)的三種類型SelectionKey集合:

  • 已注冊(cè)的鍵的集合(Registered key set)。所有與選擇器關(guān)聯(lián)的通道所生成的鍵的集合稱為已經(jīng)注冊(cè)的鍵的集合。并不是所有注冊(cè)過的鍵都仍然有效。這個(gè)集合通過 keys() 方法返回,并且可能是空的。這個(gè)已注冊(cè)的鍵的集合不是可以直接修改的;試圖這么做的話將引發(fā)java.lang.UnsupportedOperationException。
  • 已選擇的鍵的集合(Selected key set)。所有選擇器監(jiān)聽到關(guān)聯(lián)的通道所生成的鍵的集合稱為已經(jīng)選擇的鍵的集合。這個(gè)集合通過 selectedKeys() 方法返回,并且可能是空的。
  • 已取消的鍵的集合(Cancelled key set)。已注冊(cè)的鍵的集合的子集,這個(gè)集合包含了 cancel() 方法被調(diào)用過的鍵(這個(gè)鍵已經(jīng)被無效化),但它們還沒有被注銷。這個(gè)集合是選擇器對(duì)象的私有成員,因而無法直接訪問。

注意: 當(dāng)鍵被取消( 可以通過isValid( ) 方法來判斷)時(shí),它將被放在相關(guān)的選擇器的已取消的鍵的集合里。注冊(cè)不會(huì)立即被取消,但鍵會(huì)立即失效。當(dāng)再次調(diào)用 select( ) 方法時(shí)(或者一個(gè)正在進(jìn)行的select()調(diào)用結(jié)束時(shí)),已取消的鍵的集合中的被取消的鍵將被清理掉,并且相應(yīng)的注銷也將完成。通道會(huì)被注銷,而新的SelectionKey將被返回。當(dāng)通道關(guān)閉時(shí),所有相關(guān)的鍵會(huì)自動(dòng)取消(記住,一個(gè)通道可以被注冊(cè)到多個(gè)選擇器上)。當(dāng)選擇器關(guān)閉時(shí),所有被注冊(cè)到該選擇器的通道都將被注銷,并且相關(guān)的鍵將立即被無效化(取消)。一旦鍵被無效化,調(diào)用它的與選擇相關(guān)的方法就將拋出CancelledKeyException。

select()方法介紹:

在剛初始化的Selector對(duì)象中,這三個(gè)集合都是空的。 通過Selector的select()方法可以選擇已經(jīng)準(zhǔn)備就緒的通道 (這些通道包含你感興趣的的事件)。比如你對(duì)讀就緒的通道感興趣,那么select()方法就會(huì)返回讀事件已經(jīng)就緒的那些通道。下面是Selector幾個(gè)重載的select()方法:

int select():阻塞到至少有一個(gè)通道在你注冊(cè)的事件上就緒了。 int select(long timeout):和select()一樣,但最長阻塞時(shí)間為timeout毫秒。 int selectNow():非阻塞,只要有通道就緒就立刻返回。 select()方法返回的int值表示有多少通道已經(jīng)就緒,是自上次調(diào)用select()方法后有多少通道變成就緒狀態(tài)。之前在select()調(diào)用時(shí)進(jìn)入就緒的通道不會(huì)在本次調(diào)用中被記入,而在前一次select()調(diào)用進(jìn)入就緒但現(xiàn)在已經(jīng)不在處于就緒的通道也不會(huì)被記入。例如:首次調(diào)用select()方法,如果有一個(gè)通道變成就緒狀態(tài),返回了1,若再次調(diào)用select()方法,如果另一個(gè)通道就緒了,它會(huì)再次返回1。如果對(duì)第一個(gè)就緒的channel沒有做任何操作,現(xiàn)在就有兩個(gè)就緒的通道,但在每次select()方法調(diào)用之間,只有一個(gè)通道就緒了。

一旦調(diào)用select()方法,并且返回值不為0時(shí),則 可以通過調(diào)用Selector的selectedKeys()方法來訪問已選擇鍵集合 。如下: Set selectedKeys=selector.selectedKeys(); 進(jìn)而可以放到和某SelectionKey關(guān)聯(lián)的Selector和Channel。如下所示:

		while (true) {
            if (selector.select(1000) == 0) {
                System.out.println("服務(wù)器未連接到客戶端。。。");
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    System.out.println("服務(wù)端接受到了客戶端請(qǐng)求");
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    channel.read(byteBuffer);
                    System.out.println("from 客戶端 " + new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        }

4.5.5 停止選擇的方法

選擇器執(zhí)行選擇的過程,系統(tǒng)底層會(huì)依次詢問每個(gè)通道是否已經(jīng)就緒,這個(gè)過程可能會(huì)造成調(diào)用線程進(jìn)入阻塞狀態(tài),那么我們有以下三種方式可以喚醒在select()方法中阻塞的線程。

  • wakeup()方法 :通過調(diào)用Selector對(duì)象的wakeup()方法讓處在阻塞狀態(tài)的select()方法立刻返回 該方法使得選擇器上的第一個(gè)還沒有返回的選擇操作立即返回。如果當(dāng)前沒有進(jìn)行中的選擇操作,那么下一次對(duì)select()方法的一次調(diào)用將立即返回。
  • close()方法 :通過close()方法關(guān)閉Selector, 該方法使得任何一個(gè)在選擇操作中阻塞的線程都被喚醒(類似wakeup()),同時(shí)使得注冊(cè)到該Selector的所有Channel被注銷,所有的鍵將被取消,但是Channel本身并不會(huì)關(guān)閉。

4.5.6 NIO客戶端、服務(wù)端

服務(wù)端代碼:

package com.Zhongger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
 * @author zhongmingyi
 * @date 2021/9/15 3:00 下午
 */
public class NIOServer {
    public static void main(String[] args) throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8886));
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            if (selector.select(1000) == 0) {
                System.out.println("服務(wù)器未連接到客戶端。。。");
                continue;
            }
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    System.out.println("服務(wù)端接受到了客戶端請(qǐng)求");
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {
                    SocketChannel channel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    channel.read(byteBuffer);
                    System.out.println("from 客戶端 " + new String(byteBuffer.array()));
                }
                iterator.remove();
            }
        }
    }
}

客戶端代碼:

package com.Zhongger;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
 * @author zhongmingyi
 * @date 2021/9/24 1:27 下午
 */
public class NIOClient {
    public static void main(String[] args) throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8886);
        //連接服務(wù)器
        if (!socketChannel.connect(inetSocketAddress)) {
            while (!socketChannel.finishConnect()) {
                System.out.println("連接需要時(shí)間,客戶端不會(huì)阻塞,可以做其他工作");
            }
        }
        //連接成功,發(fā)送數(shù)據(jù)
        String str = "Hello,Zhongger!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}

5 Java NIO 小結(jié)

  • 事件驅(qū)動(dòng)模型
  • 避免多線程
  • 單線程處理多任務(wù)
  • 非阻塞I/O,I/O讀寫不再阻塞
  • 基于block的傳輸,通常比基于流的傳輸更高效
  • 更高級(jí)的IO函數(shù),zero-copy
  • IO多路復(fù)用大大提高了Java網(wǎng)絡(luò)應(yīng)用的可伸縮性和實(shí)用性

以上就是Netty實(shí)戰(zhàn)源碼解析NIO編程的詳細(xì)內(nèi)容,更多關(guān)于Netty NIO編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java編程Webservice指定超時(shí)時(shí)間代碼詳解

    Java編程Webservice指定超時(shí)時(shí)間代碼詳解

    這篇文章主要介紹了Java編程Webservice指定超時(shí)時(shí)間代碼詳解,簡單介紹了webservice,然后分享了通過使用JDK對(duì)Webservice的支持進(jìn)行Webservice調(diào)用實(shí)現(xiàn)指定超時(shí)時(shí)間完整示例,具有一定借鑒價(jià)值,需要的朋友可以參考下。
    2017-11-11
  • SpringBoot讀取Nacos上配置文件的步驟詳解

    SpringBoot讀取Nacos上配置文件的步驟詳解

    在 Spring Boot 應(yīng)用程序中,可以使用 Spring Cloud Nacos 來實(shí)現(xiàn)從 Nacos 服務(wù)注冊(cè)中心和配置中心讀取配置信息,本文介紹如何在 Spring Boot 中讀取 Nacos 上的配置文件的步驟,需要的朋友可以參考下
    2024-03-03
  • Java中的Unsafe工具類使用詳解

    Java中的Unsafe工具類使用詳解

    這篇文章主要介紹了Java中的Unsafe工具類使用詳解,Unsafe是jdk提供的一個(gè)直接訪問操作系統(tǒng)資源的工具類(底層c++實(shí)現(xiàn)),它可以直接分配內(nèi)存,內(nèi)存復(fù)制,copy,提供cpu級(jí)別的CAS樂觀鎖等操作,需要的朋友可以參考下
    2023-12-12
  • Idea設(shè)置spring boot應(yīng)用配置參數(shù)的兩種方式

    Idea設(shè)置spring boot應(yīng)用配置參數(shù)的兩種方式

    本文通過兩個(gè)方式介紹Idea設(shè)置spring boot應(yīng)用配置參數(shù),一種是配置VM options的參數(shù)時(shí)要以:-DparamName的格式設(shè)置參數(shù),第二種可以參考下本文詳細(xì)設(shè)置,感興趣的朋友跟隨小編一起看看吧
    2023-11-11
  • Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測試

    Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測試

    這篇文章主要介紹了Java中對(duì)AtomicInteger和int值在多線程下遞增操作的測試,本文得出AtomicInteger操作 與 int操作的效率大致相差在50-80倍上下的結(jié)論,需要的朋友可以參考下
    2014-09-09
  • Java多線程狀態(tài)及方法實(shí)例解析

    Java多線程狀態(tài)及方法實(shí)例解析

    這篇文章主要介紹了Java多線程狀態(tài)及方法實(shí)例解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-02-02
  • java使用apache commons連接ftp修改ftp文件名失敗原因

    java使用apache commons連接ftp修改ftp文件名失敗原因

    這篇文章主要介紹了java使用apache commons連接ftp修改ftp文件名失敗原因解析,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Java 8 Lambda 表達(dá)式比較器使用示例代碼

    Java 8 Lambda 表達(dá)式比較器使用示例代碼

    這篇文章主要介紹了Java 8 Lambda 表達(dá)式比較器使用示例代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Java ArrayList的底層實(shí)現(xiàn)方法

    Java ArrayList的底層實(shí)現(xiàn)方法

    今天小編就為大家分享一篇Java ArrayList的底層實(shí)現(xiàn)方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-10-10
  • 基于Java事件監(jiān)聽編寫一個(gè)中秋猜燈謎小游戲

    基于Java事件監(jiān)聽編寫一個(gè)中秋猜燈謎小游戲

    眾所周知,JavaSwing是Java中關(guān)于窗口開發(fā)的一個(gè)工具包,可以開發(fā)一些窗口程序,然后由于工具包的一些限制,導(dǎo)致Java在窗口開發(fā)商并沒有太多優(yōu)勢,不過,在JavaSwing中關(guān)于事件的監(jiān)聽機(jī)制是我們需要重點(diǎn)掌握的內(nèi)容,本文將基于Java事件監(jiān)聽編寫一個(gè)中秋猜燈謎小游戲
    2023-09-09

最新評(píng)論

阿图什市| 阳泉市| 莱阳市| 平潭县| 浦县| 新乡市| 台中县| 晋江市| 眉山市| 富源县| 贡嘎县| 山丹县| 峨边| 华坪县| 乐陵市| 金阳县| 柘城县| 越西县| 蚌埠市| 龙岩市| 乌海市| 广宗县| 博湖县| 沙湾县| 乌鲁木齐县| 临沧市| 嫩江县| 名山县| 广宗县| 茶陵县| 衡山县| 新竹县| 临海市| 临高县| 奉新县| 潞西市| 神池县| 连城县| 电白县| 普兰县| 客服|