1. 程式人生 > >Netty進階:Netty核心NioEventLoop原理解析

Netty進階:Netty核心NioEventLoop原理解析


Netty提供了Java NIO Reactor模型的實現,之前寫過一篇文章是對三種Reactor模式的簡單實現:Reactor模型的Java NIO實現,當然netty中的實現要複雜的多。並且Netty將實現好的Reactor模型封裝起來,我們只需提供適當的引數就可以實現不同執行緒模式的Reactor模型。

單執行緒模型

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup)

例項化了一個 NioEventLoopGroup, 構造器引數是1, 表示 NioEventLoopGroup 的執行緒池大小是1. 然後接著我們呼叫 b.group(bossGroup) 設定了伺服器端的 EventLoopGroup。

多執行緒模型

EventLoopGroup bossGroup = new NioEventLoopGroup(n);
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)

主從多執行緒模型

EventLoopGroup bossGroup = new NioEventLoopGroup(4);
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group
(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)

Netty 的伺服器端的 acceptor 階段, 沒有使用到多執行緒,也就是說n設定1和10是沒有區別的。

伺服器端的 ServerSocketChannel 只繫結到了 bossGroup 中的一個執行緒, 因此在呼叫 Java NIO 的 Selector.select 處理客戶端的連線請求時, 實際上是在一個執行緒中的, 所以對只有一個服務的應用來說, bossGroup 設定多個執行緒是沒有什麼作用的, 反而還會造成資源浪費。

1. NioEventLoopGroup

NioEventLoopGroup 類層次結構:
NIOEventLoopGroup
下面分析例項化過程,NioEventLoop 有幾個過載的構造器, 不過內容都沒有什麼大的區別, 最終都是呼叫的父類MultithreadEventLoopGroup構造器:

protected MultithreadEventLoopGroup(int nThreads, Executor executor, Object... args) {
        super(nThreads == 0 ? DEFAULT_EVENT_LOOP_THREADS : nThreads, executor, args);
    }

此時個引數值對應如下:

nThreads:0
executor: null
selectorProvider: SelectorProvider.provider()
selectStrategyFactory: DefaultSelectStrategyFactory.INSTANCE
拒絕策略RejectedExecutionHandlers.reject()

如果我們傳入的執行緒數 nThreads 是0, 那麼 Netty 會為我們設定預設的執行緒數 DEFAULT_EVENT_LOOP_THREADS, 而這個預設的執行緒數是怎麼確定的呢?其實很簡單, 在靜態程式碼塊中, 會首先確定 DEFAULT_EVENT_LOOP_THREADS 的值:

 static {
        DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
                "io.netty.eventLoopThreads", NettyRuntime.availableProcessors() * 2)); 
    }

回到MultithreadEventLoopGroup構造器中, 這個構造器會繼續呼叫父類 MultithreadEventExecutorGroup 的構造器:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor, Object... args) {
    this(nThreads, executor, DefaultEventExecutorChooserFactory.INSTANCE, args);
}

在此構造方法中,我們指定了一個EventExecutor的選擇工廠DefaultEventExecutorChooserFactory,此工廠主要是用於選擇下一個可用的EventExecutor, 其內部有兩種選擇器, 一個是基於位運算,一個是基於普通的輪詢,它們的程式碼分別如下:

基於位運算的選擇器PowerOfTwoEventExecutorChooser

private static final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    private final AtomicInteger idx = new AtomicInteger();
    private final EventExecutor[] executors;

    PowerOfTwoEventExecutorChooser(EventExecutor[] executors) {
        this.executors = executors;
    }

    @Override
    public EventExecutor next() {
        return executors[idx.getAndIncrement() & executors.length - 1];
    }
}

基於普通輪詢的選擇器GenericEventExecutorChooser

private static final class GenericEventExecutorChooser implements EventExecutorChooser {
    private final AtomicInteger idx = new AtomicInteger();
    private final EventExecutor[] executors;

