Netty框架學習(一) -- Netty框架簡介 & I/O模型

@[TOC]

介紹

  1. Netty是由JBOSS提供的一個Java開源框架孵延,現(xiàn)為Github獨立項目。
  2. Netty是一個異步的状共、基于事件驅(qū)動的網(wǎng)絡應用框架费彼,用以快速開發(fā)高性能,高可用的網(wǎng)絡IO程序口芍。
  3. Netty主要針對TCP協(xié)議下箍铲,面向Client端高并發(fā)應用,或者Peer-to-Peer場景下大量數(shù)據(jù)持續(xù)傳輸?shù)膽谩?/li>
  4. Netty本質(zhì)是一個NIO框架鬓椭,適用于服務器通訊相關的多種應用場景
在這里插入圖片描述

官網(wǎng)介紹 : [ Netty是一個NIO客戶端服務器框架颠猴,可以快速輕松地開發(fā)網(wǎng)絡應用程序,例如協(xié)議服務器和客戶端小染。它極大地簡化和簡化了諸如TCP和UDP套接字服務器之類的網(wǎng)絡編程翘瓮。]

  • 更高的吞吐量,更低的延遲
  • 減少資源消耗
  • 減少不必要的內(nèi)存復制

應用場景

  1. 分布式系統(tǒng)中的各個節(jié)點之間服務遠程調(diào)用裤翩,RPC框架必不可少资盅,所以Netty做為異步高性能的通信框架,做為基礎通信組件被RPC框架使用
  2. 阿里的分布式框架Dubbo, Dubbo協(xié)議默認使用Netty作為基礎通信組件踊赠,用于實現(xiàn)各進程節(jié)點之間內(nèi)部通信

I/O模型 (BIO呵扛、NIO、AIO)

  • BIO :同步阻塞(傳統(tǒng)阻塞型)筐带,服務器實現(xiàn)模式為一個連接一個線程今穿,即客戶端有連接請求時服務器端就需要啟動一個線程進行處理,如果這個連接不做任何事情會造成不必要的線程開銷伦籍。

  • NIO :同步非阻塞蓝晒,服務器實現(xiàn)模式為一個線程處理多個請求腮出,即客戶端發(fā)送的連接請求都會注冊到多路復用器上,多路復用器輪詢到連接有I/O請求就進行處理芝薇。

  • AIO : 異步非阻塞胚嘲,AIO引入異步通道的概念,采用了 Proactor模式洛二,簡化了程序編寫馋劈,有效的請求菜啟動線程,它的特點是先由操作系統(tǒng)完成后才通知服務端和程序啟動線程去處理灭红,一般適用于連接數(shù)多且連接時間較長的應用

I/O使用場景:

  • BIO方式適用于連接數(shù)目比較小且固定的架構(gòu),這種方式對服務器資源要求比較高口注,并發(fā)局限于應用中变擒,JDK1.4以前的唯一選擇,簡單易懂寝志。
  • NIO方式適用于連接數(shù)目多且連接比較短(輕操作)的架構(gòu)娇斑,比如聊天服務器,彈幕系統(tǒng)材部,服務器之間通訊毫缆,JDK1.4后支持
  • AIP方式適用于連接數(shù)多且連接比較長(重操作)的架構(gòu),比如相冊服務器乐导,充分調(diào)用OS參與并發(fā)操作苦丁,JDK1.7后支持。

BIO

在這里插入圖片描述

BIO編程簡單流程:

  1. 服務端啟動一個ServerSocket
  2. 客戶端啟動socket對服務器進行通信物臂,默認情況下服務器端需要對每個客戶建立一個線程與之通訊
  3. 客戶端發(fā)出請求后旺拉,先咨詢服務器是否有線程響應,如果沒有則會等待棵磷,或者被拒絕
  4. 如果有響應蛾狗,客戶端線程會等待請求結(jié)束后,在繼續(xù)執(zhí)行

案例 :

  1. 使用BIO模型編寫一個服務端仪媒,監(jiān)聽端口沉桌,當有客戶端連接時,就啟動一個線程與之通訊
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 yxl
 * @version 1.0
 * @date 2021/3/9 20:18
 */
