##

Netty实战 IM即时通讯系统(二)Netty简介

零、 目录

  1. IM系统简介
  • Netty 简介
  • Netty 环境配置
  • 服务端启动流程
  • 实战: 客户端和服务端双向通信
  • 数据传输载体ByteBuf介绍
  • 客户端与服务端通信协议编解码
  • 实现客户端登录
  • 实现客户端与服务端收发消息
  • pipeline与channelHandler
  • 构建客户端与服务端pipeline
  • 拆包粘包理论与解决方案
  • channelHandler的生命周期
  • 使用channelHandler的热插拔实现客户端身份校验
  • 客户端互聊原理与实现
  • 群聊的发起与通知
  • 群聊的成员管理(加入与退出,获取成员列表)
  • 群聊消息的收发及Netty性能优化
  • 心跳与空闲检测
  • 总结
  • 扩展

二、 Netty简介

  1. 回顾IO编程

    1. 场景: 客户端每隔两秒发送一个带有时间戳的“hello world”给服务端 , 服务端收到之后打印。

    2. 代码:

       IOServer.java/*** @author 闪电侠*/public class IOServer {public static void main(String[] args) throws Exception {ServerSocket serverSocket = new ServerSocket(8000);// (1) 接收新连接线程new Thread(() -> {while (true) {try {// (1) 阻塞方法获取新的连接Socket socket = serverSocket.accept();// (2) 每一个新的连接都创建一个线程,负责读取数据new Thread(() -> {try {int len;byte[] data = new byte[1024];InputStream inputStream = socket.getInputStream();// (3) 按字节流方式读取数据while ((len = inputStream.read(data)) != -1) {System.out.println(new String(data, 0, len));}} catch (IOException e) {}}).start();} catch (IOException e) {}}}).start();}}IOClient.java/*** @author 闪电侠*/public class IOClient {public static void main(String[] args) {new Thread(() -> {try {Socket socket = new Socket("127.0.0.1", 8000);while (true) {try {socket.getOutputStream().write((new Date() + ": hello world").getBytes());Thread.sleep(2000);} catch (Exception e) {}}} catch (IOException e) {}}).start();}}
      
    3. IO编程,模型在客户端较少的场景下运行良好 , 但是客户端比较多的业务来说 , 单机服务端可能需要支撑成千上万的连接, IO模型可能就不太合适了 , 原因:

      1. 在传统的IO模型中 , 每一个连接创建成功之后都需要一个线程来维护 , 每个线程包含一个while死循环, 那么1W个连接就对应1W个线程 , 继而1W个死循环。
      2. 线程资源受限: 线程是操作系统中非常宝贵的资源 , 同一时刻有大量的线程处于阻塞状态是非常严重的资源浪费,操作系统开销太大。
      3. 线程切换效率低下: 单机CPU核数固定 , 线程爆炸之后操作系统频繁的进行线程切换 , 应用性能几句下降
      4. IO编程中 , 数据读写是以字节流为单位。
    4. 为了解决这些问题 , JDK1.4之后提出了NIO

  2. NIO 编程
    1. NIO 是如何解决一下三个问题。

      1. 线程资源受限

        1. NIO编程模型中 , 新来一个连接不再创建一个新的线程, 而是可以把这条连接直接绑定在某个固定的线程 , 然后这条连接所有的读写都由这个线程来负责 , 那么他是怎么做到的?

          1. 如上图所示,IO 模型中,一个连接来了,会创建一个线程,对应一个 while 死循环,死循环的目的就是不断监测这条连接上是否有数据可以读,大多数情况下,1w 个连接里面同一时刻只有少量的连接有数据可读,因此,很多个 while 死循环都白白浪费掉了,因为读不出啥数据。
          2. 而在 NIO 模型中,他把这么多 while 死循环变成一个死循环,这个死循环由一个线程控制,那么他又是如何做到一个线程,一个 while 死循环就能监测1w个连接是否有数据可读的呢? 这就是 NIO 模型中 selector 的作用,一条连接来了之后,现在不创建一个 while 死循环去监听是否有数据可读了,而是直接把这条连接注册到 selector 上,然后,通过检查这个 selector,就可以批量监测出有数据可读的连接,进而读取数据,下面我再举个非常简单的生活中的例子说明 IO 与 NIO 的区别。
          3. 在一家幼儿园里,小朋友有上厕所的需求,小朋友都太小以至于你要问他要不要上厕所,他才会告诉你。幼儿园一共有 100 个小朋友,有两种方案可以解决小朋友上厕所的问题:
            1. 每个小朋友配一个老师。每个老师隔段时间询问小朋友是否要上厕所,如果要上,就领他去厕所,100 个小朋友就需要 100 个老师来询问,并且每个小朋友上厕所的时候都需要一个老师领着他去上,这就是IO模型,一个连接对应一个线程。
            2. 所有的小朋友都配同一个老师。这个老师隔段时间询问所有的小朋友是否有人要上厕所,然后每一时刻把所有要上厕所的小朋友批量领到厕所,这就是 NIO 模型,所有小朋友都注册到同一个老师,对应的就是所有的连接都注册到一个线程,然后批量轮询。
          4. 这就是 NIO 模型解决线程资源受限的方案,实际开发过程中,我们会开多个线程,每个线程都管理着一批连接,相对于 IO 模型中一个线程管理一条连接,消耗的线程资源大幅减少
      2. 线程切换效率低下
        1. 由于NIO模型中线程数量大大降低 , 线程切换的效率也因此大幅度提高
      3. IO读写面向流
        1. IO读写是面向流的 , 一次性只能从流中读取一个或多个字节 , 并且读完之后无法再次读取 , 你需要自己缓存数据 , 而NIO的读写是面向Buffer的 , 你可以随意读取里面的任何一个字节数据 , 不需要你自己缓存数据 , 这一切只需要移动读写指针即可。
    2. 原生NIO 实现

      /*** 服务端* */
      class NIO_server_test_01{public static void start () throws IOException {Selector serverSelect = Selector.open();Selector clientSelect = Selector.open();new Thread(() -> {try {ServerSocketChannel socketChannel = ServerSocketChannel.open();socketChannel.socket().bind(new InetSocketAddress(8000)); // 监听端口socketChannel.configureBlocking(false); // 是否阻塞socketChannel.register(serverSelect, SelectionKey.OP_ACCEPT);while ( true ) {// 检测是否有新的连接if(serverSelect.select(1) > 0) {  // 1 是超时时间     select 方法返回当前连接数量Set<SelectionKey> set = serverSelect.selectedKeys();set.stream().filter(key -> key.isAcceptable()).collect(Collectors.toList()).forEach(key ->{try {// 每次来一个新的连接, 不需要创建新的线程 , 而是注册到clientSelectorSocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();clientChannel.configureBlocking(false);clientChannel.register(serverSelect, SelectionKey.OP_ACCEPT);}catch(Exception e) {e.printStackTrace();}finally {set.iterator().remove();}});}}}catch (Exception e) {e.printStackTrace();}}).start();new Thread(() -> {try {// 批量轮询  有哪些连接有数据可读while ( true ) {if(clientSelect.select(1) > 0) {clientSelect.selectedKeys().stream().filter(key -> key.isReadable()).collect(Collectors.toList()).forEach(key -> {try {SocketChannel clientChannl = (SocketChannel) key.channel();ByteBuffer bf = ByteBuffer.allocate(1024);// 面向byteBufferclientChannl.read(bf);bf.flip();System.out.println(Charset.defaultCharset().newDecoder().decode(bf).toString());}catch ( Exception e) {e.printStackTrace();}finally {clientSelect.selectedKeys().iterator().remove();key.interestOps(SelectionKey.OP_READ);}});}}}catch (Exception e) {e.printStackTrace();}}).start();}}
      
      1. 通常NIO 模型中会有两个线程每个线程中绑定一个轮询器selector , 在我们的例子中serverSelector负责轮询是否有新的连接 , clientSelector 负责轮询连接中是否有数据可读。
      2. 服务端检测到新的连接之后 , 不在创建一个新的线程 , 而是直接将连接注册到clientSelector中
      3. clientorSelector 被一个while死循环抱着 , 如果在某一时刻有多个连接数据可读 ,数据将会被clientSelector.select() 方法轮询出来。 进而批量处理 。
      4. 数据的读写面向buffer 而不是面向流。
    3. 原生NIO 进行网络开发的缺点:

      1. JDK 的NIO 编程需要了解很多概念, 编程复杂 , 对NIO 入门很不友好 , 编程模型不友好 , ByteBuffer的API简直反人类 (这是书里这么说的 , 不要喷我)。
      2. 对NIO 编程来说 , 一个比较适合的线程模型能充分发挥它的优势 , 而JDK没有给你实现 , 你需要自己实现 , 就连简单的协议拆包都要自己实现 (我感觉这样才根据创造力呀 )
      3. JDK NIO 底层由epoll 实现 , 该实现饱受诟病的空轮训bug会导致cpu 飙升100%
      4. 项目庞大之后 , 自己实现的NIO 很容易出现各类BUG , 维护成本高 (作者怎么把自己的过推向JDK haha~)
      5. 正因为如此 , 我连客户端的代码都懒得给你写了 (这作者可真够懒的) , 你可以直接使用IOClient 和NIO_Server 通信
    4. JDK 的NIO 犹如带刺的玫瑰 , 虽然美好 , 让人向往 , 但是使用不当会让你抓耳挠腮 , 痛不欲生 , 正因为如此 , Netty横空出世!(作者这才华 啧啧啧~)

  3. Netty 编程
    1. Netty到底是何方神圣(被作者吹上天了都) , 用依据简单的话来说就是: Netty 封装了JDK 的NIO , 让你使用更加干爽 (干爽???) , 你不用在写一大堆复杂的代码了 , 用官方的话来说就是: Netty是一个异步事件驱动的网络应用框架 , 用于快速开发可维护的高性能服务器和客户端。
    2. Netty 相比 JDK 原生NIO 的优点 :
      1. 使用NIO 需要了解太多概念, 编程复杂 , 一不小心 BUG 横飞
      2. Netty 底层IO模型随意切换 , 而这一切只需要小小的改动 , 改改参数 , Netty乐意直接从NIO模型转换为IO 模型 。
      3. Netty 自带的拆包解包 , 异常检测可以让你从NIO 的繁重细节中脱离出来 , 让你只关心业务逻辑 。
      4. Netty 解决了JDK 的很多包括空轮训在内的BUG
      5. Netty社区活跃 , 遇到问题可以轻松解决
      6. Netty 已经经历各大RPC 框架 , 消息中间价 , 分布式通信中间件线上的广泛验证 , 健壮性无比强大
    3. 代码实例
      1. maven 依赖

        <dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.6.Final</version>
        </dependency>
        
      2. NettyServer

         /*** @author outman* */class Netty_server_02 {public void start () {ServerBootstrap serverBootstrap = new ServerBootstrap();NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup woker = new NioEventLoopGroup();serverBootstrap.group(boss ,woker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {@Overrideprotected void channelRead0(ChannelHandlerContext cxt, String msg) throws Exception {System.out.println(msg);}});}}).bind(8000);}}
        
        1. 这么一小段代码就实现了我们前面NIO 编程中所有的功能 , 包括服务端启动 , 接收新连接 , 打印客户端传来的数据。
        2. 将NIO 中的概念与IO模型结合起来理解:
          1. boss 对应 IOServer 中接收新连接创建线程 , 主要负责创建新连接
          2. worker 对应 IOServer 中负责读取数据的线程 , 主要用于数据读取语句业务逻辑处理 。
          3. 详细逻辑会在后续深入讨论
      3. NettyClient

        /**
        * @author outman
        * */
        class Netty_client_02 {

           public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap();NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) {ch.pipeline().addLast(new StringEncoder());}});Channel channel = bootstrap.connect("127.0.0.1", 8000).channel();while (true) {channel.writeAndFlush(new Date() + ": hello world!");Thread.sleep(2000);}}
        

        }

      4. 在客户端程序中 , group 对应了我们IOClient 中 新起的线程。

      5. 剩下的逻辑 我们在后文中详细分析 , 现在你可以把 Netty_server_02 和Netty_client_02 复制到 你的IDE 中 运行起来 感受世界 的美好 (注意 先启动 服务端 再启动客户端 )

      6. 使用Netty 之后 整个世界都美好了, 一方面 Netty 对NIO 封装的如此完美 , 另一方面 , 使用Netty 之后 , 网络通信这块的性能问题几乎不用操心 , 尽情的让Netty 榨干你的CPU 吧~~

