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

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

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

1 前言

很久之前就想寫與Netty相關(guān)的博客了,但由于個人時間安排的問題一直拖到了現(xiàn)在,借助這個機(jī)會,重新溫習(xí)Java高級編程的同時,也把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是一個基于NIO的客戶端-服務(wù)端框架,能過快速而簡單地開發(fā)像客戶端-服務(wù)端協(xié)議的網(wǎng)絡(luò)應(yīng)用。它極大地精簡了 TCP 和 UDP 套接字服務(wù)器等網(wǎng)絡(luò)編程。“快速而簡單”并不意味著生成的應(yīng)用程序會受到可維護(hù)性或性能問題的影響。Netty 是根據(jù)從許多協(xié)議(如 FTP、SMTP、HTTP 以及各種二進(jìn)制和基于文本的遺留協(xié)議)的實(shí)現(xiàn)中獲得的經(jīng)驗精心設(shè)計的。結(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)模式為一個連接一個線程,即客戶端有一個請求連接服務(wù)器時,服務(wù)器就會啟動一個線程進(jìn)行處理,可見當(dāng)有多個客戶端發(fā)出請求時,服務(wù)器需要啟動等量的線程,而且當(dāng)客戶端沒有響應(yīng)時,線程也必須一直等待,長期下來需要大量的線程且線程利用率低,會造成浪費(fèi)。
  • NIO,同步非阻塞,服務(wù)器用一個線程來處理多個請求,客戶端發(fā)送的請求會注冊到多路復(fù)用器(selector選擇器)上,有I/O請求的客戶端分配線程處理。
  • AIO,異步非阻塞,AIO引入了異步通道的概念,采用Proactor模式,簡化程序編寫,有效的請求才啟動線程,特點(diǎn)是要先由操作系統(tǒng)完成后才通知服務(wù)的程序啟動線程去處理,一般適用于連接數(shù)較多且連接時間較長的應(yīng)用??蛻舳税l(fā)送的請求先交給操作系統(tǒng)處理,OS處理后再通知線程

Netty其實(shí)就是基于Java的NIO的。接下來,我們通過編寫代碼來體驗一下這三種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ù)端已啟動");
        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ù)器開啟了一個ServerSocket,綁定在8989端口上,循環(huán)等待接受客戶端的連接
  • 當(dāng)客戶端連接到了服務(wù)器后,ServerSocket.accept方法可以獲取到客戶端的Socket
  • 每當(dāng)有一個客戶端連接了服務(wù)器,線程池就會啟動一個線程去處理Socket中的IO數(shù)據(jù)流,通過InputStream的read方法讀取客戶端發(fā)給服務(wù)器的數(shù)據(jù),并輸出打??;當(dāng)InputStream沒有數(shù)據(jù)了,最后將Socket關(guān)閉,該線程會回收到線程池中

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

4 Java NIO

4.1 基本介紹

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

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

NIO有三個核心組件:

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

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

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

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

如圖所示:

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

4.3 Buffer緩沖區(qū)

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

Buffer是一個頂層的抽象類,它的子類有多種實(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ū)對象。前面說到,Buffer是和數(shù)據(jù)打交道的載體,也就是讀取緩沖區(qū)的數(shù)據(jù)或者把寫數(shù)據(jù)到緩沖區(qū)中。所以,Buffer緩沖區(qū)的核心方法就是 put()方法和get()方法以及對應(yīng)的重載方法、擴(kuò)展方法等。

Buffer類中有以下四個屬性:

// 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è)定,中途無法被修改。Capacity也就規(guī)定了Buffer中底層的數(shù)組的大小
  • Limit:Buffer緩沖區(qū)里的當(dāng)前的終點(diǎn),不能對緩沖區(qū)超過Limit的位置進(jìn)行讀寫操作,Limit是可以被修改的
  • Position:Buffer緩沖區(qū)中下一個要被讀或?qū)懙脑氐乃饕恢?,Position會自動由相應(yīng)的 get( )和 put( )函數(shù)更新,為下一次讀/寫做準(zhǔn)備
  • 標(biāo)記Mark一個備忘位置。用于記錄上一次讀寫的位置。

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

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