public class BIOServer {
    public static void main(String[] args) throws IOException {

        //1.創(chuàng)建一個線程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        ServerSocket serverSocket = new ServerSocket(6666);
        System.out.println("服務器啟動了");
        while(true){
            Socket socket = serverSocket.accept();
            System.out.println("連接到一個客戶端");
            //創(chuàng)建線程池與之
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    handler(socket);
                }
            });
        }
    }

    public static void handler(Socket socket){
        try {
            System.out.println("當前線程ID"+ Thread.currentThread().getId() + "名稱" + Thread.currentThread().getName());
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream();
            while (true){
                int read = inputStream.read();
                if(read != -1){
                    System.out.println("-");
                    System.out.println(new String(bytes,0,read));
                }else{
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            System.out.println("關閉客戶端的連接");
        }

    }
}

使用 telnet 測試

在這里插入圖片描述
在這里插入圖片描述

缺點:

  1. 每個請求都需要創(chuàng)建獨立都線程算吩,與對應都客戶端進行數(shù)據(jù)Read, 業(yè)務處理, 數(shù)據(jù) Write .
  2. 當并發(fā)數(shù)較大時留凭,需要創(chuàng)建大量線程來處理連接 ,系統(tǒng)資源占有較大偎巢。
  3. 連接建立后冰抢,如果當前線程暫時沒有數(shù)據(jù)可讀,則線程阻塞在Read操作上艘狭,造成資源浪費挎扰。

NIO

  1. Java NIO 全稱 Java non-blocking IO, 是指JDK提供新當API翠订,從JDK1.4開始,Java 提供了一系列改進當輸入/輸出的新特性遵倦,被統(tǒng)稱為 NIO 尽超,是異步非阻塞的
  2. NIO 相關類都被放在 Java.nio 包及子包下,并且對原 Java.io 包中很多類進行改寫梧躺。
  3. NIO 有三大核心部分 : Channel (通道)似谁,Buffer(緩存區(qū)),Selector(選擇器)
  4. NIO 是面向緩存區(qū)掠哥,或者面向快編程對巩踏,數(shù)據(jù)讀到一個它稍后處理對緩存區(qū),需要時可在緩存區(qū)中前后移動续搀,這就增加類處理過程中的靈活性塞琼。使用它可以提供非阻塞式的高伸縮性網(wǎng)絡

一、Buffer :

├─ByteBuffer
├─IntBuffer
├─LongBuffer
├─ShortBuffer
├─StringCharBuffer
├─DoubleBuffer
├─CharBuffer
└ FloatBuffer
  • 緩存區(qū) Bufer 禁舷,本質(zhì)上是一個可以讀取數(shù)據(jù)的內(nèi)存塊彪杉,可以理解成是一個容器對象(含數(shù)組),該對象提供了一組方法牵咙,可以更輕松的使用內(nèi)存塊派近,緩存區(qū)對象內(nèi)置了一些機制 ,能夠跟蹤和記錄緩存區(qū)的狀態(tài)變化洁桌。Channel 提供文件網(wǎng)絡讀取的渠道渴丸,但是讀取或者寫入的數(shù)據(jù)必須經(jīng)由buffer 。
public class BasicBuffer {

    public static void main(String[] args) {
        //創(chuàng)建一個IntBuffer 大小為5
        IntBuffer intBuffer = IntBuffer.allocate(5);
        intBuffer.put(1);
        intBuffer.put(2);
        intBuffer.put(3);
        intBuffer.put(4);
        intBuffer.put(45);
        //讀寫轉(zhuǎn)換
        intBuffer.flip();
        while (intBuffer.hasRemaining()){
            System.out.println(intBuffer.get());
        }

    }
}
在這里插入圖片描述

所有的 Boffer 都繼承 并且有幾個重要的參數(shù)

public abstract class IntBuffer
    extends Buffer
    implements Comparable<IntBuffer>
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;  //當你寫數(shù)據(jù)到Buffer中時另凌,position表示當前的位置 初始值為o
    private int limit;
    private int capacity;
}
  • mark: 標記

  • capacity:容量曙强,既可以容納的最大數(shù)據(jù)量,在緩存區(qū)創(chuàng)建時被設定并且不能更改途茫。

  • position:位置碟嘴,下一個要被讀或者寫的元素的索引,每次讀寫緩存區(qū)數(shù)據(jù)時都會改變值囊卜,為下次讀寫做準備娜扇。

  • limit:表示緩存區(qū)的當前終點,不能對緩存區(qū)超過極限的位置進行讀寫栅组,且極限時可以修改的雀瓢。