    GenericEventExecutorChooser(EventExecutor[] executors) {
        this.executors = executors;
    }
    @Override
    public EventExecutor next() {
        return executors[Math.abs(idx.getAndIncrement() % executors.length)];
    }
}

我們知道, MultithreadEventExecutorGroup 內部維護了一個 EventExecutor 陣列, Netty 的 EventLoopGroup 的實現機制其實就建立在 MultithreadEventExecutorGroup 之上。 每當 Netty 需要一個 EventLoop 時, 會呼叫EventExecutorChooser的next() 方法獲取一個可用的 EventLoop。

繼續回到MultithreadEventExecutorGroup的構造器:

protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
		 // 初始化executor
		if (executor == null) {
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }
 		// 初始化EventExecutor
        children = new EventExecutor[nThreads];
        for (int i = 0; i < nThreads; i ++) {
            boolean success = false;
            children[i] = newChild(executor, args);
            success = true;
        }
		// 生成選擇器物件
        chooser = chooserFactory.newChooser(children);
    }

此構造方法主要做了三件事:

  1. 初始化executor為ThreadPerTaskExecutor的例項
public final class ThreadPerTaskExecutor implements Executor {
    private final ThreadFactory threadFactory;

    public ThreadPerTaskExecutor(ThreadFactory threadFactory) {
        if (threadFactory == null) {
            throw new NullPointerException("threadFactory");
        }
        this.threadFactory = threadFactory;
    }

    @Override
    public void execute(Runnable command) {
        threadFactory.newThread(command).start();
    }
}

ThreadPerTaskExecutor 實現了Executor介面,其內部會通過newDefaultThreadFactory()指定的預設執行緒工廠來建立執行緒,並執行相應的任務。
2. 建立一個大小為 nThreads的EventExecutor陣列children,然後為每一個數組元素建立EventExecutor。children[i] = newChild(executor, args);, newChild(executor, args)方法在MultithreadEventExecutorGroup中沒有實現,我們在NioEventLoopGroup中找到了newChild的實現:

@Override
protected EventLoop newChild(Executor executor, Object... args) throws Exception {
    return new NioEventLoop(this, executor, (SelectorProvider) args[0],
        ((SelectStrategyFactory) args[1]).newSelectStrategy(), (RejectedExecutionHandler) args[2]);
}
就是建立NioEventLoop物件,關於NioEventLoop在後面詳細介紹。
  1. 根據我們預設DefaultEventExecutorChooserFactory選擇器工廠,繫結NioEventLoop陣列物件。在前面的構造方法中,我們指定了chooserFactory為DefaultEventExecutorChooserFactory,在此工廠內部,會根據children陣列的長度來動態選擇選擇器物件,用於選擇下一個可執行的EventExecutor,也就是NioEventLoop。

總結一下整個 NioEventLoopGroup 的初始化過程:

  • NioEventLoopGroup(其實是MultithreadEventExecutorGroup) 內部維護一個型別為 EventExecutor children 陣列, 其大小是 nThreads, 這樣就構成了一個執行緒池

  • 如果我們在例項化 NioEventLoopGroup 時, 如果指定執行緒池大小, 則 nThreads 就是指定的值, 反之是處理器核心數 * 2

  • MultithreadEventExecutorGroup 中會呼叫 newChild 抽象方法來初始化 children 陣列。抽象方法 newChild 是在 NioEventLoopGroup 中實現的, 它返回一個 NioEventLoop 例項.

最後以一張圖來總結:
在這裡插入圖片描述

2. NioEventLoop

前面一節說道了newChild建立NioEventLoop例項,下面開始分析NioEventLoop。

NioEventLoop 繼承於 SingleThreadEventLoop, 而 SingleThreadEventLoop 又繼承於 SingleThreadEventExecutor. SingleThreadEventExecutor 是 Netty 中對本地執行緒的抽象, 它內部有一個 Thread thread 屬性, 儲存了一個本地 Java 執行緒. 因此我們可以認為, 一個 NioEventLoop 其實和一個特定的執行緒繫結, 並且在其生命週期內, 繫結的執行緒都不會再改變。
NIOEventLoop
NioEventLoop的父類以及介面比較多,但是隻需要關注幾個重要的:

  • SingleThreadEventLoop
  • SingleThreadEventExecutor :實現任務佇列
  • AbstractScheduledEventExecutor:主要是實現定時任務
