10.1 IO模型 10.2 Netty原理源碼 10.3 Netty應用
10.1 IO模型
Reactor設計模式
所有IO操作都由NIO單線程完成,然後通過這個線程分配handler處理請求。
這樣不會每有新的請求就會創建新的線程。有的線程存活但空閒,不給這些任務分配線程也節約了IO線程空間
10.2 Netty原理源碼
一 原理
Netty主要基於主從Reactor多線程模型
Netty執行流程圖
主從線程池具體介紹
1 Netty 抽象2組線程池
2 BossGroup 負責接收客戶端連接
WorkerGroup 負責網絡讀寫
3 兩者都是NioEventloopGroup類型
4 NioEventloopGroup可有多線程,相當於一個事件循環組,組中多個事件不斷循環,每個事件循環都是一個NioEventloop
5 NioEventloop:不斷循環執行處理任務的線程,每個NioEventloop有一個select用於監聽綁定在其上的socket網絡通訊
6 每個Boss NioEventloopGroup循環執行步驟
1 輪詢accept事件
2 處理accept與client連接,生成NioSocketChannel並將其註冊到某個worker NioEventloop上的selector
3 處理任務隊列任務,即runAllTasks
7 每個Worker NioEventloopGroup循環執行步驟
1 輪詢讀寫事件
2 處理I/O事件(即讀寫事件),在對應的NioSocketChannel處理
3 runAllTasks
8 每個worker NioEventloop處理業務時會使用pipeline管道,理論上channel包含channelpipeline,每個pipeline包含多個ChannelHandlerContext,每個ChannelHandler封裝一個ChannelHandler。每個pipeline也會包含即調出所在channel
bossGroup和WorkerGroup含有的子線程數默認cpu核數×2
子線程輪詢執行
channel和pipeline互相包含
ctx包含了channel和pipeline
Reactor中任務隊列(TaskQueue) runAllTasks
當有一個非常消耗時間的業務
會異步執行(先返回一個結果但IO還沒完成),別的IO處理完再從taskQueue中拿task最後處理)
提交該channel先到對應NioEventloop的taskQueue中
異步模型
ChannelFuture是一個接口 extend Future
可添加監聽器,當監聽事件發生時觸發
異步高併發更穩定更高吞吐量
ChannelFuture cf = bootstrap.bind(6668).syn();
cf.addListener(new ChannelFutureListener(){
@Override
~ operationComplete(ChannelFuture future){
if(cf.isSuccess()){
System.out.println("監聽成功");
}else{
System.out.println("監聽失敗");
}
}
})
Netty核心組件
Bootstrap,ServerBootstrap
配置整個Netty程序,串聯各個組件
bootstrap.handler(xxx) //對應BossGroup
bootstrap.childHandler(xxx) //對應workerGroup
Future, ChannelFuture
返回異步對象
Channel
執行網絡I/O操作
可通過Channel獲得當前網絡連接通道狀態,網絡連接配置參數,提供異步操作
不同協議,不同阻塞提供不同類型的Channel
Selector
1 Netty基於selector實現I/O多路複用,一個selector線程可監聽多個連接的channel事件
2 當向selector中註冊channel,selector可不斷自動查詢這些註冊的channel是否有就緒狀態
ChannelHandler及其實現類
ChannelHandler一個接口, 處理或攔截I/O事件,並將其轉發到其ChannelPipeline中下一個
ChannelPipeline
提供了ChannelHandler鏈的容器,如client爲例,事件從client->server,稱該事件爲出站。即client發送給server的數據會通過pipeline中一系列ChannelOutBoundHandler處理,反之爲入站
是一個雙向鏈表,入站事件從head往tail傳,出站tail往head傳,兩種類型handler互不干擾
channelPipeline實現了一種高級形式攔截過濾器模式,使用戶可控制事件處理方式,及Channel中各個handler如何互相交互
ChannelHandlerContext
保存channel相關所有信息,同時關聯一個channelHandler對象,即context中包含了具體事件處理器channelHandler也有對應的pipeline和channel信息,方便對channelHandler調用
ChannelOption
創建channel實例後設置channelOption參數,如初始化服務器可用隊列大小,心跳保持連接等
EventLoopGroup
1 一組Eventloop爲更好利用多核CPU資源,一般會有多個EventLoop同時工作,一個EventLoop維護一個selector
2 提供next接口,按規則拿其中Eventloop處理任務,如BossEventloop和WorkerEventloop
3 通常一個服務器端口即一個ServerSocketChannel對應一個selector和一個eventloop線程。BossEventLoop接收client連接並將socketChannel交給workerEventLoop處理IO
Unpooled類
用來操作緩衝區工具類(Netty數據容器),不用flip反轉,底層維護了readerIndex,writerIndex
Netty零拷貝
heapByteBuffer是JVM heap上分配的Byte緩衝區,簡單看成對這個byte緩衝區的直接byte[]數組封裝,不需要拷貝直接引用封裝寫到DirectByteBuffer(機器內存上實現零拷貝)
二 基本源碼
看完應用,再回來看源碼
Netty對象池源碼
核心組件源碼
1 啓動類 main方法中首先創建SSL配置類
2 創建兩個EventLoopGroup對線
1 bossGroup 接受TCP請求,然後交給workerGroup通信
2 EventLoopGroup 包含多個Eventlooop,可註冊channel,事件循環中選擇
3 主 new NioEventloop(1) 1表示有1個線程可指定
從 new NioEventloop() 默認 cpu*2數量的線程
會創建EventExcutor數組 children = new EventExecutor[nThread];
每個元素類型爲NioEventloop,NioEventloop實現了EventLoop和Executor接口
EventLoop接口
1 繼承了ScheduledExecutorService接口,可接受定時任務
2 繼承了SingleThreadEventExecutor接口,所以是單線程線程池
3 該接口一旦被channel註冊,就處理該channel對應所有IO操作
4 它是單線程線程池,一個死循環線程反覆不斷做3件事情
監聽端口:默認selector調用CAS阻塞1秒監聽,
處理端口:有任務則喚醒selector調用processSelectKeys處理key
處理隊列事件:再之後runAllTasks
5 一個Eventloop可以綁定多個channel,每個channel只能由一個Eventloop處理
children數組下
1 實例化單例線程池數組(CPU*2數量)
2 根據線程選擇工廠創建一個線程選擇器selector
3 每個單例線程池加一個關閉監聽器
4 將所有單例線程池加入LinkedHashSet(children)
3 try塊創建ServerBootstrap對象,引導類,用於啓動服務器和引導整個程序初始化。即調用group方法將兩個主從group加入自己字段
4 添加一個channel(服務器自身channel),參數是clas對象,引導類通過這個class反射創建ChannelFactory。再添加一些TCP參數(改channel在bind方法真正創建)
5 加服務器日誌handler
6 加SocketChannel的handler配置主從handler
7 綁定端口並阻塞直到連接成功
bind底層兩個方法
initAndRegister 簡而言之就是初始化所有channel,handler,pipeline,context
dobind 則是select輪詢事件,處理IO,處理任務隊列
8 mian線程阻塞等待關閉
9 finally塊中代碼在服務器關閉時關閉所有資源
10.3 Netty應用
1 Netty服務端客戶端簡單通訊
服務端
//創建主從線程
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//創建服務器啓動對象,配置參數
try{
ServerBootstrap bootstrap = new ~;
//NioServerSocketChannel.class服務器通道反射實現
//ChannelOption.SO_BACKLOG,128線程隊列連接個數
//ChannelOption.SO_KEEPALIVE,true從線程池都保持心跳機制
//new ChannelInitializer<SocketChannel>()給從線程池的eventloop對應管道設置處理器handler
bootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG,128).childOption(ChannelOption.SO_KEEPALIVE,true).childHandler(new ChannelInitializer<SocketChannel>(){
@Override
~ initChannel(SocketChannel ch){
ch.pipeline.addLast(new NettyServerHandler());//添加自己的實現類
}
})
//服務器ready了
//綁定一個端口同步,生成一個ChannelFuture異步對線,啓動server
ChannelFuture cf = bootstrap.bind(6668).sync();
//對關閉的通道監聽,監聽到關閉事件才關閉
cf.channel().closeFuture().sync();
}catch{
xxx
}finally{
xxx
}
//自定義一個handler需要繼承netty規定好的適配器
public class NettyServerHandler extends ChannelInboundHanderAdapter{
//client data實現
//當通道有讀取事件會觸發
//ctx上下文對象包含pipeline和channel,msg是client發送的數據
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
//講msg數據轉成ByteBuf(Netty提供的buffer)
ByteBuffer buf = (ByteBuf) msg;
System.out.println(buf.toString(CharsetUtil.UTF_8))//顯示發送的數據
System...("客服端地址: "+ctx.channel().remoteAddress());
}
//數據讀取完觸發
@Override
public void channelReadComplete(ChannelHandlerContext ctx){
//講數據寫入緩存並刷新
ctx.writeAndFlush(Unpooled.copiedBuffer("HelloClient",~UTF_8));
}
//異常處理,一般是關閉通道
~ exceptionCaught(ChannelHandlerContext ctx,~){
ctx.channel().close(); or ctx.close();
}
}
客戶端
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//創建服務器啓動對象,配置參數
try{
Bootstrap bootstrap = new ~;
bootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).handler(new ChannelInitializer<SocketChannel>(){
@Override
~ initChannel(SocketChannel ch){
ch.pipeline.addLast(new NettyClientHandler());
}
})
//啓動client連接server
ChannelFuture cf = bootstrap.connect("127.0.0.1",6668).sync();
//對關閉的通道監聽,監聽到關閉事件才關閉
cf.channel().closeFuture().sync();
}catch{
xxx
}finally{
xxx
}
public class NettyClientHandler extends ChannelInboundHanderAdapter{
@Override
public void channelActive(ChannelHandlerContext ctx){
ctx.writeAndFlush(Unpooled.copiedBuffer("HelloServer",~UTF-8));
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
//讀取服務器消息
ByteBuffer buf = (ByteBuf) msg;
System.out.println(buf.toString(CharsetUtil.UTF_8))//顯示發送的數據
System...("服務端地址: "+ctx.channel().remoteAddress());
}
~ exceptionCaught(ChannelHandlerContext ctx,~){
ctx.channel().close(); or ctx.close();
}
}
2 編解碼器
Netty的StringEncoder(String) 和 ObjectEncoder(Java對象) 都使用java序列化技術
體積太大,二進制編碼5倍多。無法跨語言。序列化性能低
解決方案
TCP+谷歌的 protobuf 效率更高
3 TCP粘包拆包
解決方案:自定義協議+編解碼器
Client端
pipeline.addLast(new MyMessageEncoder());
pipeline.addLast(new MyClientHandler());
Server端
pipeline.addLast(new MyMessageDncoder());
pipeline.addLast(new MyServerHandler());
客戶端
public class MyClientHandler extends SimpleChannelInboundHandler<MessageProtocol>{
public void channelActive(ChannelHandlerContext ctx){
//自定義協議
MessageProtocol protocol = new ~() //自己定義的類,規定交互數據的長度內容
protocol.setLen(xxx);
protocol.setContent(xxx);
ctx.writeAndFlush(protocol);
}
}
//客戶端編碼器
public class MyMessageEncoder extends MessageToByteEncoder<MessageProtocol>{
@Override
~ encode(ChannelHandlerContext ctx,MessageProtocol msg,ByteBuf out){
out.writeInt(msg.getLen());
out.writeBytes(msg.getContent());
}
}
服務端
public class MyMessageDecoder extends ReplayingDecoder<void>{
@Override
~ decode(ChannelHandlerContext ctx, ByteBuf in,List<Object> out){
//講二進制字節碼轉成我們需要的MessageProtocol
int len = in.readInt();
byte[] content = new ~;
in.readBytes(content);
MessageProtocol protocol = new ~;
protocol.setLen(len);
protocol.setContent(content);
out.add(protocol);
}
}
public class MyServerHandler ~{
@Override
~ channelRead0(ChannelHandlerContext ctx, MessageProtocol msg){
//拿到數據進行業務處理
}
}
Netty耗時操作的解決
普通線程池仍同一個線程,會阻塞,所以需要異步
handler中新建一個線程池變量來處理