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

基于Java?NIO編寫一個簡單版Netty服務端

 更新時間:2024年04月03日 08:22:09   作者:gg12138  
基于?NIO?實現(xiàn)的網(wǎng)絡框架,可以用少量的線程,處理大量的連接,更適用于高并發(fā)場景,所以被就將利用NIO編寫一個簡單版Netty服務端,需要的可以參考下

前置知識

NIO

  • NIO 一般指 同步非阻塞 IO,同樣用于**描述程序訪問數(shù)據(jù)方式 **的還有BIO(同步阻塞)、AIO(異步非阻塞)
  • 同步異步指獲取結果的方式,同步為主動去獲取結果,不管結果是否準備好,異步為等待結果準備好的通知
  • 阻塞非阻塞是線程在結果沒有到來之前,是否進行等待,阻塞為進行等待,非阻塞則不進行等待
  • NIO 主動地去獲取結果,但是在結果沒有準備好之前,不會進行等待。而是通過一個 多路復用器 管理多個通道,由一個線程輪訓地去檢查是否準備好即可。在網(wǎng)絡編程中,多路復用器通常由操作系統(tǒng)提供,Linux中主要有 select、poll、epoll。同步非阻塞指線程不等待數(shù)據(jù)的傳輸,而是完成后由多路復用器通知,線程再將數(shù)據(jù)從內(nèi)核緩沖區(qū)拷貝到用戶空間內(nèi)存進行處理。

Java NIO

基于 NIO 實現(xiàn)的網(wǎng)絡框架,可以用少量的線程,處理大量的連接,更適用于高并發(fā)場景。于是,Java提供了NIO包提供相關組件,用于實現(xiàn)同步非阻塞IO

核心三個類Channel、Buffer、Selector。Channel代表一個數(shù)據(jù)傳輸通道,但不進行數(shù)據(jù)存取,有Buffer類進行數(shù)據(jù)管理,Selector為一個復用器,管理多個通道

Bytebuffer

  • 該類為NIO 包中用于操作內(nèi)存的抽象類,具體實現(xiàn)由HeapByteBuffer、DirectByteBuffer兩種
  • HeapByteBuffer為堆內(nèi)內(nèi)存,底層通過 byte[ ] 存取數(shù)據(jù)
  • DirectByteBuffer 為堆外內(nèi)存,通過JDK提供的 Unsafe類去存取;同時創(chuàng)建對象會關聯(lián)的一個Cleaner對象,當對象被GC時,通過cleaner對象去釋放堆外內(nèi)存

各核心組件介紹

NioServer

為啟動程序類,監(jiān)聽端口,初始化Channel

下面為NIO模式下簡單服務端處理代碼

// 1、創(chuàng)建服務端Channel,綁定端口并配置非阻塞
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
serverSocketChannel.configureBlocking(false);

// 2、創(chuàng)建多路復用器selector,并將channel注冊到多路復用器上
// 不能直接調(diào)用channel的accept方法,因為屬于非阻塞,直接調(diào)用沒有新連接會直接返回
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

// 3、循環(huán)處理多路復用器的IO事件
while(true){

    // 3.1、select屬于阻塞的方法,這里阻塞等待1秒
    // 如果返回0,說明沒有事件處理
    if (selector.select(1000) == 0){
        System.out.println("服務器等待了1秒,無IO事件");
        continue;
    }
    // 3.2、遍歷事件進行處理
    Set<SelectionKey> selectionKeys = selector.selectedKeys();
    Iterator<SelectionKey> iterator = selectionKeys.iterator();
    while(iterator.hasNext()){
        SelectionKey key = iterator.next();
        // accept事件,說明有新的客戶端連接
        if (key.isAcceptable()){
            // 新建一個socketChannel,注冊到selector,并關聯(lián)buffer
            SocketChannel socketChannel = serverSocketChannel.accept();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
            System.out.println("客戶端連接:"+socketChannel.getRemoteAddress());
        }
        // read事件 (內(nèi)核緩沖區(qū)的數(shù)據(jù)準備好了)
        if(key.isReadable()){
            SocketChannel channel = (SocketChannel)key.channel();
            ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
            try {
              // 將數(shù)據(jù)寫進buffer
                int readNum = channel.read(byteBuffer);
                if (readNum == -1){
                    System.out.println("讀取-1時,表示IO流已結束");
                    channel.close();
                    break;
                }
                // 打印buffer
                byteBuffer.flip();
                byte[] bytes = new byte[readNum];
                byteBuffer.get(bytes, 0, readNum);
                System.out.println("讀取到數(shù)據(jù):" + new String(bytes));
            } catch (IOException e) {
                System.out.println("讀取發(fā)生異常,廣播socket");
                channel.close();
            }

        }
        // write事件 (操作系統(tǒng)有內(nèi)存寫出了)
        if (key.isWritable()){
            SocketChannel channel = (SocketChannel)key.channel();
            // 讀取read時暫存數(shù)據(jù)
            byte[] bytes = (byte[])key.attachment();
            if (bytes != null){
                System.out.println("可寫事件發(fā)生,寫入數(shù)據(jù): " + new String(bytes));
                channel.write(ByteBuffer.wrap(bytes));
            }
            // 清空暫存數(shù)據(jù),并切換成關注讀事件
            key.attach(null);
            key.interestOps(SelectionKey.OP_READ);
        }
        iterator.remove();
    }
}