2.1例項化過程

在newChild方法中,呼叫了下面NioEventLoop建構函式:

NioEventLoop(NioEventLoopGroup parent, Executor executor, SelectorProvider selectorProvider,
             SelectStrategy strategy, RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler); 
    provider = selectorProvider;//1
    final SelectorTuple selectorTuple = openSelector();//2
    selector = selectorTuple.selector;
    unwrappedSelector = selectorTuple.unwrappedSelector;
    selectStrategy = strategy;//3
}
  1. 在初始化NioEventLoopGroup的時候就已經用到了SelectorProvider這個引數了,SelectorProvider是Java NIO中的抽象類,它的作用是呼叫Windows或者Linux底層NIO的實現,為JavaNIO提供服務,比如經常用的Selector.open()方法內部就是通過呼叫SelectorProvider.openSelector()來得到多路複用器selector。在這裡賦值給NioEventLoop的provider屬性。
  2. SelectorTuple是NioEventLoop的內部類,其實就是對Java NIO Selector的封裝。
private static final class SelectorTuple {
    final Selector unwrappedSelector;
    final Selector selector;
	//省略建構函式
}

selector和unwrappedSelector分別表示優化過的Selector和未優化過的Selector,selectedKeys表示優化過的SelectionKey。Netty在該類中對Java NIO的Selector做了優化,可以通過設定系統屬性io.netty.noKeySetOptimization進行修改,設定為true、yes或者1關閉優化,設定為false、no或者0開啟優化,預設開啟優化。
3. TODO

接下里進入父類SingleThreadEventLoop的建構函式super(parent, executor, false, DEFAULT_MAX_PENDING_TASKS, rejectedExecutionHandler):

protected SingleThreadEventLoop(EventLoopGroup parent, Executor executor,
                                boolean addTaskWakesUp, int maxPendingTasks,
                                RejectedExecutionHandler rejectedExecutionHandler) {
    super(parent, executor, addTaskWakesUp, maxPendingTasks, rejectedExecutionHandler);
    tailTasks = newTaskQueue(maxPendingTasks);
}

在此建構函式中只是初始化了TaskQueue(內部為有界的LinkedBlockingQueue),長度預設為DEFAULT_MAX_PENDING_TASKS,該常量定義於SingleThreadEventLoop類中,預設為16。繼續看父類SingleThreadEventExecutor的建構函式:

 protected SingleThreadEventExecutor(EventExecutorGroup parent, Executor executor,
                                        boolean addTaskWakesUp, int maxPendingTasks,
                                        RejectedExecutionHandler rejectedHandler) {
        super(parent);
        this.addTaskWakesUp = addTaskWakesUp;
        this.maxPendingTasks = Math.max(16, maxPendingTasks);
        this.executor = ObjectUtil.checkNotNull(executor, "executor");
        taskQueue = newTaskQueue(this.maxPendingTasks);
        rejectedExecutionHandler = ObjectUtil.checkNotNull(rejectedHandler, "rejectedHandler");
    }

executor即執行緒池,還記得上節講的初始化NioEventLoopGroup麼,在MultithreadEventExecutorGroup建構函式中執行executor = new ThreadPerTaskExecutor(newDefaultThreadFactory()),一邊儲存在類屬性中,一邊傳入了newChild方法中,最終也傳入該建構函式。

並且在SingleThreadEventExecutor類中有一個屬性private volatile Thread thread,它用來引用支撐該EventExecutor的執行緒,用來處理I/O事件和執行任務,叫支撐執行緒或者I/O執行緒均可,thread所引用的執行緒即來自executor。