斷點:

在這里插入圖片描述

二、Channel :

  1. Java的通道類似于流玉掸,但是有些區(qū)別刃麸,通道可以同時進行讀寫,而流只能讀或者寫司浪,也可以實現(xiàn)異步讀寫數(shù)據(jù)泊业,也可以從緩存區(qū)讀數(shù)據(jù)把沼,寫入到緩存區(qū)數(shù)據(jù)
  2. Channel 是一個 NIO 的接口
  3. 常用的 Channel 類有 :FileChannel、DatagramChannrl吁伺、ServerSocketChannel

FileChannel :

public class BasicFileChannel {

    public static void main(String[] args) throws IOException {
        String str = "Hello World";
        //創(chuàng)建一個輸出流Channel
        FileOutputStream fileOutputStream = new FileOutputStream("/Users/yanxiaolong/a.txt");

        //拿到FileChannel
        FileChannel channel = fileOutputStream.getChannel();
        
        //創(chuàng)建緩存區(qū)Buffer
        ByteBuffer allocate = ByteBuffer.allocate(1034);
        allocate.put(str.getBytes(StandardCharsets.UTF_8));
        //對ByteBuffer進行flip
        allocate.flip();
        //將ByteBuffer寫入FileChannel
        channel.write(allocate);

        //關閉流
        fileOutputStream.close();
    }
}

在這里插入圖片描述
public class BasicFileChannel2 {

    public static void main(String[] args) throws IOException {
        //讀取文件創(chuàng)建輸入流
        File file = new File("/Users/yanxiaolong/a.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        FileChannel channel = fileInputStream.getChannel();

        //創(chuàng)建緩存區(qū)Buffer
        ByteBuffer allocate = ByteBuffer.allocate((int)file.length());

        channel.read(allocate);
        System.out.println(new String(allocate.array()));

        fileInputStream.close();
    }
}
在這里插入圖片描述

MappedByteBuffer 文件Copy

public class BasicFileChannel3 {

    public static void main(String[] args) throws IOException {
        long start = System.currentTimeMillis();
        FileChannel inChannel = FileChannel.open(Paths.get("/Users/yanxiaolong/a.txt"), StandardOpenOption.READ);
        FileChannel outChannel = FileChannel.open(Paths.get("/Users/yanxiaolong/b.txt"), StandardOpenOption.WRITE,StandardOpenOption.READ,StandardOpenOption.CREATE_NEW);

        //內(nèi)存映射文件
        MappedByteBuffer inMappedBuf = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
        MappedByteBuffer outMappedBuf = outChannel.map(FileChannel.MapMode.READ_WRITE, 0, inChannel.size());

        //直接對緩沖區(qū)進行數(shù)據(jù)的讀寫操作
        byte[] dst = new byte[inMappedBuf.limit()];
        inMappedBuf.get(dst);
        outMappedBuf.put(dst);

        inChannel.close();
        outChannel.close();
        long end = System.currentTimeMillis();
        System.out.println("內(nèi)存映射文件所花時間:"+(end-start));

    }
}
在這里插入圖片描述

三饮睬、Selector :

