欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

1 前言

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

3 Java I/O模型簡(jiǎn)介

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

Netty其實(shí)就是基于Java的NIO的。接下來(lái),我們通過(guò)編寫(xiě)代碼來(lái)體驗(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ù)器開(kāi)啟了一個(gè)ServerSocket,綁定在8989端口上,循環(huán)等待接受客戶端的連接
  • 當(dāng)客戶端連接到了服務(wù)器后,ServerSocket.accept方法可以獲取到客戶端的Socket
  • 每當(dāng)有一個(gè)客戶端連接了服務(wù)器,線程池就會(huì)啟動(dòng)一個(gè)線程去處理Socket中的IO數(shù)據(jù)流,通過(guò)InputStream的read方法讀取客戶端發(fā)給服務(wù)器的數(shù)據(jù),并輸出打??;當(dāng)InputStream沒(méi)有數(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í)中,肯定有接觸過(guò)Socket編程,實(shí)現(xiàn)一個(gè)簡(jiǎn)易版的聊天工具。

4 Java NIO

4.1 基本介紹

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

  • New I/O:原因在于它相對(duì)于之前的I/O類庫(kù)是新增的。
  • Non-block I/O:由于之前老的I/O類庫(kù)是阻塞I/O,New I/O類庫(kù)的目標(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)于車票,用來(lái)選擇火車應(yīng)該通過(guò)哪個(gè)Channel去運(yùn)輸)

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

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

三大核心組件的關(guān)系簡(jiǎ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ù)組,可以寫(xiě)入數(shù)據(jù),可以通過(guò)flip方法來(lái)切換成讀取數(shù)據(jù),Buffer也是雙向的

4.3 Buffer緩沖區(qū)

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

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

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

上述緩沖區(qū)的管理方式基本上一致,都可以用類的allocate(int capacity) 方法去獲取緩沖區(qū)對(duì)象。前面說(shuō)到,Buffer是和數(shù)據(jù)打交道的載體,也就是讀取緩沖區(qū)的數(shù)據(jù)或者把寫(xiě)數(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è)定,中途無(wú)法被修改。Capacity也就規(guī)定了Buffer中底層的數(shù)組的大小
  • Limit:Buffer緩沖區(qū)里的當(dāng)前的終點(diǎn),不能對(duì)緩沖區(qū)超過(guò)Limit的位置進(jìn)行讀寫(xiě)操作,Limit是可以被修改的
  • Position:Buffer緩沖區(qū)中下一個(gè)要被讀或?qū)懙脑氐乃饕恢?,Position會(huì)自動(dòng)由相應(yīng)的 get( )和 put( )函數(shù)更新,為下一次讀/寫(xiě)做準(zhǔn)備
  • 標(biāo)記Mark一個(gè)備忘位置。用于記錄上一次讀寫(xiě)的位置。

簡(jiǎn)單看下ByteBuffer的使用,體會(huì)下往其中寫(xiě)入數(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ù)寫(xiě)入到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

下面簡(jiǎn)單看下這個(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");
        //通過(guò)文件輸出流獲取到FileChannel
        FileChannel fileChannel = fileOutputStream.getChannel();
        //創(chuàng)建一個(gè)ByteBuffer,將數(shù)據(jù)寫(xiě)入ByteBuffer中
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        buffer.put(text.getBytes());
        //切換成讀模式
        buffer.flip();
        //把ByteBuffer里到數(shù)據(jù)寫(xiě)入到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");
        //通過(guò)文件輸入流獲取到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寫(xiě)入到另外一個(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();
    }

再簡(jiǎn)單介紹下:FileChannel提供了map方法把文件映射到虛擬內(nèi)存,通常情況可以映射整個(gè)文件,如果文件比較大,可以進(jìn)行分段映射。簡(jiǎn)單地說(shuō)就是通過(guò)映射的方式來(lái)減少一次內(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è)事件,若沒(méi)有事件發(fā)生時(shí),線程可以空出來(lái)去做其他事情。使用Selector的好處在于: 使用更少的線程來(lái)就可以來(lái)處理通道了, 相比使用多個(gè)線程,避免了線程上下文切換帶來(lái)的開(kāi)銷。

4.5.1 Selector的創(chuàng)建

通過(guò)調(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集合 ”,意思是在通過(guò)Selector監(jiān)聽(tīng)Channel時(shí)對(duì)什么事件感興趣??梢员O(jiān)聽(tīng)四種不同類型的事件:

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ù)可讀的通道可以說(shuō)是“ 讀就緒(read)”。等待寫(xiě)數(shù)據(jù)的通道可以說(shuō)是“ 寫(xiě)就緒(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è)過(guò)的Channel集合,并且這種注冊(cè)關(guān)系都被封裝在SelectionKey當(dāng)中。 Selector維護(hù)的三種類型SelectionKey集合:

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