這裡也初始化了taskQueue,它是任務佇列用來存放需要排程執行的任務,其中tailTasks和taskQueue均是任務佇列,而優先順序不同,taskQueue的優先順序高於tailTasks,定時任務的優先順序高於taskQueue。

宣告一點:
本文中使用的是Netty4.1.22版本,相比之前的版本,這裡對執行緒模型做了一些小改動。Thread thread屬性應用了執行緒池中的執行緒,也就是execute中的執行緒,而在舊版本中是指向獨立的執行緒,並且是通過執行緒工廠建立的。舊版本如下

protected SingleThreadEventExecutor(
        EventExecutorGroup parent, ThreadFactory threadFactory, boolean addTaskWakesUp) {
    this.parent = parent;
    this.addTaskWakesUp = addTaskWakesUp;

    thread = threadFactory.newThread(new Runnable() {
        @Override
        public void run() {
            boolean success = false;
            updateLastExecutionTime();
            SingleThreadEventExecutor.this.run();
            success = true;
        }
    });
    threadProperties = new DefaultThreadProperties(thread);
    taskQueue = newTaskQueue();
}
2.2 Netty 對Selecter的優化

在NioEventLoop例項化的過程中有提到,Netty對JDK Selector的優化,其實主要是對SelectKeys進行優化,JDK NIO中比如獲取準備好的key通過如下程式碼:

 Set<SelectionKey> keys = selector.selectedKeys();

那麼返回的是Set介面的實現HashSet,SeletctorImp中的定義如下。

 protected Set<SelectionKey> selectedKeys = new HashSet();
protected HashSet<SelectionKey> keys = new HashSet();

每當向Selector註冊時,對自動向key中新增元素,呼叫select方法後會更新該集合。Netty實現了AbstractSet,提供了和HashSet同樣的方法,只是內部實現不同(HashSet內部是HashMap),至於為什麼要這樣做,本人認為1.省去了Map中的value物件的記憶體,2.更方便的擴容。那麼Netty是如何做到的?其實就是在NioEventLoop例項的過程中呼叫的openSelector方法內部。

 private SelectorTuple openSelector() {
        final Selector unwrappedSelector;
            unwrappedSelector = provider.openSelector();
        final SelectedSelectionKeySet selectedKeySet = new SelectedSelectionKeySet();
       //省去幹擾程式碼
        Object maybeException = AccessController.doPrivileged(new PrivilegedAction<Object>() {
            public Object run() {
                   Field selectedKeysField = selectorImplClass.getDeclaredField("selectedKeys");
                   Field publicSelectedKeysField = selectorImplClass.getDeclaredField("publicSelectedKeys");
                   Throwable cause = ReflectionUtil.trySetAccessible(selectedKeysField, true);
                   selectedKeysField.set(unwrappedSelector, selectedKeySet);
                   publicSelectedKeysField.set(unwrappedSelector, selectedKeySet);
                   return null;
            }
        });
        selectedKeys = selectedKeySet;
    }

先是通過相應平臺的的Epoll實現,建立Selector物件,然後構造一個SelectedSelectionKeySet物件,這是Netty自己對SelectKeys的實現,然後通過反射將Selector物件中的selectedKeySet成員變數替換為自己的實現。此處應該全體起立喊666,哈哈!更絕的是Netty最新的4.x版本中加了一條:如果JDK版本大於等於9,連反射都不用了,直接通過CAS操作,通過成員變數的的偏移地址修改。有興趣可以把程式碼拉下來參觀參觀。

2.3 關聯EventLoop

前面說完了EventLoopGroup的例項化(包括EventLoop),也就是EventLoopGroup bossGroup = new NioEventLoopGroup(n)這行程式碼。接下來在group方法將BootStrap和EventLoopgroup關聯,下面主要分析channel關聯EventLoop。

channel關聯Eventloop有三種情況:客戶端SocketChannel關聯EventLoop、服務端ServerSocketChannel關聯EventLoop、由服務端ServerSocketChannel建立的SocketChannel關聯EventLoop。Netty厲害的就是把這三種情況都都能複用Multithread EventLoopGroup中的registe方法:

@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}

根據選擇策略找到可用的EventLoop,然後呼叫SingleThreadEventLoop中的register方法,最終呼叫了 AbstractChannel#AbstractUnsafe.register 後


public final void register(EventLoop eventLoop, final ChannelPromise promise) {
    // 刪除條件檢查.
    ...
    AbstractChannel.this.eventLoop = eventLoop;
    if (eventLoop.inEventLoop()) {
        register0(promise);
    } else {
        try {
            eventLoop.execute(new OneTimeTask() {
                @Override
                public void run() {
                    register0(promise);
                }
            });
        } catch (Throwable t) {
            ...
        }
    }
}

將一個 EventLoop 賦值給 AbstractChannel 內部的 eventLoop 欄位, 到這裡就完成了 EventLoop 與 Channel 的關聯過程。
但是上面程式碼一直是bind過程,也及時說在main執行緒中執行,所以會跳入else分支。將註冊操作包裝為一個Runnable,提交給eventloop的execute方法,該方法實現是在SingleThreadEventExecutor中實現的。

總結一下NioEventLoop,NioEventLoop是對IO 操作和執行緒的整合,那麼什麼時候啟動這個執行緒?答案是註冊Channel(向selector註冊)的時候。這是Server端的第一步,因此在這裡啟動執行緒比較合適。
Netty

下面分析NoioEventLoop的任務處理機制。

3. NioEventLoop的任務處理機制

一個 NioEventLoop 通常需要肩負起兩種任務, 第一個是作為 IO 任務, 處理 IO 操作,如accept、connect、read、write等。第二個就是非IO任務, 處理 taskQueue 中的任務,,如register0、bind0等任務。

Java NIO流程

回顧一下NIO中Selector的使用流程:

  1. 通過 Selector.open() 開啟一個 Selector.

  2. 將 Channel 註冊到 Selector 中, 並設定需要監聽的事件(interest set)

  3. 不斷重複:

    3.1 呼叫 select() 方法

    3.2 呼叫 selector.selectedKeys() 獲取 selected keys

    3.3迭代每個 selected key:

    3.4 從 selected key 中獲取 對應的 Channel 和附加資訊(如果有的話)

    3.5 判斷是哪些 IO 事件已經就緒了, 然後處理它們. 如果是 OP_ACCEPT 事件, 則呼叫 “SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()” 獲取 SocketChannel, 並將它設定為 非阻塞的, 然後將這個 Channel 註冊到 Selector 中.

    3.6 根據需要更改 selected key 的監聽事件.

    3.7將已經處理過的 key 從 selected keys 集合中刪除

3.1 register任務

第一步開啟Selector,在例項化NioEventLoop時已經初始化完成,也儲存至NioEventLoop的屬性selector中。將channel註冊到selector中通過上面register0(promise)來完成,回顧一下注冊的呼叫鏈:

Bootstrap.initAndRegister -> 
    AbstractBootstrap.initAndRegister -> 
        MultithreadEventLoopGroup.register -> 
            SingleThreadEventLoop.register -> 
                AbstractUnsafe.register -> //交給EventLoop執行
                	AbstractUnsafe.register0 -> 
                        AbstractNioChannel.doRegister

register0 又呼叫了 AbstractNioChannel.doRegister:

  @Override
protected void doRegister() throws Exception {
    selectionKey = javaChannel().register(eventLoop().unwrappedSelector(), 0, this);
    return;
}

在這裡 javaChannel() 返回的是一個 Java NIO SocketChannel 物件, 我們將此 SocketChannel 註冊到 Selector 中。第二個是感興趣的事件,0表示對所有事件都不感興趣。這裡只是將Channel註冊到Selecto。設定感興趣的事件在ChannelActive事件中實現,具體的說是在HeadContet中實現的。具體邏輯請看https://blog.csdn.net/TheLudlows/article/details/82712942 。第三個引數this,是attch物件。相當於把當前的NioServerScoketChannel(Netty對JDK原生ServerSocketChannel的包裝)放進去。這個會在後面的IO處理中用到。