這里介紹一個比較高效的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是一個抽象類,常用的實(shí)現(xiàn)有:ServerSocketChannel、SocketChannel、FileChannel、DatagramChannel,其中DatagramChannel是用于UDP數(shù)據(jù),而其他三者用于TCP數(shù)據(jù)。

FileChannel類主要用于對本地文件進(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

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

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)建一個文件輸出流
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/bytedance/Desktop/file.txt");
        //通過文件輸出流獲取到FileChannel
        FileChannel fileChannel = fileOutputStream.getChannel();
        //創(chuàng)建一個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)建一個文件輸入流
        FileInputStream fileInputStream = new FileInputStream("/Users/bytedance/Desktop/file.txt");
        //通過文件輸入流獲取到FileChannel
        FileChannel fileChannel = fileInputStream.getChannel();
        //創(chuàng)建一個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();
    }

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

 	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();
    }

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

 	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)存,通常情況可以映射整個文件,如果文件比較大,可以進(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ù)用器,一個線程對應(yīng)一個Selector,而Selector中可以注冊多個Channel,當(dāng)Channel中有事件發(fā)生時,線程就可以去處理這個事件,若沒有事件發(fā)生時,線程可以空出來去做其他事情。使用Selector的好處在于: 使用更少的線程來就可以來處理通道了, 相比使用多個線程,避免了線程上下文切換帶來的開銷。

4.5.1 Selector的創(chuàng)建

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

Selector selector = Selector.open();

4.5.2 注冊Channel到Selector

Channel必須是非阻塞的,否則會拋出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() 方法的第二個參數(shù),是一個“ interest集合 ”,意思是在通過Selector監(jiān)聽Channel時對什么事件感興趣??梢员O(jiān)聽四種不同類型的事件:

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

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

4.5.3 SelectionKey

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

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

4.5.4 從Selector中選擇Channel

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

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

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

select()方法介紹:

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

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

一旦調(diào)用select()方法,并且返回值不為0時,則 可以通過調(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ù)端接受到了客戶端請求");
                    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)底層會依次詢問每個通道是否已經(jīng)就緒,這個過程可能會造成調(diào)用線程進(jìn)入阻塞狀態(tài),那么我們有以下三種方式可以喚醒在select()方法中阻塞的線程。

  • wakeup()方法 :通過調(diào)用Selector對象的wakeup()方法讓處在阻塞狀態(tài)的select()方法立刻返回 該方法使得選擇器上的第一個還沒有返回的選擇操作立即返回。如果當(dāng)前沒有進(jìn)行中的選擇操作,那么下一次對select()方法的一次調(diào)用將立即返回。
  • close()方法 :通過close()方法關(guān)閉Selector, 該方法使得任何一個在選擇操作中阻塞的線程都被喚醒(類似wakeup()),同時使得注冊到該Selector的所有Channel被注銷,所有的鍵將被取消,但是Channel本身并不會關(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ù)端接受到了客戶端請求");
                    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("連接需要時間,客戶端不會阻塞,可以做其他工作");
            }
        }
        //連接成功,發(fā)送數(shù)據(jù)
        String str = "Hello,Zhongger!";
        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
        socketChannel.write(byteBuffer);
        System.in.read();
    }
}

5 Java NIO 小結(jié)

  • 事件驅(qū)動模型
  • 避免多線程
  • 單線程處理多任務(wù)
  • 非阻塞I/O,I/O讀寫不再阻塞
  • 基于block的傳輸,通常比基于流的傳輸更高效
  • 更高級的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編程的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring中常用注解的用法

    Spring中常用注解的用法

    這篇文章主要介紹了Spring中常用注解的用法,Spring注解方式減少了配置文件內(nèi)容,更加便于管理,并且使用注解可以大大提高了開發(fā)效率,注解本身是沒有功能的,和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è)計模式之適配器模式

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

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

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

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

    java對指定目錄下文件讀寫操作介紹

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

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

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

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

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

    PowerJobAutoConfiguration自動配置源碼流程解析

    這篇文章主要為大家介紹了PowerJobAutoConfiguration自動配置源碼流程解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(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的新特性,極大簡化了集合的處理操作,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • idea 使用Maven Helper idea的解決方法

    idea 使用Maven Helper idea的解決方法

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

最新評論