Netty实战 IM即时通讯系统(二)Netty简介相关推荐

  1. Netty实战 IM即时通讯系统(十二)构建客户端与服务端pipeline

    Netty实战 IM即时通讯系统(十二)构建客户端与服务端pipeline 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向 ...

  2. Netty实战 IM即时通讯系统(十一)pipeline与channelHandler

    Netty实战 IM即时通讯系统(十一)pipeline与channelHandler 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端 ...

  3. Netty实战 IM即时通讯系统(十)实现客户端和服务端收发消息

    Netty实战 IM即时通讯系统(十)实现客户端和服务端收发消息 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向通信 数据 ...

  4. Netty实战 IM即时通讯系统(九)实现客户端登录

    ## Netty实战 IM即时通讯系统(九)实现客户端登录 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向通信 数据传输载 ...

  5. Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍

    ## Netty实战 IM即时通讯系统(七)数据传输载体ByteBuf介绍 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向 ...

  6. Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码

    Netty实战 IM即时通讯系统(八)服务端和客户端通信协议编解码 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 客户端启动流程 实战: 客户端和服务端双向通信 数 ...

  7. Netty实战 IM即时通讯系统(六)实战: 客户端和服务端双向通信

    ## Netty实战 IM即时通讯系统(六)实战: 客户端和服务端双向通信 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 实战: 客户端和服务端双向通信 数据传输载 ...

  8. Netty实战 IM即时通讯系统(五)客户端启动流程

    ## Netty实战 IM即时通讯系统(五)客户端启动流程 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 实战: 客户端和服务端双向通信 数据传输载体ByteBuf ...

  9. Netty实战 IM即时通讯系统(四)服务端启动流程

    ## Netty实战 IM即时通讯系统(四)服务端启动流程 零. 目录 IM系统简介 Netty 简介 Netty 环境配置 服务端启动流程 实战: 客户端和服务端双向通信 数据传输载体ByteBuf ...