3.2 啟動執行緒,新增任務

在3.2節中所講的只是channel註冊任務,netty會把此任務提交給任務佇列,通過execute方法去啟動執行緒。同時,selector的操作流程只完成了兩步,帶著一個問題:還有最後一步迴圈呼叫select方法在哪完成的呢?execute方法實現了Java併發包Executor的介面方法,用來執行任務。

 @Override
    public void execute(Runnable task) {
		//此處為false,因為還在main執行緒中
        boolean inEventLoop = inEventLoop();
        if (inEventLoop) {
            addTask(task);
        } else {
            startThread();
            addTask(task);
            if (isShutdown() && removeTask(task)) {
                reject();
            }
        }
        if (!addTaskWakesUp && wakesUpForTask(task)) {
            wakeup(inEventLoop);
        }
    }

如果當前執行的執行緒不是所繫結的執行緒則呼叫startThread方法為本EventExecutor繫結支撐執行緒,然後嘗試啟動執行緒(但由於執行緒是單個的,因此只能啟動一次),隨後再將任務新增到佇列中去。如果執行緒已經停止,並且刪除任務失敗,則執行拒絕策略,預設是丟擲異常。

private void startThread() {
    if (state == ST_NOT_STARTED) {
        if (STATE_UPDATER.compareAndSet(this, ST_NOT_STARTED, ST_STARTED)) {
            try {
                doStartThread();
            } catch (Throwable cause) {
                STATE_UPDATER.set(this, ST_NOT_STARTED);
                PlatformDependent.throwException(cause);
            }
        }
    }
}

