-
源代码: github.com/lightningMa…html
-
NIO与OIO的区别java
- 解决线程资源有限
- 使用selector管理多个链接,减小建立线程的开销。当有新链接进来时,将它注册到一个selector上,批量监控是否有可读的数据链接
- 解决线程切换效率低下
- 因为NIO模型中线程数量大大下降,线程切换的开销也变低了
- 解决IO读写面向流
- IO读写是面向流,意味着读完以后流没法再次读取,须要本身缓存数据。而NIO是面向Buffer的,能够随意读取Buffer中的任一字节,不须要本身缓存,只需移动指针。
-
NIO编程核心思想react
- NIO 模型中一般会有两个线程,每一个线程绑定一个轮询器 selector ,例如 serverSelector负责轮询是否有新的链接,clientSelector负责轮询链接是否有数据可读
- 服务端监测到新的链接以后,再也不建立一个新的线程,而是直接将新链接绑定到clientSelector上,这样就不用 IO 模型中 1w 个 while 循环在死等
- clientSelector被一个 while 死循环包裹着,若是在某一时刻有多条链接有数据可读,那么经过clientSelector.select(1)方法能够轮询出来,进而批量处理
- 数据的读写面向 Buffer
-
JAVA原始NIO相比Netty的缺点nginx
- JDK 的 NIO 编程模型不友好,ByteBuffer 的 Api 简直反人类
- 对 NIO 编程来讲,连简单的自定义协议拆包都要你本身实现
- JDK 的 NIO 底层由 epoll 实现,该实现饱受诟病的空轮询 bug 会致使 cpu 飙升 100%
- 项目庞大以后,自行实现的 NIO 很容易出现各种 bug,维护成本较高
-
服务端的启动git
- ServerBootstrap
- NioEventLoopGroup -> boss/worker
- .channel -> NioServerSocketChannel
- .group
- .handler
- .childHandler
- ChannelInitializer
- childOption
- .option
- .bind
-
客户端的启动github
- Bootstrap
- channel -> NioSocketChannel
- option
- handler
- attr
- pipeline -> SocketChannel.pipeline
-
channelHandlerweb
- ChannelInboundHandlerAdapter 主要用于实现其接口 ChannelInboundHandler的全部方法,这样咱们只须要重写咱们感兴趣的方法,不用实现handler中的每个方法
- ChannelOutboundHandlerAdapter 主要用于实现 ChannelOutboundHandler的全部方法
-
pipelineredis
- ByteToMessageDecoder 实现自定义解码 不用关心ByteBuf的强转和解码结构的传递
- MessageToByteEncoder 实现自定义编码 不用关系ByteBuf的建立,不用每次向对端写Java对象都进行一次编码
- SimpleChannelInboundHandler 实现每一种指令的处理,将类型转换操做交给编解码处理器,必须实现channelRead0来完成request/response的处理
-
粘包拆包编程
- Netty 自带的拆包器
- FixedLengthFrameDecoder 固定长度的拆包器
- 若是你的应用层协议很是简单,每一个数据包的长度都是固定的,好比 100,那么只须要把这个拆包器加到 pipeline 中,Netty 会把一个个长度为 100 的数据包 (ByteBuf) 传递到下一个 channelHandler。
- LineBasedFrameDecoder 行拆包器 - 从字面意思来看,发送端发送数据包的时候,每一个数据包之间以换行符做为分隔,接收端经过 LineBasedFrameDecoder 将粘过的 ByteBuf 拆分红一个个完整的应用层数据包。
- DelimiterBasedFrameDecoder 分隔符拆包器 - DelimiterBasedFrameDecoder 是行拆包器的通用版本,只不过咱们能够自定义分隔符。
- LengthFieldBasedFrameDecoder 基于长度域拆包器 《数据长度》 - 最后一种拆包器是最通用的一种拆包器,只要你的自定义协议中包含长度域字段,都可以使用这个拆包器来实现应用层拆包。
- LengthFieldBasedFrameDecoder 长度域
- new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 7, 4); 第一个参数指的是数据包的最大长度,第二个参数指的是长度域的偏移量,第三个参数指的是长度域的长度
- 须要在 pipeline 的最前面加上这个拆包器
- 拒绝非本协议链接
- 解码器解码时判断协议标识,若是非本协议,则拒绝处理该报文
-
channelHandler lifeCycle缓存
- 开启链接: handlerAdded -> channelRegister -> channelActive -> channelRead -> channelReadComplete
- handlerAdded 指的是当检测到新链接以后,调用 ch.pipeline().addLast(new LifeCyCleTestHandler()); 以后的回调,表示在当前的 channel 中,已经成功添加了一个 handler 处理器。
- channelRegistered
- 这个回调方法,表示当前的 channel 的全部的逻辑处理已经和某个 NIO 线程创建了绑定关系,相似 socket.accept 到新的链接,而后建立一个线程来处理这条链接的读写,只不过 Netty 里面是使用了线程池的方式, 只须要从线程池里面去抓一个线程绑定在这个 channel 上便可。
- channelActive 当 channel 的全部的业务逻辑链准备完毕(也就是说 channel 的 pipeline 中已经添加完全部的 handler)以及绑定好一个 NIO 线程以后,这条链接算是真正激活了,接下来就会回调到此方法。
- channelRead
- channelReadComplete
- 关闭链接: channelInactive -> channelUnregistered -> handlerRemoved
- channelInactive 表面这条链接已经被关闭了,这条链接在 TCP 层面已经再也不是 ESTABLISH 状态了
- channelUnregistered 既然链接已经被关闭,那么与这条链接绑定的线程就不须要对这条链接负责了,这个回调就代表与这条链接对应的 NIO 线程移除掉对这条链接的处理
- handlerRemoved 咱们给这条链接上添加的全部的业务逻辑处理器都给移除掉
-
channelHandler lifeCycle应用举例
- ChannelInitializer 的实现原理
- 仔细翻看一下咱们的服务端启动代码,咱们在给新链接定义 handler 的时候,其实只是经过 childHandler() 方法给新链接设置了一个 handler,这个 handler 就是 ChannelInitializer, 而在 ChannelInitializer 的 initChannel() 方法里面,咱们经过拿到 channel 对应的 pipeline,而后往里面塞 handler
- ChannelInitializer 其实就利用了 Netty 的 handler 生命周期中 channelRegistered() 与 handlerAdded() 两个特性,咱们简单翻一翻 ChannelInitializer 这个类的源代码
- ChannelInitializer:initChannel、 handlerAdded、channelRegistered
- handlerAdded() 与 handlerRemoved() 这两个方法一般能够用在一些资源的申请和释放
- channelActive() 与 channelInActive()
- 对咱们的应用程序来讲,这两个方法代表的含义是 TCP 链接的创建与释放,一般咱们在这两个回调里面统计单机的链接数,channelActive() 被调用,链接数加一,channelInActive() 被调用,链接数减一
- 另外,咱们也能够在 channelActive() 方法中,实现对客户端链接 ip 黑白名单的过滤
- 统计用户链接数、流量
- channelRead
- channelReadComplete
- 优化 ctx.channel().flush() 批量刷新
-
处理优化
- 共享handler
- 对于没有成员变量的handler可使用单例模式,提升效率,避免建立不少小的对象
- @ChannelHandler.Sharable
- LoginRequestHandler is not a @Sharable handler, so can't be added or removed multiple times.
- 合并编解码器
- extends MessageToMessageCodec<ByteBuf,Packet>
- 编码器当outbound处理,解码器当作inbound处理便可
- 压缩 handler 合并平行 handler
- handler和handler之间无前后顺序时,能够放在一个map中,经过类型判断处理
- 更改事件传播源
- ctx.writeAndFlush() 替代 ctx.channel.writeAndFlush()
- 若是明确知道后面的操做只须要解码便可,就能够直接使用ctx.writeAndFlush
- 减小阻塞主线程的操做
- 统计处理时长
- 使用addListener监听channel信息 -> ChannelFuture
-
心跳和空闲检测
- 链接假死缘由
- 应用程序出现线程堵塞,没法进行数据的读写。
- 客户端或者服务端网络相关的设备出现故障,好比网卡,机房故障。
- 公网丢包
- 服务端空闲检测
- IdleStateHandler判断是不是链接假死,channelIdle方法处理假死的链接,通常是手动关闭
- 客户端定时发心跳
- heartbeat 定时发送
- 主要用于排除客户端确实没有数据传输的正常状况
- 服务端回复心跳与客户端空闲检测
-
netty-user-guide:netty.io/wiki/user-g…
-
netty-new-feature:netty.io/wiki/new-an…
-
netty-websocket-test-demo: github.com/netty/netty…
-
Netty源码分析
-
拓展问题
- 集群模式下多机器,netty 服务端如何工做?
- nginx负载均衡,redis存储channel关系和ip,服务器之间互相转发消息
- websocket-netty如何应用?
- netty-sockio redsion 如何使用?
- 使用netty实现消息推送,在生产环境须要处理那些常见问题?
- dubbo等RPC服务是如何使用netty的?