netty(一)nio基礎(chǔ)

NIO芥颈,Non-Block IO 放案,從 Java 1.4 版本開始引入的非阻塞 IO ,是一種非阻塞 + 同步的通信模式止状,也是 I/O 多路復用的基礎(chǔ)烹棉,基于緩沖區(qū)(Buffer)的模式。

一怯疤、NIO主要有三大核心

NIO三大核心

通過下圖簡單展示三大核心之間的關(guān)系:

三個組件的關(guān)系

1.1 Channel簡介

讀寫數(shù)據(jù)的雙向通道浆洗,可以從 channel 將數(shù)據(jù)讀入 buffer,也可以將 buffer 的數(shù)據(jù)寫入 channel集峦。

jdk中定義了一個java.nio.channels.channel接口伏社,主要提供了開啟連接關(guān)閉連接的方法。

package java.nio.channels;

import java.io.IOException;
import java.io.Closeable;


/**
 * A nexus for I/O operations.
 *
 * <p> A channel represents an open connection to an entity such as a hardware
 * device, a file, a network socket, or a program component that is capable of
 * performing one or more distinct I/O operations, for example reading or
 * writing.
 *
 * <p> A channel is either open or closed.  A channel is open upon creation,
 * and once closed it remains closed.  Once a channel is closed, any attempt to
 * invoke an I/O operation upon it will cause a {@link ClosedChannelException}
 * to be thrown.  Whether or not a channel is open may be tested by invoking
 * its {@link #isOpen isOpen} method.
 *
 * <p> Channels are, in general, intended to be safe for multithreaded access
 * as described in the specifications of the interfaces and classes that extend
 * and implement this interface.
 *
 *
 * @author Mark Reinhold
 * @author JSR-51 Expert Group
 * @since 1.4
 */

public interface Channel extends Closeable {

    /**
     * Tells whether or not this channel is open.
     *
     * @return <tt>true</tt> if, and only if, this channel is open
     */
    public boolean isOpen();

    /**
     * Closes this channel.
     *
     * <p> After a channel is closed, any further attempt to invoke I/O
     * operations upon it will cause a {@link ClosedChannelException} to be
     * thrown.
     *
     * <p> If this channel is already closed then invoking this method has no
     * effect.
     *
     * <p> This method may be invoked at any time.  If some other thread has
     * already invoked it, however, then another invocation will block until
     * the first invocation is complete, after which it will return without
     * effect. </p>
     *
     * @throws  IOException  If an I/O error occurs
     */
    public void close() throws IOException;

在jdk中對于channel接口提供了很多實現(xiàn)類塔淤,最主要的是以下四個:

1)SocketChannel :一個客戶端用來發(fā)起 TCP 的 Channel 摘昌。
2)ServerSocketChannel :一個服務(wù)端用來監(jiān)聽新進來的連接的 TCP 的 Channel 。對于每一個新進來的連接高蜂,都會創(chuàng)建一個對應(yīng)的 SocketChannel 聪黎。
3)DatagramChannel :通過 UDP 讀寫數(shù)據(jù)。
4)FileChannel :從文件中妨马,讀寫數(shù)據(jù)挺举。

1.2 Buffer簡介

buffer 則用來緩沖讀寫數(shù)據(jù)杀赢,jdk提供java.nio.buffer抽象類烘跺,其屬性和構(gòu)造如下代碼所示:

package java.nio;

import java.util.Spliterator;

public abstract class Buffer {

    /**
     * The characteristics of Spliterators that traverse and split elements
     * maintained in Buffers.
     */
    static final int SPLITERATOR_CHARACTERISTICS =
        Spliterator.SIZED | Spliterator.SUBSIZED | Spliterator.ORDERED;

    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;

    // Used only by direct buffers
    // NOTE: hoisted here for speed in JNI GetDirectBufferAddress
    long address;

