zoukankan      html  css  js  c++  java
  • netty 学习笔记一:感受 IO编程 NIO编程 与 Netty 编程

    代码和注释:https://github.com/christmad/code-share/tree/master/share-netty/src/main/java/code.share.netty

    (1)IO编程模式

     IO server端代码:

     1 public void IOserver() throws IOException {
     2         // IO模型-服务端监听端口
     3         ServerSocket server = new ServerSocket(8000);
     4 
     5         new Thread(() -> {
     6             while (true) {
     7                 try {
     8                     // (1) 阻塞方式获取新连接
     9                     Socket socket = server.accept();
    10 
    11                     // (2) 将新连接绑定到一条新线程上去处理
    12                     new Thread(() -> {
    13                         try {
    14                             int len;
    15                             byte[] data = new byte[1024];
    16                             InputStream input = socket.getInputStream();
    17                             // (3) 按字节流方式读取数据
    18                             while ((len = input.read(data)) != -1) {
    19                                 System.out.println(new String(data, 0, len));
    20                             }
    21                         } catch (IOException e) {
    22                         }
    23                     }).start();
    24                 } catch (IOException ex) {
    25                 }
    26             }
    27         }).start();
    28     }
    View Code

    由上我们可用看出 IO server端编码三要素:

      1. 阻塞方式获取新连接

      2. 将新连接绑定到一条新线程上处理

      3. 按字节流方式读取数据

    IO client端代码:

    public void IOclient() {
            new Thread(() -> {
                try {
                    // IO模型-客户端连接服务器
                    Socket socket = new Socket("localhost", 8000);
                    while (true) {
                        try {
                            // 每隔 2s 向服务器发送一条信息
                            socket.getOutputStream().write((new Date() + " : hello").getBytes());
                            TimeUnit.SECONDS.sleep(2);
                        } catch (InterruptedException e) {
                        }
                    }
                } catch (IOException e) {
                }
    
            }).start();
    
        }
    View Code

    (2)NIO编程模式

     NIO server端代码:

     1 public void NIOserver() throws IOException {
     2         // throw ex
     3         // 创建 selector:NIO模型中通常会有两个线程,每个线程绑定一个轮询器 selector
     4         Selector serverSelector = Selector.open();      // serverSelector 负责轮询是否有新的连接
     5         Selector clientSelector = Selector.open();      // clientSelector 负责轮询连接是否有数据可读
     6 
     7         // 模拟服务端接收新连接,demo中只用一条线程去处理
     8         new Thread(() -> {
     9             try {
    10                 // NIO服务端启动
    11                 ServerSocketChannel listenerChannel = ServerSocketChannel.open();
    12                 // 在 bind 的时候底层已经开始监听了,windows 1.8 JDK 默认实现 backlog=50,即最多允许50条全连接
    13                 listenerChannel.socket().bind(new InetSocketAddress(8000));
    14                 listenerChannel.configureBlocking(false);   // non-blocking
    15 
    16                 // ServerSocketChannel#register 函数是将与操作系统交互(I/O)的工作交给持有 serverSelector,
    17                 // 持有 serverSelector 对象的线程在 while 循环中处理新连接:Selector.select() 函数会获得操作系统层面做好三次握手的客户端连接,一次可获得批量
    18                 //      实际开发中可能是多条线程来共同负责处理一大批连接,即 1个serverSelector、多个clientSelector。如果可以对同一个端口多次 bind 的话,有可能多个 serverSelector?
    19                 // 注意:register 方法是来自 ServerSocketChannel,这个方法的作用是让 selector 获得 channel 的引用。
    20                 //      操作系统底层处理的经过三次握手的连接应该是体现在 Channel 上,channel 负责存储网络数据,而 Selector 负责数据操作
    21 
    22                 // SelectionKey.OP_ACCEPT 指明 serverSelector 要监听的是新连接
    23                 listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
    24 
    25                 while (true) {
    26                     // 整个 while 循环处理的是将 serverSelector#select 获取到的连接事件绑定到 clientSelector 上,由它来监听这些连接的 SelectionKey.OP_READ 事件
    27 
    28                     // 等待新连接,阻塞时间为 1ms
    29                     if (serverSelector.select(1) > 0) {
    30                         Set<SelectionKey> acceptIdSet = serverSelector.selectedKeys();
    31                         Iterator<SelectionKey> acceptIdItor = acceptIdSet.iterator();
    32                         while (acceptIdItor.hasNext()) {
    33                             SelectionKey key = acceptIdItor.next();
    34 
    35                             if (key.isAcceptable()) {
    36                                 // 客户端连接经过三次握手进来后,我们需要处理客户端连接的事件:读或写
    37                                 // 本例中模拟的是监听连接的可读事件——请求
    38                                 try {
    39                                     // 通过 ServerSocketChannel#accept 函数获取 SocketChannel
    40                                     // 底层原理是客户端请求和上面绑定的 8000 端口连接,三次握手后,操作系统会启用另一个端口来标识这条连接,
    41                                     // 这条新连接在 NIO API 上反应出来就是一个 SocketChannel 对象,我们将这个对象注册到 clientSelector 以达到统一管理客户端连接读事件的目的
    42                                     SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept();
    43                                     clientChannel.configureBlocking(false);
    44                                     // 调用 SocketChannel#register 函数,表示 clientSelector 将会关注 channel 的可读事件;一旦对应的 channel 标志位变化,就可以进行下一步操作
    45                                     clientChannel.register(clientSelector, SelectionKey.OP_READ);
    46                                 } finally {
    47                                     // 只是在本轮的 Selector#selectedKeys 中移除了,可能是帮助清空此次 Selector#selectedKeys 产生的临时 Set<SelectionKey> 空间
    48                                     acceptIdItor.remove();
    49                                 }
    50                             }
    51 
    52                         }
    53                     }
    54                 }
    55             } catch (IOException e) {
    56                 e.printStackTrace();
    57             }
    58         }).start();
    59 
    60         // 在上面的代码中,我们只是把连接注册到 clientSelector 中并监听它们的读事件,下面则是批量处理读事件的代码
    61         new Thread(() -> {
    62             while (true) {
    63                 try {
    64                     // 批量轮询有哪些连接有数据可读,阻塞时间为 1ms
    65                     if (clientSelector.select(1) > 0) {
    66                         Set<SelectionKey> readIdSet = clientSelector.selectedKeys();
    67                         Iterator<SelectionKey> readIdItor = readIdSet.iterator();
    68                         while (readIdItor.hasNext()) {
    69                             SelectionKey key = readIdItor.next();
    70 
    71                             if (key.isReadable()) {
    72                                 try {
    73                                     SocketChannel clientChannel = (SocketChannel) key.channel();
    74                                     ByteBuffer buf = ByteBuffer.allocate(1024); // 申请 1KB 缓冲区
    75                                     // NIO 读面向 buf, 将 clientChannel 数据缓存到 buf 对象中
    76                                     clientChannel.read(buf);
    77                                     buf.flip();
    78                                     System.out.println(Charset.defaultCharset().newDecoder().decode(buf).toString());
    79                                 } finally {
    80                                     readIdItor.remove();
    81                                     // 暂时不知道有什么用
    82                                     key.interestOps(SelectionKey.OP_READ);
    83                                 }
    84 
    85                             }
    86                         }
    87                     }
    88                 } catch (IOException e) {
    89                     e.printStackTrace();
    90                 }
    91             }
    92         }).start();
    93 
    94 
    95     }
    View Code

    由上我们可以看出 NIO server端编码概念比较多:

      Channel、Selector、SelectionKey、select逻辑、register逻辑、ByteBuffer缓冲区读写逻辑......特别是 ByteBuffer 里面那一套 flip、capacity、reset、mark 等操作缓冲游标的方法,稍不注意就会出错。如果开发者都是基础比较过硬的,那么可以封装一套更易用的代码出来,毕竟 Java 作为一门业务语言的优势在于快速开发,而不是每一样都需要注重到底层。而前面说的一套好的封装,netty 已经帮我们做好了,接下来我们会介绍 netty编程模式。

    NIO client端代码:太繁琐,直接看下面 netty 如何实现 server、client 端编码吧

    (3)Netty编程模式

    netty server端代码:

     1 public void nettyServer() {
     2         ServerBootstrap serverBootstrap = new ServerBootstrap();
     3         NioEventLoopGroup boss = new NioEventLoopGroup();
     4         NioEventLoopGroup worker = new NioEventLoopGroup();
     5 
     6         serverBootstrap
     7                 .group(boss, worker)
     8                 .channel(NioServerSocketChannel.class)
     9                 .childHandler(new ChannelInitializer<NioSocketChannel>() {
    10                     @Override
    11                     protected void initChannel(NioSocketChannel ch) throws Exception {
    12                         ch.pipeline().addLast(new StringDecoder());
    13                         ch.pipeline().addLast(new SimpleChannelInboundHandler<String>() {
    14 
    15                             @Override
    16                             protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
    17                                 System.out.println(msg);
    18                             }
    19                         });
    20                     }
    21                 });
    22 
    23         // 定义一个自动寻找端口绑定的方法,在预设端口被占用时比较灵活
    24         // windows 上 1000 端口是可以直接绑定成功的,linux 上据说 0-1023 都不能绑定,是预留端口
    25         int port = 1000;
    26         bind(serverBootstrap, port);
    27     }
    28 
    29     private void bind(ServerBootstrap serverBootstrap, int port) {
    30         serverBootstrap.bind(port).addListener(future -> {
    31             if (future.isSuccess()) {
    32                 System.out.println("监听端口绑定成功,port=" + port);
    33             } else {
    34                 System.out.println("监听端口绑定失败,port=" + port);
    35                 System.out.println("尝试下一个端口, next port= " + (port + 1));
    36                 bind(serverBootstrap, port + 1);
    37             }
    38         });
    39     }
    View Code

    netty client端代码:

     1 public void nettyClient() {
     2         Bootstrap bootstrap = new Bootstrap();
     3         NioEventLoopGroup worker = new NioEventLoopGroup();
     4 
     5         bootstrap
     6                 .group(worker)
     7                 .channel(NioSocketChannel.class)
     8                 .handler(new ChannelInitializer<Channel>() {
     9                     @Override
    10                     protected void initChannel(Channel ch) throws Exception {
    11                         ch.pipeline().addLast(new StringEncoder());
    12                     }
    13                 });
    14 
    15         Channel channel = bootstrap.connect("localhost", 8000).channel();
    16 
    17         while (true) {
    18             channel.writeAndFlush(new Date() + " : hello");
    19             try {
    20                 TimeUnit.SECONDS.sleep(2);
    21             } catch (InterruptedException e) {
    22             }
    23         }
    24     }
    View Code

    经过本节介绍,应该可以看到 netty 的封装已经比原生API优雅了不止一点点,各种分布式框架的网络模块大量应用 netty 作为第三方网络包也验证了 netty 的封装在性能方面绝对可以榨干你的 CPU 。下一篇将会分析 netty编程 server端、client端的一些基本要素。链接如下:https://www.cnblogs.com/christmad/p/11625674.html

  • 相关阅读:
    oracle参数文件(1)
    提高HTML5 canvas性能的几种方法(转)
    基于TouchVG开发的Windows矢量图形编辑器
    使用rapidjson实现了TouchVG的序列化适配器类
    关于用例的几个问题分析
    重温《UML风格》
    API设计准则(转)
    UML初级培训录音内容
    暂定的UML培训大纲
    基于Android平台多个Icon的APk——实现多程序入口总结(转)
  • 原文地址:https://www.cnblogs.com/christmad/p/11622366.html
Copyright © 2011-2022 走看看