注意: 當(dāng)鍵被取消( 可以通過(guò)isValid( ) 方法來(lái)判斷)時(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)的鍵將立即被無(wú)效化(取消)。一旦鍵被無(wú)效化,調(diào)用它的與選擇相關(guān)的方法就將拋出CancelledKeyException。

select()方法介紹:

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

int select():阻塞到至少有一個(gè)通道在你注冊(cè)的事件上就緒了。 int select(long timeout):和select()一樣,但最長(zhǎng)阻塞時(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沒(méi)有做任何操作,現(xiàn)在就有兩個(gè)就緒的通道,但在每次select()方法調(diào)用之間,只有一個(gè)通道就緒了。

一旦調(diào)用select()方法,并且返回值不為0時(shí),則 可以通過(guò)調(diào)用Selector的selectedKeys()方法來(lái)訪問(wèn)已選擇鍵集合 。如下: 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í)行選擇的過(guò)程,系統(tǒng)底層會(huì)依次詢問(wèn)每個(gè)通道是否已經(jīng)就緒,這個(gè)過(guò)程可能會(huì)造成調(diào)用線程進(jìn)入阻塞狀態(tài),那么我們有以下三種方式可以喚醒在select()方法中阻塞的線程。

  • wakeup()方法 :通過(guò)調(diào)用Selector對(duì)象的wakeup()方法讓處在阻塞狀態(tài)的select()方法立刻返回 該方法使得選擇器上的第一個(gè)還沒(méi)有返回的選擇操作立即返回。如果當(dāng)前沒(méi)有進(jìn)行中的選擇操作,那么下一次對(duì)select()方法的一次調(diào)用將立即返回。
  • close()方法 :通過(guò)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讀寫(xiě)不再阻塞
  • 基于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)文章

  • Spring中常用注解的用法

    Spring中常用注解的用法

    這篇文章主要介紹了Spring中常用注解的用法,Spring注解方式減少了配置文件內(nèi)容,更加便于管理,并且使用注解可以大大提高了開(kāi)發(fā)效率,注解本身是沒(méi)有功能的,和xml一樣,注解和xml都是一種元數(shù)據(jù),元數(shù)據(jù)即解釋數(shù)據(jù)的數(shù)據(jù),也就是所謂的配置,需要的朋友可以參考下
    2023-08-08
  • MySql多表查詢 事務(wù)及DCL

    MySql多表查詢 事務(wù)及DCL

    這篇文章主要介紹了MySql多表查詢 、事務(wù)、DCL的相關(guān)資料,需要的朋友可以參考下面文章內(nèi)容
    2021-09-09
  • Java設(shè)計(jì)模式之適配器模式

    Java設(shè)計(jì)模式之適配器模式

    這篇文章介紹了Java設(shè)計(jì)模式之適配器模式,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-09-09
  • Springboot中項(xiàng)目的屬性配置的詳細(xì)介紹

    Springboot中項(xiàng)目的屬性配置的詳細(xì)介紹

    很多時(shí)候需要用到一些配置的信息,這些信息可能在測(cè)試環(huán)境和生產(chǎn)環(huán)境下會(huì)有不同的配置,本文主要介紹了Springboot中項(xiàng)目的屬性配置的詳細(xì)介紹,感興趣的可以了解一下
    2022-01-01
  • java對(duì)指定目錄下文件讀寫(xiě)操作介紹

    java對(duì)指定目錄下文件讀寫(xiě)操作介紹

    本文將詳細(xì)介紹java對(duì)指定目錄下文件的讀寫(xiě)功能實(shí)現(xiàn),需要的朋友可以參考下
    2012-11-11
  • 微信開(kāi)發(fā)準(zhǔn)備第二步 springmvc mybatis項(xiàng)目結(jié)構(gòu)搭建

    微信開(kāi)發(fā)準(zhǔn)備第二步 springmvc mybatis項(xiàng)目結(jié)構(gòu)搭建

    這篇文章主要為大家詳細(xì)介紹了微信開(kāi)發(fā)準(zhǔn)備第二步,springmvc和mybatis項(xiàng)目結(jié)構(gòu)的搭建,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • Java實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)功能界面

    Java實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)功能界面

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)簡(jiǎn)單抽獎(jiǎng)功能界面,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-11-11
  • PowerJobAutoConfiguration自動(dòng)配置源碼流程解析

    PowerJobAutoConfiguration自動(dòng)配置源碼流程解析

    這篇文章主要為大家介紹了PowerJobAutoConfiguration自動(dòng)配置源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Java?Stream如何將List分組成Map或LinkedHashMap

    Java?Stream如何將List分組成Map或LinkedHashMap

    這篇文章主要給大家介紹了關(guān)于Java?Stream如何將List分組成Map或LinkedHashMap的相關(guān)資料,stream流是Java8的新特性,極大簡(jiǎn)化了集合的處理操作,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • idea 使用Maven Helper idea的解決方法

    idea 使用Maven Helper idea的解決方法

    這篇文章主要介紹了idea 使用Maven Helper idea的解決方法,本文給大家介紹的非常詳細(xì)對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07

最新評(píng)論