    // Creates a new buffer with the given mark, position, limit, and capacity,
    // after checking invariants.
    //
    Buffer(int mark, int pos, int lim, int cap) {       // package-private
        if (cap < 0)
            throw new IllegalArgumentException("Negative capacity: " + cap);
        this.capacity = cap;
        limit(lim);
        position(pos);
        if (mark >= 0) {
            if (mark > pos)
                throw new IllegalArgumentException("mark > position: ("
                                                   + mark + " > " + pos + ")");
            this.mark = mark;
        }
    }
}

如上代碼所示,Buffer有四個屬性脂崔,他們在Buffer讀和寫的情況下具有不同的含義

mark:記錄當前讀或?qū)懙奈恢?br> position:下一個位置
limit:范圍
capacity:Buffer的容量滤淳,創(chuàng)建時候指定,不能修改砌左。

主要模式切換如下所示:


buffer的狀態(tài)切換.png

主要方法:
每個buffer的實現(xiàn)類都實現(xiàn)了以下主要方法脖咐,下面以ByteBuffer的源碼舉例:
1)創(chuàng)建 Buffer:

/**
 * 創(chuàng)建并指定大小
 **/
public static ByteBuffer allocate(int capacity) {
        if (capacity < 0)
            throw new IllegalArgumentException();
        return new HeapByteBuffer(capacity, capacity);
    }

/**
 * 數(shù)組轉(zhuǎn)成Buffer對象
 **/
public static ByteBuffer wrap(byte[] array, int offset, int length)
    {
        try {
            return new HeapByteBuffer(array, offset, length);
        } catch (IllegalArgumentException x) {
            throw new IndexOutOfBoundsException();
        }
    }

2)寫入:

// 寫入 byte
public abstract ByteBuffer put(byte b); 
public abstract ByteBuffer put(int index, byte b);
// 寫入 byte 數(shù)組
public final ByteBuffer put(byte[] src) { ... }
public ByteBuffer put(byte[] src, int offset, int length) {...}

從channel將數(shù)據(jù)寫入buffer,該方法返回數(shù)據(jù)大谢愦酢:

int num = channel.read(buffer);

3)讀绕ㄉ谩:

// 讀取 byte
public abstract byte get();
public abstract byte get(int index);
// 讀取 byte 數(shù)組
public ByteBuffer get(byte[] dst, int offset, int length) {...}
public ByteBuffer get(byte[] dst) {...}

從buffer中的數(shù)據(jù)寫入到channel中,該方法返回數(shù)據(jù)大胁:

int num = channel.write(buffer);

4)讀寫模式切換
ByteBuffer是繼承buffer的派歌,其讀寫切換要基于Buffer的方法:

切換成讀模式:

    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

切換成寫模式,clear()方法:
這個方法會將buffer的起始位置設(shè)為0痰哨,其實表面是將buffer清空了胶果,實際是不在記錄buffer的讀寫位置,此時寫入數(shù)據(jù)斤斧,原數(shù)據(jù)將會被覆蓋早抠。

    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }

如果想咋之前的數(shù)據(jù)后面繼續(xù)寫可以使用compact()方法:

public abstract ByteBuffer compact();

它有兩種實現(xiàn),我們應(yīng)該知道了解撬讽,分別是基于堆內(nèi)存和基于直接內(nèi)存的:

conpact

以下兩種buffer需要記兹锪:

/**
 * @description: byteBuffer讀寫
 * @author:weirx
 * @date:2021/5/18 10:22
 * @version:3.0
 */
public class ByteBufferReadWrite {

    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.allocate(16);
        ByteBuffer buffer2 = ByteBuffer.allocateDirect(16);
        //java.nio.HeapByteBuffer[pos=0 lim=16 cap=16]
        //java 堆內(nèi)存悬垃,讀寫效率低,受gc影響
        System.out.println(buffer1);
        //java.nio.DirectByteBuffer[pos=0 lim=16 cap=16]
        //直接內(nèi)存 讀寫效率高(少一次拷貝)咪奖,不收gc影響盗忱,分配的效率低,注意關(guān)閉羊赵,否則會造成內(nèi)存泄漏
        System.out.println(buffer2);
    }
}