EventLoop

處理 Channel 中數(shù)據(jù)的讀寫

  • 在上面的Server中,大量并發(fā)時單線程地處理讀寫事件會導致延遲,因此將讀寫處理抽取出來,可利用多線程實現(xiàn)高并發(fā)
  • 一個EventLoop會關聯(lián)一個selector,只會處理這個selector上的Channel
public class EventLoop2 implements Runnable{


    private final Thread thread;
    /**
     * 復用器,當前線程只處理這個復用器上的channel
     */
    public Selector selector;
    /**
     * 待處理的注冊任務
     */
    private final Queue<Runnable> queue = new LinkedBlockingQueue<>();

    /**
     * 初始化復用器,線程啟動
     * @throws IOException
     */
    public EventLoop2() throws IOException {
        this.selector = SelectorProvider.provider().openSelector();
        this.thread = new Thread(this);
        thread.start();
    }

    /**
     * 將通道注冊給當前的線程處理
     * @param socketChannel
     * @param keyOps
     */
    public void register(SocketChannel socketChannel,int keyOps){
        // 將注冊新的socketChannel到當前selector封裝成一個任務
        queue.add(()->{
            try {
                MyChannel myChannel = new MyChannel(socketChannel, this);
                SelectionKey key = socketChannel.register(selector, keyOps);
                key.attach(myChannel);
            } catch (Exception e){
                e.printStackTrace();
            }
        });
        // 喚醒阻塞等待的selector線程
        selector.wakeup();
    }