private void doStartThread() {
assert thread == null;
executor.execute(new Runnable() {
    @Override
    public void run() {
        thread = Thread.currentThread();
        if (interrupted) {
            thread.interrupt();
        }
        boolean success = false;
        updateLastExecutionTime();
        SingleThreadEventExecutor.this.run();
        success = true;
		//省略部分程式碼
	}
}

如果EventExecutor的狀態為ST_NOT_STARTED,那麼先修改狀態然後呼叫doStartThread方法為本EventExecutor繫結執行緒,斷言指出此時thread必須為null,表明當前EventExecutor還未繫結任何執行緒。繫結任務交由執行緒池排程執行,執行緒池中執行該任務的執行緒被繫結到EventExecutor上。然後設定最後一次的執行時間。執行當前 NioEventLoop 的 run 方法,注意:這個方法是個死迴圈,是整個 EventLoop 的核心。

@Override
    protected void run() {
        for (;;) {
            try {
                switch (selectStrategy.calculateStrategy(selectNowSupplier, hasTasks()
            
           

相關推薦

NettyNetty核心NioEventLoop原理解析

Netty提供了Java NIO Reactor模型的實現,之前寫過一篇文章是對三種Reactor模式的簡單實現:Reactor模型的Java NIO實現,當然netty中的實現要複雜的多。並且Netty將實現好的Reactor模型封裝起來,我們只需提供適當的

NettyFutrue&Promise原始碼解析

文章目錄 1. Future&Promise 2. AbstractFuture 3.Completefuture 4.Channelfuture&Completechannel

NettyPilpeline原理分析

在上篇文章中提到每個SocketCahnnel或者ServerSocketChannel的父類AbstractChannel的建構函式中會例項化DefaultChannelPipeline。在本文中會詳細的介紹ChannelPiple例項化過程中的細節、以及Ch

Netty客戶端連線原始碼分析

1. BootStrap啟動程式碼 客戶端方面的程式碼開始 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.gr

Netty write流程全解析

1. ChannelOutboundBuffer 1.1 ChannelOutboundBuffer概述 在分析write前,有必要介紹一下ChannelOutboundBuffer,顧名思義該類是用來快取寫向Socket的資料。每個 ChannelSocke

Netty淺析Read事件

上篇文章講述了Accept事件的處理過程,本文將詳細分析Read過程中的細節。按照accept事件的思路,當讀事件進來的時候,會呼叫 unsafe 的 read 方法,這個方法的主要作用是讀取 Socket 緩衝區的記憶體,幷包裝成 Netty 的 ByteB

Trie樹Double-Array Trie原理及狀態轉移過程詳解

前言:  Trie樹本身就是一個很迷人的資料結構,何況是其改進的方案。  在本部落格中我會從DAT(Double-Array Tire)的原理開始,並結合其原始碼對DAT的狀態轉移過程進行解析。如果因此

薦書 | Netty之路跟著案例學Netty

內容精選自 1000 多個一線業務實際案例真正從原理到實踐全景式講解 Netty 專案實踐快速領

Docker映象瞭解其背後的技術原理

什麼是 docker 映象 docker 映象是一個只讀的 docker 容器模板,含有啟動 docker 容器

Android六、在子線程中直接使用 Toast 及其原理

我的文章 public str toast 技術交流群 ui控件 及其 handle android進階 一般我們都把Toast當做一個UI控件在主線程顯示。但是有時候非想在子線程中顯示Toast,就會使用Handler切換到主線程顯示。 但是子線程中真的不能直接顯示Toa

Netty基礎篇之流概念篇(1)

序言:學Netty之前,對流的概念應該深刻一些,所以先理解一下流的使用! 1、IO、NIO、AIO 含義區別 1.1 同步

Netty基礎篇之Buffer篇(2)

1、Buffer概念 1.1 緩衝區獲取 Buffer緩衝區是就是一個數組,有著不同的資料型別:ByteBuffer、Ch

Netty基礎篇之NIO Channel篇(4)

1、Channel概念 通道(Channel):用於源節點與目標節點的連線。在 Java NIO 中負責緩衝區中資料的傳輸。C

Netty基礎篇之NIO 非阻塞通訊(6)

1、Tcp網路非阻塞通訊 //客戶端 @Test public void client() throws IOExcept

Netty篇之簡單版websocket發訊息(7)

序言:簡單開發前端傳送訊息,後端接收訊息並返回。 1、故事牽引 今天通過一個故事來講解netty,主要講client和ser

Netty篇之websocket傳送訊息(8)

序言:Netty進階篇之簡單版websocket發訊息(7) 大概和下面的程式碼就成相似,如果不懂,可以看一下這篇部落格

【學習筆記】StringStringBuffer類(線程安全)和StringBuilder類

n) static this util double 字符串 對象 ice 單線程 一、除了使用String類存儲字符串之外,還可以使用StringBuffer類存儲字符串。而且它是比String類更高效的存儲字符串的一種引用數據類型。 優點:   對字符串進行連接操作時,

python開發函數命名空間,作用域,函數的本質,閉包,內置方法(globales)

問題 總結 加載 自己的 ger 作用域 範圍 沒有 概念 一,命名空間 #局部命名空間#全局命名空間#內置命名空間 #三者的順序#加載順序 硬盤上——內存裏#內置-->全局(從上到下順序加載進來的)-->局部(調用的時候加載) 1 #!/usr/bin/

python開發函數裝飾器

for 中國 eas login please 函數 功能 log 原則 一,裝飾器本質 閉包函數 功能:就是在不改變原函數調用方式的情況下,在這個函數前後加上擴展功能 二,設計模式 開放封閉原則 *對擴展是開放的 *對修改是封閉的 三,代碼解釋 1 #!/

[您有新的未分配科技點]博弈論似乎不那麽恐懼了…… (SJ定理,簡單的基礎模型)

裏的 如果 cnblogs 經典 ant 控制 nim osi 取石子 這次,我們來繼續學習博弈論的知識。今天我們會學習更多的基礎模型,以及SJ定理的應用。 首先,我們來看博弈論在DAG上的應用。首先來看一個小例子:在一個有向無環圖中,有一個棋子從某一個點開始一直向它的出點