  1. Java 的 NIO,非阻塞的iO方式/可以用一個線程篮奄,處理多個的客戶端連接则吟,就會使用到selector(選擇器), 是SelecttableChannle 對象的多路復用器
  2. Selector 能夠檢測多個注冊的通道上是否有事情發(fā)生(注意: 多個Channel以事件的方式可以注冊到同一個Selector)颂碧,如果有事情發(fā)生狞玛,便獲取事件然后針對每個事件進行相應的處理未妹。這樣就可以只用一個單線程區(qū)管理多個通道,也就是管理多個連接和請求夸赫。
  3. 只有連接真正有讀寫事件發(fā)生時菩帝,才會進行讀寫,就大大地減少了系統(tǒng)開銷憔足,并且不必為每個連接都創(chuàng)建一個線程胁附,不用區(qū)維護多個線程
  4. 避免了多線程之間都上下文切換導致的開銷
在這里插入圖片描述

服務端

public class NIOServer {

    public static void main(String[] args) throws IOException {
        //創(chuàng)建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        //創(chuàng)建選擇器
        Selector selector = Selector.open();

        serverSocketChannel.bind(new InetSocketAddress(6666));
        serverSocketChannel.configureBlocking(false);
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        //循環(huán)等待客戶端連接
        while (true){
            if(selector.select(1000) == 0){
                System.out.println("服務器等待了一秒 無連接");
                continue;
            }
            //如果大于0就獲取相關的selectedKeys連接酒繁,已經(jīng)獲取到關注到事件
            //selector.selectedKeys(); 返回事件的集合
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()){
                //獲取下一個元素
                SelectionKey next = iterator.next();
                //如果是OP_ACCEPT滓彰,有新的客戶端連接
                if(next.isAcceptable()){
                    //該客戶端生成一個SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);
                    System.out.println("客戶端連接成功 生成一個 socketChannel" + socketChannel.hashCode());
                    //注冊,關聯(lián)ByteBuffer
                    socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                //發(fā)送OP_READ
                if(next.isReadable()){
                    SocketChannel socketChannel = (SocketChannel) next.channel();
                    ByteBuffer byteBuffer =  (ByteBuffer) next.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println("from 客戶端:" + new String(byteBuffer.array()));
                }
                iterator.remove();
            }


        }
    }


}

客戶端

public class NIOClient {
    public static void main(String[] args) throws IOException {
        //得到網(wǎng)絡通道
        SocketChannel socketChannel = SocketChannel.open();
        //設置非阻塞
        socketChannel.configureBlocking(false);
        //socket連接地址
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
        //連接服務器
        if(!socketChannel.connect(inetSocketAddress)){
            while(!socketChannel.finishConnect()){
                System.out.println("因為連接需要時間州袒,客戶端不會阻塞揭绑,可以做其他工作");
            }
        }

        //連接成功就發(fā)送數(shù)據(jù)
        String str = "hello world";

        ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes(StandardCharsets.UTF_8));
        //發(fā)送數(shù)據(jù) 將Buffer 數(shù)據(jù)寫入Channel
        socketChannel.write(byteBuffer);
        System.in.read();


    }
}

在這里插入圖片描述

原理:

  1. 當客戶端連接時,會通過ServerSocketChannel得到SocketChannel
  2. Selector繼續(xù)監(jiān)聽郎哭,select方法 返回有事件發(fā)送的通道的個數(shù)
  3. 將socketChannel 注冊到Selector上register (Selector sel,int ops),一個selector可以注冊多個SocketChannel
  4. 注冊后返回一個SelectionKey和Selector 關聯(lián)
  5. 進一步得到各個SelectionKey, 通過SelectionKey 反向獲取 SocketChannel , 方法Channel() 他匪,通過 channel 寫業(yè)務代碼


  • SelectionKey : 表示SelectableChannel 和 Selector 之間的注冊關系,每次向選擇器注冊通道時就會選擇一個事件(選擇鍵)夸研。選擇鍵包含2個表示為整數(shù)值的操作集邦蜜。操作集的每一位都表示該鍵的通道鎖支持的一類可選操作

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

NIO與零拷貝

介紹:

  1. 零拷貝是網(wǎng)絡編程中的關鍵,很多性能優(yōu)化都離不開
  2. 在Java 程序中亥至,常用都零拷貝有 mmap(內(nèi)存映射) 和 sendFile 悼沈。