最新文章

  1. 广义互相关计算TDOA
  2. 华西村合作制及分配制度
  3. linux-shell命令之rm(remove)【删除文件或目录】
  4. CodeForces 1396E Distance Matching(构造+树的重心+dfs+set)
  5. js能调用c语言吗,HTML页面,测试JS对C函数的调用
  6. 无人机4G数传一站多机模式
  7. 注册表修改系统显示时间
  8. 联想计算机wifi卸载,怎么卸载联想笔记本电源管理软件
  9. 简便word批量打印 - Office批量打印精灵(支持正版)
  10. SCAU 计算智能 8615 快乐
  11. (原创推荐文章)kerberos服务器端与客户端
  12. 酷我CEO雷鸣:差异化服务是制胜关键
  13. ad19原理图标注_AD19如何让原理图的栅格显示更加清晰醒目
  14. java期末考试复习题_java期末考试复习题库 试题题库.doc
  15. 酒店智能门锁方案功能介绍_模组_西城微科
  16. 计算机excel相关课件,计算机excel教学课件
  17. 跟着小甲鱼学python怎么样_跟着小甲鱼的python视频学习,小白能够接受吗?
  18. fastapi——简单快速入门
  19. EndNote20导入知网文献和导出BibTeX于TexStudio
  20. 测量银河系年龄的新方法

热门文章

  1. Native与H5交互的一些解决方法
  2. BIETLOLTP之概念熟悉
  3. php 判断 多组,请教怎么有效判断几组数据范围是否有交集
  4. delphi mysql dll直接_十万火急!!!那位高手用过libmysql.dll直接连接MySql数据库?如何将二进制文件保存到blob字段中? (60分)...
  5. ffmpeg linux 命令,Linux命令行下转换媒体格式工具FFMPEG介绍
  6. Java黑皮书课后题第9章:9.1 (Rectangle类)遵照9.2节中Circle类的例子,设计一个名为Rectangle的类表示矩形
  7. Java黑皮书课后题第8章:*8.26(行排序)用下面的方法实现一个二维数组中的行排序。返回新数组,且原数组保持不变。编写一个测试程序,提示用户输入一个3*3的double型矩阵,显示一个排好的矩阵
  8. C语言学习之求一个3X3的整型矩阵对角线元素之和。
  9. atoi() 与 itoa()函数的内部实现
  10. java基础(网络编程---IP、端口、URL)