    /**
     * 循環(huán)地處理 注冊事件、讀寫事件
     */
    @Override
    public void run() {
        while (!thread.isInterrupted()){
            try {
                int select = selector.select(1000);
                // 處理注冊到當前selector的事件
                if (select == 0){
                    Runnable task;
                    while ((task = queue.poll()) != null){
                        task.run();
                    }
                    continue;
                }
                // 處理讀寫事件
                System.out.println("服務器收到讀寫事件,select:" + select);
                processReadWrite();

            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 處理讀寫事件
     * @throws Exception
     */
    private void processReadWrite() throws Exception{
        System.out.println(Thread.currentThread() + "開始監(jiān)聽讀寫事件");
        // 3.2、遍歷事件進行處理
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> iterator = selectionKeys.iterator();
        while(iterator.hasNext()){
            SelectionKey key = iterator.next();
            MyChannel myChannel = (MyChannel)key.attachment();
            if(key.isReadable()){
                // 將數(shù)據(jù)讀進buffer
                myChannel.doRead(key);
            }
            if (key.isWritable()){
                myChannel.doWrite(key);
            }
            iterator.remove();
        }
    }
}

EventloopGroup

一組EventLoop,輪訓地為eventLoop分配Channel

public class EventLoopGroup {
    private EventLoop2[] children = new EventLoop2[1];

    private AtomicInteger idx = new AtomicInteger(0);

    public EventLoopGroup() throws IOException {
        for (int i = 0; i < children.length; i++){
            children[i] = new EventLoop2();
        }
    }

    public EventLoop2 next(){
        // 輪訓每一個children
        return children[idx.getAndIncrement() & (children.length - 1)];
    }

    public void register(SocketChannel channel,int ops){
        next().register(channel,ops);
    }
}

Channel

封裝了SocketChannel 和 Pipline,將從Channel讀寫的消息,沿著Pipline上的節(jié)點進行處理

  • 在上面EventLoop中,注冊Channel到對應的Selector前,會進行封裝,將自定義的Channel放在讀寫事件觸發(fā)時會返回的SelectionKey里面
  • 同時提供了數(shù)據(jù)讀寫處理方法,讀寫事件觸發(fā)時調(diào)用該方法,數(shù)據(jù)會沿著pipline上去處理
public class MyChannel {

    private SocketChannel channel;

    private EventLoop2 eventLoop;

    private Queue<ByteBuffer> writeQueue;

    private PipLine pipLine;

    /**
     * 一個channel關聯(lián)一個eventLoop、一個pipLine、一個socketChannel、一個writeQueue
     * @param channel
     * @param eventLoop
     */
    public MyChannel(SocketChannel channel, EventLoop2 eventLoop) {
        this.channel = channel;
        this.eventLoop = eventLoop;
        this.writeQueue = new ArrayDeque<>();
        this.pipLine = new PipLine(this,eventLoop);
        this.pipLine.addLast(new MyHandler1());
        this.pipLine.addLast(new MyHandler2());
    }

    /**
     * 讀事件處理
     * @param key
     * @throws Exception
     */
    public void doRead(SelectionKey key) throws Exception{
        try {
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int readNum = channel.read(buffer);
            if (readNum == -1){
                System.out.println("讀取-1時,表示IO流已結束");
                channel.close();
                return;
            }
            // 轉成可讀狀態(tài)
            buffer.flip();
            // 消息放入pipLine,交給頭節(jié)點, 頭節(jié)點開始傳遞
            pipLine.headContext.fireChannelRead(buffer);

        } catch (IOException e) {
            System.out.println("讀取發(fā)生異常,廣播socket");
            channel.close();
        }
    }

    /**
     * 真正地寫出數(shù)據(jù),關注寫事件后,會觸發(fā)
     * @param key
     * @throws IOException
     */
    public void doWrite(SelectionKey key) throws IOException{
        ByteBuffer buffer;
        while ((buffer =writeQueue.poll()) != null){
            channel.write(buffer);
        }
        // 回復讀取狀態(tài)
        key.interestOps(SelectionKey.OP_READ);

    }

    /**
     * 寫出到隊列
     * @param msg
     */
    public void doWriteQueue(ByteBuffer msg){
        writeQueue.add(msg);
    }

    /**
     * 從最后一個節(jié)點進行寫出,寫出到頭節(jié)點是調(diào)用doWriteQueue
     * @param msg
     */
    public void write(Object msg){
        this.pipLine.tailContext.write(msg);
    }

    /**
     * 從最后一個節(jié)點進行flush,寫出到頭節(jié)點時調(diào)用doFlush
     */
    public void flush(){
        this.pipLine.tailContext.flush();
    }

    /**
     * 關注寫事件,才能進行真正地寫出
     */
    public void doFlush(){
        this.channel.keyFor(eventLoop.selector).interestOps(SelectionKey.OP_WRITE);
    }

}

Handler 和 HandlerContext

handler 接口定義了可以擴展處理的消息,由開發(fā)人員實現(xiàn)具體的處理

handlerContext 類封裝了handler的實現(xiàn)類,將handler的上一個節(jié)點和下一個節(jié)點,讓消息可以延者鏈表傳遞

public interface Handler {

    /**
     * 讀取數(shù)據(jù)處理
     * @param ctx
     * @param msg
     */
    void channelRead(HandlerContext ctx,Object msg);

    /**
     * 寫出數(shù)據(jù)
     * @param ctx
     * @param msg
     */
    void write(HandlerContext ctx,Object msg);

    /**
     * 刷下數(shù)據(jù)
     * @param ctx
     */
    void flush(HandlerContext ctx);
}
public class HandlerContext {

    private Handler handler;

    MyChannel channel;

    HandlerContext prev;

    HandlerContext next;

    public HandlerContext(Handler handler, MyChannel channel) {
        this.handler = handler;
        this.channel = channel;
    }

    /**
     * 讀消息的傳遞,從頭節(jié)點開始往后傳
     * @param msg
     */
    public void fireChannelRead(Object msg){
        HandlerContext next = this.next;
        if (next != null){
            next.handler.channelRead(next,msg);
        }
    }

    /**
     * 從尾節(jié)點開始往前傳
     * @param msg
     */
    public void write(Object msg){
        HandlerContext prev = this.prev;
        if (prev != null){
            prev.handler.write(prev,msg);
        }
    }

    /**
     * 從尾節(jié)點開始往前傳
     */
    public void flush(){
        HandlerContext prev = this.prev;
        if (prev != null){
            prev.handler.flush(prev);
        }
    }
}

Pipline

本質是鏈表,包含了頭尾節(jié)點的HandlerContext,提供方法給開發(fā)人員加節(jié)點

public class PipLine {

    private MyChannel channel;

    private EventLoop2 eventLoop;

    public HandlerContext headContext;

    public HandlerContext tailContext;

    public PipLine(MyChannel channel, EventLoop2 eventLoop) {
        this.channel = channel;
        this.eventLoop = eventLoop;
        PipHandler headHandler = new PipHandler();
        this.headContext = new HandlerContext(headHandler,channel);
        PipHandler tailHandler = new PipHandler();
        this.tailContext = new HandlerContext(tailHandler,channel);
        // 構建鏈表
        this.headContext.next = this.tailContext;
        this.tailContext.prev = this.headContext;
    }

    public void addLast(Handler handler){
        HandlerContext curr = new HandlerContext(handler, channel);

        // 連接在倒數(shù)第二個后面
        HandlerContext lastButOne = this.tailContext.prev;
        lastButOne.next = curr;
        curr.prev = lastButOne;

        // 連接在最后一個前面
        curr.next = tailContext;
        tailContext.prev = curr;

    }

    public static class PipHandler implements Handler{

        @Override
        public void channelRead(HandlerContext ctx, Object msg) {
            System.out.println("接收"+(String) msg +"進行資源釋放");
        }

        @Override
        public void write(HandlerContext ctx, Object msg) {
            System.out.println("寫出"+msg.toString());
        }

        @Override
        public void flush(HandlerContext ctx) {
            System.out.println("flush");
        }
    }
}

到此這篇關于基于Java NIO編寫一個簡單版Netty服務端的文章就介紹到這了,更多相關Java NIO編寫Netty服務端內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Springboot筆記之熱部署及不生效的解決方案

    Springboot筆記之熱部署及不生效的解決方案

    這篇文章主要介紹了Springboot筆記之熱部署及不生效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • SpringBoot@Profile注解和Spring?EL(多環(huán)境注入)

    SpringBoot@Profile注解和Spring?EL(多環(huán)境注入)

    為了方便, Spring還提供了 Profile機制, 使我們可以很方便地實現(xiàn)各個環(huán)境之間的切換,在使用DI來依賴注入的時候,能夠根據(jù)@profile標明的環(huán)境,將注入符合當前運行環(huán)境的相應的bean,本文通過示例代碼介紹SpringBoot@Profile注解和Spring?EL,需要的朋友可以參考下
    2024-02-02
  • spring注解 @Valid 的作用說明

    spring注解 @Valid 的作用說明

    這篇文章主要介紹了spring注解 @Valid 的作用說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Java編程實現(xiàn)中英混合字符串數(shù)組按首字母排序的方法

    Java編程實現(xiàn)中英混合字符串數(shù)組按首字母排序的方法

    這篇文章主要介紹了Java編程實現(xiàn)中英混合字符串數(shù)組按首字母排序的方法,涉及Java字符串操作及拼音轉換的相關使用技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-11-11
  • Java使用@EnableEurekaServer實現(xiàn)自動裝配詳解

    Java使用@EnableEurekaServer實現(xiàn)自動裝配詳解

    這篇文章主要介紹了Java使用@EnableEurekaServer實現(xiàn)自動裝配過程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧
    2022-10-10
  • 關于IDEA配置文件字符集的問題

    關于IDEA配置文件字符集的問題

    這篇文章主要介紹了關于IDEA配置文件字符集的問題,本文通過圖文并茂的形式給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-12-12
  • 通過Spring自定義NamespaceHandler實現(xiàn)命名空間解析(推薦)

    通過Spring自定義NamespaceHandler實現(xiàn)命名空間解析(推薦)

    這篇文章主要介紹了通過Spring自定義NamespaceHandler實現(xiàn)命名空間解析,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-04-04
  • MyBatis-Plus之邏輯刪除的實現(xiàn)

    MyBatis-Plus之邏輯刪除的實現(xiàn)

    這篇文章主要介紹了MyBatis-Plus之邏輯刪除的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11
  • java使用異或對文件進行加密解密

    java使用異或對文件進行加密解密

    這篇文章主要為大家詳細介紹了java使用異或方式對文件進行加密解密,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • 詳解java中controller層是干什么的

    詳解java中controller層是干什么的

    Controller一般指的是MVC架構里的控制層,是對項目里的功能做統(tǒng)一的調(diào)度,下面這篇文章主要給大家介紹了關于java中controller層是干什么的相關資料,文中通過圖文介紹的非常詳細,需要的朋友可以參考下
    2023-03-03

最新評論

夏津县| 长治市| 无棣县| 河曲县| 五原县| 富阳市| 榆树市| 修水县| 林口县| 镇宁| 阿城市| 盘锦市| 大新县| 自贡市| 宁陕县| 河西区| 油尖旺区| 宾川县| 宣汉县| 雅江县| 奉新县| 壶关县| 蒙阴县| 石城县| 中西区| 哈尔滨市| 辽源市| 留坝县| 清涧县| 方正县| 高要市| 肇源县| 康乐县| 天峨县| 炎陵县| 青海省| 莎车县| 嵩明县| 河北省| 若尔盖县| 东宁县|