傳統(tǒng)I/O :

在這里插入圖片描述
  • 傳統(tǒng)的IO拷貝技術需要經(jīng)過四次拷貝(CPU copy 和 DMA copy),四次的狀態(tài)轉(zhuǎn)換(用戶態(tài)和內(nèi)核態(tài))姐扮,效率較為低下

mmap優(yōu)化 :

在這里插入圖片描述
  • mmap 通過內(nèi)存映射絮供,將文件映射搭配內(nèi)核緩存區(qū),同時茶敏,用戶看見可以共享內(nèi)核的數(shù)據(jù)壤靶。這樣,在進行網(wǎng)絡傳輸時惊搏,就可以減少內(nèi)核空間到用戶軟件到拷貝次數(shù)

sendFile :

在這里插入圖片描述
  • Linux 2.1 版本 提供了 sendFile 函數(shù)贮乳,其基本原理如下:數(shù)據(jù)根本不經(jīng)過用戶狀態(tài)忧换,直接從內(nèi)核緩沖區(qū)進入到 Socket Buffer,同時塘揣,由于和用戶態(tài)完全無關包雀,就減少了一次上下文切換。

mmap 和 sendFile的區(qū)別

  1. mmap適合小數(shù)據(jù)量讀寫亲铡,sendFile適合大文件傳輸
  2. mmap需要4次上下文切換才写,3次數(shù)據(jù)拷貝,sendFile 需要3次上下文切換奖蔓,最少2次數(shù)據(jù)拷貝
  3. sendFile 可以利用DMA 方式赞草,減少CPU拷貝,mmap (必須從內(nèi)核拷貝到Socket 緩存區(qū) )

案例:

public class NewIoServer {
    public static void main(String[] args) throws Exception {

        InetSocketAddress address = new InetSocketAddress(7001);

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        ServerSocket serverSocket = serverSocketChannel.socket();

        serverSocket.bind(address);

        //創(chuàng)建buffer
        ByteBuffer byteBuffer = ByteBuffer.allocate(4096);

        while (true) {
            SocketChannel socketChannel = serverSocketChannel.accept();

            int readcount = 0;
            while (-1 != readcount) {
                try {

                    readcount = socketChannel.read(byteBuffer);

                } catch (Exception ex) {
                    break;
                }
                //倒帶 position = 0 mark 作廢
                byteBuffer.rewind();
            }
        }
    }
}

public class NewIoClient {
    public static void main(String[] args) throws Exception {

        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress("localhost", 7001));
        String filename = "protoc-3.6.1-win32.zip";

        //得到一個文件channel
        FileChannel fileChannel = new FileInputStream(filename).getChannel();

        //準備發(fā)送
        long startTime = System.currentTimeMillis();

        //在linux下一個transferTo 方法就可以完成傳輸
        //在windows 下 一次調(diào)用 transferTo 只能發(fā)送8m , 就需要分段傳輸文件, 而且要主要
        //傳輸時的位置 =》 課后思考...
        //transferTo 底層使用到零拷貝
        long transferCount = fileChannel.transferTo(0, fileChannel.size(), socketChannel);

        System.out.println("發(fā)送的總的字節(jié)數(shù) =" + transferCount + " 耗時:" + (System.currentTimeMillis() - startTime));

        //關閉
        fileChannel.close();

    }
}

AIO

  1. JDK1.7 引入 Asynchronous I/O, 即AIO 吆鹤,在進行I/O 編程中厨疙,常用到兩種哦是:Reactor 和 Proactor. Java 的 NIO 就是 Reactor , 當有事件觸發(fā)時,服務端得到通知疑务,進行相應的處理
  2. AIO 即 NIO 模式2.0 沾凄,叫做異步不阻塞 的 IO。AIO 引入異步通道的概念知允,采用了Proactor模式撒蟀,簡化了程序編寫,有效的請求才啟動線程温鸽,它的特點是由操作系統(tǒng)完成后才通知服務端程序啟動去處理線程保屯,一般用于連接數(shù)較多且連接事件較長的應用
  3. 目前AIO還沒有廣泛流行