執(zhí)行結(jié)果:

java.nio.HeapByteBuffer[pos=0 lim=0 cap=16]
java.nio.DirectByteBuffer[pos=0 lim=16 cap=16]

常見buffer有:

  • ByteBuffer
    • MappedByteBuffer
    • DirectByteBuffer
    • HeapByteBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer
  • CharBuffer

1.3 Selector簡介

Selector被稱為多路復用器趟佃,用于輪詢NIO的channel是否處于可讀或者可寫的狀態(tài),其位于jdk的java.nio.channels.Selector昧捷。

selector 的作用就是配合一個線程來管理多個 channel闲昭,獲取這些 channel 上發(fā)生的事件,這些 channel 工作在非阻塞模式下靡挥,不會讓線程吊死在一個 channel 上序矩。適合連接數(shù)特別多,但流量低的場景跋破。

調(diào)用 selector 的 select() 會阻塞直到 channel 發(fā)生了讀寫就緒事件簸淀,這些事件發(fā)生,select 方法就會返回這些事件交給 thread 來處理毒返。

輪詢

輪詢步驟:
1)每個channel需要注冊到selector上租幕。
2)selector輪詢每個channel,當有channel發(fā)生讀寫操作拧簸,這個channel處于就緒狀態(tài)劲绪,會被輪詢到,等到就緒狀態(tài)的channel集合盆赤,進行后續(xù)的IO操作贾富。

代碼舉例:

//創(chuàng)建selector
Selector selector = Selector.open();
//注冊channel
channel.configureBlocking(false); // 必須是非阻塞
/*
第二個參數(shù)有以下四種類型:
Connect :連接完成事件( TCP 連接 ),僅適用于客戶端牺六,對應(yīng) SelectionKey.OP_CONNECT 
Accept:接受新連接事件颤枪,僅適用于服務(wù)端,對應(yīng) SelectionKey.OP_ACCEPT 淑际。
Read :讀事件畏纲,適用于兩端,對應(yīng)SelectionKey.OP_READ 庸追,表示 Buffer 可讀霍骄。
Write :寫時間,適用于兩端淡溯,對應(yīng)SelectionKey.OP_WRITE 读整,表示 Buffer 可寫。
*/
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末咱娶,一起剝皮案震驚了整個濱河市米间,隨后出現(xiàn)的幾起案子强品,更是在濱河造成了極大的恐慌,老刑警劉巖屈糊,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件的榛,死亡現(xiàn)場離奇詭異,居然都是意外死亡逻锐,警方通過查閱死者的電腦和手機夫晌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來昧诱,“玉大人晓淀,你說我怎么就攤上這事≌档担” “怎么了凶掰?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜈亩。 經(jīng)常有香客問我懦窘,道長,這世上最難降的妖魔是什么稚配? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任畅涂,我火速辦了婚禮,結(jié)果婚禮上药有,老公的妹妹穿的比我還像新娘毅戈。我一直安慰自己苹丸,他們只是感情好愤惰,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著赘理,像睡著了一般宦言。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上商模,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天奠旺,我揣著相機與錄音,去河邊找鬼施流。 笑死响疚,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的瞪醋。 我是一名探鬼主播忿晕,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼银受!你這毒婦竟也來了践盼?” 一聲冷哼從身側(cè)響起鸦采,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咕幻,沒想到半個月后渔伯,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡肄程,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年锣吼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蓝厌。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡吐限,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出褂始,到底是詐尸還是另有隱情诸典,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布崎苗,位于F島的核電站狐粱,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胆数。R本人自食惡果不足惜肌蜻,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望必尼。 院中可真熱鬧蒋搜,春花似錦、人聲如沸判莉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽券盅。三九已至帮哈,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間锰镀,已是汗流浹背娘侍。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留泳炉,地道東北人憾筏。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像花鹅,于是被迫代替她去往敵國和親氧腰。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內(nèi)容