@[TOC]
介紹
- Netty是由JBOSS提供的一個Java開源框架孵延,現(xiàn)為Github獨立項目。
- Netty是一個異步的状共、基于事件驅(qū)動的網(wǎng)絡應用框架费彼,用以快速開發(fā)高性能,高可用的網(wǎng)絡IO程序口芍。
- Netty主要針對TCP協(xié)議下箍铲,面向Client端高并發(fā)應用,或者Peer-to-Peer場景下大量數(shù)據(jù)持續(xù)傳輸?shù)膽谩?/li>
- Netty本質(zhì)是一個NIO框架鬓椭,適用于服務器通訊相關的多種應用場景
官網(wǎng)介紹 : [ Netty是一個NIO客戶端服務器框架颠猴,可以快速輕松地開發(fā)網(wǎng)絡應用程序,例如協(xié)議服務器和客戶端小染。它極大地簡化和簡化了諸如TCP和UDP套接字服務器之類的網(wǎng)絡編程翘瓮。]
- 更高的吞吐量,更低的延遲
- 減少資源消耗
- 減少不必要的內(nèi)存復制
應用場景
- 分布式系統(tǒng)中的各個節(jié)點之間服務遠程調(diào)用裤翩,RPC框架必不可少资盅,所以Netty做為異步高性能的通信框架,做為基礎通信組件被RPC框架使用
- 阿里的分布式框架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編程簡單流程:
- 服務端啟動一個ServerSocket
- 客戶端啟動socket對服務器進行通信物臂,默認情況下服務器端需要對每個客戶建立一個線程與之通訊
- 客戶端發(fā)出請求后旺拉,先咨詢服務器是否有線程響應,如果沒有則會等待棵磷,或者被拒絕
- 如果有響應蛾狗,客戶端線程會等待請求結(jié)束后,在繼續(xù)執(zhí)行
案例 :
- 使用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 測試
缺點:
- 每個請求都需要創(chuàng)建獨立都線程算吩,與對應都客戶端進行數(shù)據(jù)Read, 業(yè)務處理, 數(shù)據(jù) Write .
- 當并發(fā)數(shù)較大時留凭,需要創(chuàng)建大量線程來處理連接 ,系統(tǒng)資源占有較大偎巢。
- 連接建立后冰抢,如果當前線程暫時沒有數(shù)據(jù)可讀,則線程阻塞在Read操作上艘狭,造成資源浪費挎扰。
NIO
- Java NIO 全稱 Java non-blocking IO, 是指JDK提供新當API翠订,從JDK1.4開始,Java 提供了一系列改進當輸入/輸出的新特性遵倦,被統(tǒng)稱為 NIO 尽超,是異步非阻塞的
- NIO 相關類都被放在 Java.nio 包及子包下,并且對原 Java.io 包中很多類進行改寫梧躺。
- NIO 有三大核心部分 : Channel (通道)似谁,Buffer(緩存區(qū)),Selector(選擇器)
- 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 :
- Java的通道類似于流玉掸,但是有些區(qū)別刃麸,通道可以同時進行讀寫,而流只能讀或者寫司浪,也可以實現(xiàn)異步讀寫數(shù)據(jù)泊业,也可以從緩存區(qū)讀數(shù)據(jù)把沼,寫入到緩存區(qū)數(shù)據(jù)
- Channel 是一個 NIO 的接口
- 常用的 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 :
- Java 的 NIO,非阻塞的iO方式/可以用一個線程篮奄,處理多個的客戶端連接则吟,就會使用到selector(選擇器), 是SelecttableChannle 對象的多路復用器
- Selector 能夠檢測多個注冊的通道上是否有事情發(fā)生(注意: 多個Channel以事件的方式可以注冊到同一個Selector)颂碧,如果有事情發(fā)生狞玛,便獲取事件然后針對每個事件進行相應的處理未妹。這樣就可以只用一個單線程區(qū)管理多個通道,也就是管理多個連接和請求夸赫。
- 只有連接真正有讀寫事件發(fā)生時菩帝,才會進行讀寫,就大大地減少了系統(tǒng)開銷憔足,并且不必為每個連接都創(chuàng)建一個線程胁附,不用區(qū)維護多個線程
- 避免了多線程之間都上下文切換導致的開銷
服務端
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();
}
}
原理:
- 當客戶端連接時,會通過ServerSocketChannel得到SocketChannel
- Selector繼續(xù)監(jiān)聽郎哭,select方法 返回有事件發(fā)送的通道的個數(shù)
- 將socketChannel 注冊到Selector上register (Selector sel,int ops),一個selector可以注冊多個SocketChannel
- 注冊后返回一個SelectionKey和Selector 關聯(lián)
- 進一步得到各個SelectionKey, 通過SelectionKey 反向獲取 SocketChannel , 方法Channel() 他匪,通過 channel 寫業(yè)務代碼
-
SelectionKey : 表示SelectableChannel 和 Selector 之間的注冊關系,每次向選擇器注冊通道時就會選擇一個事件(選擇鍵)夸研。選擇鍵包含2個表示為整數(shù)值的操作集邦蜜。操作集的每一位都表示該鍵的通道鎖支持的一類可選操作
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
NIO與零拷貝
介紹:
- 零拷貝是網(wǎng)絡編程中的關鍵,很多性能優(yōu)化都離不開
- 在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ū)別
- mmap適合小數(shù)據(jù)量讀寫亲铡,sendFile適合大文件傳輸
- mmap需要4次上下文切換才写,3次數(shù)據(jù)拷貝,sendFile 需要3次上下文切換奖蔓,最少2次數(shù)據(jù)拷貝
- 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
- JDK1.7 引入 Asynchronous I/O, 即AIO 吆鹤,在進行I/O 編程中厨疙,常用到兩種哦是:Reactor 和 Proactor. Java 的 NIO 就是 Reactor , 當有事件觸發(fā)時,服務端得到通知疑务,進行相應的處理
- AIO 即 NIO 模式2.0 沾凄,叫做異步不阻塞 的 IO。AIO 引入異步通道的概念知允,采用了Proactor模式撒蟀,簡化了程序編寫,有效的請求才啟動線程温鸽,它的特點是由操作系統(tǒng)完成后才通知服務端程序啟動去處理線程保屯,一般用于連接數(shù)較多且連接事件較長的應用
- 目前AIO還沒有廣泛流行
NIO vs BIO
- BIO以流的方式處理數(shù)據(jù),而 NIO 以塊的方式處理數(shù)據(jù)涤垫,塊 I/O的效率比流 I/O 高很多
- BIO是阻塞的姑尺,NIO是非阻塞的
- 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)絡阻塞等
線程模型基本介紹:
- 不同的線程模型,對程序?qū)π阅苡泻艽笥绊?/li>
- 目前存在對線程模型有 : 傳統(tǒng)阻塞I/O服務模型 、 Reactor模式
- 根據(jù)Reactor 的數(shù)量和處理資源池線程的數(shù)量不同茵瘾,有3種典型的實現(xiàn)
- 單Reactor 單線程
- 單Reactor 多線程
- 主從Reactor 多線程 - Netty線程模型(Netty主要基于主從 Reactor 多線程模型做了一定的改進礼华,其中中從 Reactor 多線程模型有多個 Reactor)
Reactor 模式 :
- 基于I/O 復用模型,多個連接公用一個阻塞對象拗秘,應用程序只需要在一個阻塞對象等待圣絮,無需等待所有連接,當某個連接有新的數(shù)據(jù)可以處理時雕旨,操作系統(tǒng)通知應用程序扮匠,線程從阻塞狀態(tài)返回,開始業(yè)務處理
- 基于線程是復用線程資源凡涩,不必再為每個連接而創(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)點:
- 響應快,不必為單個同步時間所阻塞甫恩,雖然 Reactor 本身依然是同步都
- 可以最大程度都避免復雜多線程及同步問題逆济,并且避免來多線程/進程都切換開銷
- 擴展性好,可以方便都通過增加 Reactor 實例來充分利用CPU資源
- 復用性好磺箕,Reactor 模型本身與集體事件處理邏輯無關奖慌,具有很高的復用性
個人博客地址:http://blog.yanxiaolong.cn/