NIO vs BIO

  1. BIO以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)涤垫,塊 I/O的效率比流 I/O 高很多
  2. BIO是阻塞的姑尺,NIO是非阻塞的
  3. BIO基于字節(jié)流和字符流進行操作,而 NIO 基于 Channel (通道) 和 Buffer (緩存區(qū)) 進行操作蝠猬,數(shù)據(jù)總是從通道讀取到緩存區(qū)中切蟋,或者從緩存區(qū)寫入到通道中,Selector (選擇器) 用于監(jiān)聽多個通道事件 (比如 :連接請求榆芦,數(shù)據(jù)到達等)柄粹,因此使用單個線程就可以監(jiān)聽多個客戶端通道

Netty線程模型

  • 原NIO存在 :API繁瑣,需要熟練掌握三大組件歧杏、開發(fā)與維護難度大镰惦,需要考慮斷線重連,網(wǎng)絡閃斷犬绒,半包讀寫旺入,網(wǎng)絡阻塞等

線程模型基本介紹:

  1. 不同的線程模型,對程序?qū)π阅苡泻艽笥绊?/li>
  2. 目前存在對線程模型有 : 傳統(tǒng)阻塞I/O服務模型 、 Reactor模式
  3. 根據(jù)Reactor 的數(shù)量和處理資源池線程的數(shù)量不同茵瘾,有3種典型的實現(xiàn)
    - 單Reactor 單線程
    - 單Reactor 多線程
    - 主從Reactor 多線程
  4. Netty線程模型(Netty主要基于主從 Reactor 多線程模型做了一定的改進礼华,其中中從 Reactor 多線程模型有多個 Reactor)

Reactor 模式 :

  1. 基于I/O 復用模型,多個連接公用一個阻塞對象拗秘,應用程序只需要在一個阻塞對象等待圣絮,無需等待所有連接,當某個連接有新的數(shù)據(jù)可以處理時雕旨,操作系統(tǒng)通知應用程序扮匠,線程從阻塞狀態(tài)返回,開始業(yè)務處理
  2. 基于線程是復用線程資源凡涩,不必再為每個連接而創(chuàng)建線程棒搜,將連接完成后的業(yè)務處理任務分配給線程進行處理,一個線程可以處理多個業(yè)務

單線程模型 :

在這里插入圖片描述
  • Reactor 內(nèi)部通過selector 監(jiān)控連接事件活箕,收到事件后通過dispatch進行分發(fā)力麸,如果是連接建立都事件,則由Acceptor處理育韩,Acceptor通過accept接受連接克蚂,并創(chuàng)建一個Handler來處理連接后續(xù)都各種事件。如果是讀寫事件筋讨,直接調(diào)用連接對應都Handler來處理
  • Handler 完成read -> (decode -> compute -> encode) -> send的業(yè)務流程
  • 這種模型好處是簡單埃叭,壞處卻很明顯,當某個Handler阻塞時版仔,會導致其他客戶端的handler和accpetor都的不到執(zhí)行游盲,無法做到高性能误墓,只適合用于業(yè)務處理非陈福快都場景

單線程模型就是只指定一個線程執(zhí)行客戶端連接和讀寫操作,也就是在一個Reactor中完成谜慌,對應在Netty中的實現(xiàn)就是將NioEventLoopGroup線程數(shù)設置為1然想,核心代碼是:

 NioEventLoopGroup group = new NioEventLoopGroup(1);
        ServerBootstrap bootstrap = new ServerBootstrap();
        bootstrap.group(group)
                .channel(NioServerSocketChannel.class)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.TCP_NODELAY, true)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childHandler(new ServerHandlerInitializer());

多線程模型 :

