Netty 入门与实战:仿写微信 IM 即时通信系统学习笔记

Netty 入门与实战:仿写微信 IM 即时通信系统学习笔记

  • 源代码: 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
    • 减小阻塞主线程的操做
      • channelRead0 异步处理
    • 统计处理时长
      • 使用addListener监听channel信息 -> ChannelFuture
  • 心跳和空闲检测

    • 链接假死缘由
      • 应用程序出现线程堵塞,没法进行数据的读写。
      • 客户端或者服务端网络相关的设备出现故障,好比网卡,机房故障。
      • 公网丢包
    • 服务端空闲检测
      • IdleStateHandler判断是不是链接假死,channelIdle方法处理假死的链接,通常是手动关闭
    • 客户端定时发心跳
      • heartbeat 定时发送
      • 主要用于排除客户端确实没有数据传输的正常状况
    • 服务端回复心跳与客户端空闲检测
      • heartbeatHandler、空闲检测
  • 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的?

转载于:https://juejin.im/post/5ca09d236fb9a05e4d6c11d2