在這里插入圖片描述
  • 主線程中,Reactor對象通過selector監(jiān)控連接事件欣范,收到事件后通過dispatch進行分發(fā)变泄,如果是連接建立事件,則由Acceptor處理恼琼,Acceptor 通過accept 接收連接妨蛹,并創(chuàng)建一個Handler來處理后續(xù)事件,而Handler只負責響應事件晴竞,不進行業(yè)務操作蛙卤,也就是只進行read讀取操作和write寫出數(shù)據(jù),業(yè)務處理交給一個線程池進行處理
  • 線程池分配一個線程完成真正都業(yè)務處理,將響應結(jié)果交給主線程都Handler處理颤难,Handler將結(jié)果send給client


    在這里插入圖片描述

多線程模型就是在一個單Reactor中進行客戶端連接處理神年,然后業(yè)務處理交給線程池,核心代碼如下:

NioEventLoopGroup eventGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

主從多線程模型

在這里插入圖片描述
  • 存在多個Reactor行嗤,每個Reactor都有自己的selector選擇器已日,線程和dispatch
  • 主線程中的mainReactor通過自己的selector監(jiān)控連接建立事件,收到事件后通過Accpetor接收栅屏,將新的連接分配給某個子線程
  • 子線程中的subReactor將mainReactor分配的連接加入連接隊列中通過自己的selector進行監(jiān)聽飘千,并創(chuàng)建一個Handler用于處理后續(xù)事件
  • Handler完成read->業(yè)務處理->send的完整業(yè)務流程
在這里插入圖片描述

主從多線程模型是有多個Reactor,也就是存在多個selector栈雳,所以我們定義一個bossGroup和一個workGroup占婉,核心代碼如下:

NioEventLoopGroup bossGroup = new NioEventLoopGroup();
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup,workerGroup)
        .channel(NioServerSocketChannel.class)
        .option(ChannelOption.TCP_NODELAY, true)
        .option(ChannelOption.SO_BACKLOG, 1024)
        .childHandler(new ServerHandlerInitializer());

Reactor模式優(yōu)點:

  1. 響應快,不必為單個同步時間所阻塞甫恩,雖然 Reactor 本身依然是同步都
  2. 可以最大程度都避免復雜多線程及同步問題逆济,并且避免來多線程/進程都切換開銷
  3. 擴展性好,可以方便都通過增加 Reactor 實例來充分利用CPU資源
  4. 復用性好磺箕,Reactor 模型本身與集體事件處理邏輯無關奖慌,具有很高的復用性



個人博客地址:http://blog.yanxiaolong.cn/

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市松靡,隨后出現(xiàn)的幾起案子简僧,更是在濱河造成了極大的恐慌,老刑警劉巖雕欺,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件岛马,死亡現(xiàn)場離奇詭異,居然都是意外死亡屠列,警方通過查閱死者的電腦和手機啦逆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來笛洛,“玉大人夏志,你說我怎么就攤上這事】寥茫” “怎么了沟蔑?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長狱杰。 經(jīng)常有香客問我瘦材,道長,這世上最難降的妖魔是什么仿畸? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任食棕,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘宣蠕。我一直安慰自己例隆,他們只是感情好,可當我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布抢蚀。 她就那樣靜靜地躺著镀层,像睡著了一般。 火紅的嫁衣襯著肌膚如雪皿曲。 梳的紋絲不亂的頭發(fā)上唱逢,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音屋休,去河邊找鬼坞古。 笑死,一個胖子當著我的面吹牛劫樟,可吹牛的內(nèi)容都是我干的痪枫。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼叠艳,長吁一口氣:“原來是場噩夢啊……” “哼奶陈!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起附较,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤吃粒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拒课,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體徐勃,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年早像,在試婚紗的時候發(fā)現(xiàn)自己被綠了僻肖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡扎酷,死狀恐怖檐涝,靈堂內(nèi)的尸體忽然破棺而出遏匆,到底是詐尸還是另有隱情法挨,我是刑警寧澤,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布幅聘,位于F島的核電站凡纳,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏帝蒿。R本人自食惡果不足惜荐糜,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧暴氏,春花似錦延塑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至沼撕,卻和暖如春宋雏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背务豺。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工磨总, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人笼沥。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓蚪燕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親奔浅。 傳聞我的和親對象是個殘疾皇子邻薯,可洞房花燭夜當晚...
    茶點故事閱讀 44,884評論 2 354

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