网络编程7

引导Bootstrap和ChannelOption

  • Bootstrap只能接受一个eventloopgroup,ServerBootstrap可以接受两个eventloopgroup
    • 联想reactor模式,第一个group负责和serverChannel绑定,专门负责接受网络连接,第二个专门已接受的子channel进行网络读写
    • 这两个合二为一也是可以的
    • 同时NioEventLoopGroup是可以指定线程的个数的
    • 所以之前介绍的三种反应器模式reactor都是可以实现的,根据要求自己配置就可以了
  • bootStrap.childHandler(new ChannelInitializer(){}
    • ChannelInitializer复制把一个或者多个handler添加到pipeline当中

ChannelOption

  • 配置tcp选项

    • SO_BACKLOG:监听的时候可连接队列(等待队列)

      1.建立tcp连接需要三次握手,服务器同一时间只能处理一个客户端的连接请求,此时如果又有一个客户端来请求连接,服务端就会生成一个队列,当前不能处理的请求就会放到服务队列里面排队,SO_BACKLOG就指明了这个队列的大小,一般在linux系统下是5个,jdk里面会改成50个。

      2.三次握手成功以后,又会产生一个队列,称为已连接队列

      已连接队列一般没有限制,如果服务器性能好,一台服务器支持的连接数达到上百万是没有问题的,

    • SO_REUSEADDR

      1.假设tomcat启动后,占用了80端口,由于某些原因,tomcat挂掉了,但是操作系统并不会马上释放80端口,会保留一阵子,之后才会释放80端口;

      2.所以马上启动80端口,可能会报错,80端口已经占用,指定这个参数后,就可以实现重用

    • SO_KEEPALIVE:保持tcp连接保活的参数,会定期发送保活的参数到对端,如果对端发送响应报文后,操作系统就会认为连接是存活的。

      1.但是一般写网络程序很少用,因为tcp协议里面定义时间长度为2个小时,是网络层面的

      2.所以一般是自己实现一个心跳机制,通过应用层来检测对方是否存活,相当于是应用层的

    • SO_RECBUF SO_SNDBUF:接受缓存区和发送缓存区

      1.这两个参数分别用来调整这两个缓存区的大小,一般是4k-8k

      2.这不是应用程序层面我们自己创建的buffer,这是操作系统自己实现的buffer

    • SO_LINGER:一个网络应用结束后,需要调用close()方法

      1.但是可能发送缓存区里面还有数据没有发送完,这是很正常的,一般情况下,操作系统会尽量发缓存区的数据,发多少算多少,发送结果是未知的

      2.启用这个参数后,就需要发送完缓存区的数据,才能关闭,相当于阻塞了close方法

    • TCP_NODELAY:与Nagle算法有关

      1.缓存区,被写一条,就发一条,或者堆积到一定程度再发

      2.缺省时采用批量发送,如果对网络延迟要求很高,启用该字段,就会禁用Nagle,也就是小数据、低延迟情况需要启用

    • TCP_CORK:与nodelay相反

ByteBuf

  • 网络数据的基本单位总是字节。Java NIO 提供了ByteBuffer 作为它的字节容器,但是这个类使用起来过于复杂,而且也有些繁琐。

  • Netty 的ByteBuffer 替代品是ByteBuf,一个强大的实现,既解决了JDK API 的局限性,又为网络应用程序的开发者提供了更好的API。

    • ByteBuf 维护了两个不同的索引,名称以read 或者write 开头的ByteBuf 方法,将会推进其对应的索引,而名称以set 或者get 开头的操作则不会
    • 使得不用再flip
    • 使用read、write方法才会推动索引,使用get和set方法不会推动索引,就是之前讲nio的buffer中的绝对读和相对读
  • ByteBuf 的使用

    • 堆缓冲区,分配在jvm堆上,分配和释放比较快,在网络读写上比直接缓存区慢

    • 直接缓冲区,分配在直接内存上,分配和释放比较慢,在网络读写上比堆缓存区快

    • 所以跟网络读写相关用直接缓冲区,如果跟业务相关相关,直接读写的,用堆缓冲区

    • 复合缓冲区

      http协议的消息头和消息体,消息头和消息体有各自的buffer,但是发送数据时视为一个整体

ByteBuf中的概念和API

  • 分配—new一个ByteBuf

    • ByteBufAllocate:ctx.alloc()就可以拿到一个分配器实例

      1.直接内存、堆、复合式的都可以实现

      2.ByteBufAllocate是一个接口,实现类分为池化的和非池化的,池化的就已经生成好了,放到一个内存池中,速度更快,内存碎片更少,非池化的,临时new一个新的出来

    • Unpooled

      1.这是一个工具类,实质上是通过上述的UnpooledByteBufAllocate

      2.池化算法—jemalloc算法

      3.Unpooled.copiedBuffer,这个方法可以把字符串转成ByteBuffer

  • 随机访问/顺序访问/读写操作

  • 可丢弃字节/可读字节/可写字节

    • readerIndex

      1.已经读过的字节是可丢弃字节,discardReadBytes,丢弃并回收

      2.但是会导致内存复制,一般情况不会使用,除非内存极其紧张

    • writeIndex

  • 索引管理

    • 调整索引也是可以的,进行索引的移动markReaderIndex、resetReaderIndex,读写都是可以重置的
  • 查找操作

    • indexOf,在指定范围的所有位置,起始和结束位置

    • forEachByte,例如里面有回车换行符

      ByteProcessor里面有相关的符号类

  • 派生缓冲区

    • 原生buffer是100字节,业务只需要看中间的20个字节
    • netty中提供了slice从上面弄一个视图下来,视为一个单独的buffer,进行相关操作,内存实例上是一个,并没有复制出一个新的,改了派生的,也会修改原来的
    • 只有ByteBuf.copy()才是复制出一个新的
  • 引用计数

  • 工具类

  • 资源释放

    • ReferenceCountUtil

解决粘包/半包问题

回顾我们的的Hello,Netty

修改后的客户端handler

  • package cn.enjoyedu.nettybasic.splicing.demo;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/26
     * 类说明:
     */
    public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 客户端读取到网络数据后的处理*/
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
          
          
            System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8)
                    +"] and the counter is:"+counter.incrementAndGet());
        }
    
        /*** 客户端被通知channel活跃后,做事*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
          
          
            ByteBuf msg = null;
            String request = "Mark,Lison,King,James,Deer"
                    + System.getProperty("line.separator");
            for(int i=0;i<100;i++){
          
          
                msg = Unpooled.buffer(request.length());
                msg.writeBytes(request.getBytes());
                ctx.writeAndFlush(msg);
            }
        }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    

修改后的服务器handler

  • package cn.enjoyedu.nettybasic.splicing.demo;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/25
     * 类说明:自己的业务处理
     */
    @ChannelHandler.Sharable
    public class EchoServerHandler extends ChannelInboundHandlerAdapter {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 服务端读取到网络数据后的处理*/
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          
          
            ByteBuf in = (ByteBuf)msg;
            String request = in.toString(CharsetUtil.UTF_8);
            System.out.println("Server Accept["+request
                    +"] and the counter is:"+counter.incrementAndGet());
            String resp = "Hello,"+request+". Welcome to Netty World!"
                    + System.getProperty("line.separator");
            ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
        }
    
    //    /*** 服务端读取完成网络数据后的处理*/
    //    @Override
    //    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
          
          
    //        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
    //    }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    
  • 客户端对比之前的不是只发送hello netty,发送100次,每次还有回车换行符

  • 服务器端希望区别出回车换行符,同时统计发送的次数,

    • 实际只收到两次,希望收到是100次,这就是粘包、半包问题

什么是TCP粘包半包?

  • 上面的例子就是典型的tcp粘包、半包

具体表现

  • 假如客户端发送d1和d2两个报文

    • 1.服务端收到就是d1和d2两个独立的报文
    • 2.服务器收到的d1和d2粘到一起了,就是粘包问题
    • 3.d2被拆分成两份,第一份和d1粘在一起,发生拆包和粘包
    • 4.d1被拆分成两份,第二份和d2粘在一起,发生拆包和粘包
  • 注意这不是tcp协议的问题,因为消息如何界定应该是由应用层来定义的,udp是一次性发送的,不会拆包,所以不会有粘包问题

TCP粘包/半包发生的原因

  • 应用程序写入数据的字节大小大于套接字发送缓冲区的大小

    • 发送1m数据,缓存区只有4k,就必须要分包
  • 进行MSS大小的TCP分段

    • MSS是最大报文段的意思,以太网是1460个字节,到了tcp这一层,如果超过了,操作系统就会进行分段
  • 以太网的payload大于MTU进行IP分片

    • 在数据链路层,MTU是最大传输单元,网卡规定一次就只能发送这么多字节,如果1500个字节,操作系统就会进行分片

解决粘包半包问题

  • 1.增加一个分隔符

    • 最主要的是回车换行符,对于文本编辑是最方便的

    • 以回车换行符,netty直接有对应的handler

    • package cn.enjoyedu.nettybasic.splicing.linebase;
      
      import io.netty.bootstrap.Bootstrap;
      import io.netty.channel.Channel;
      import io.netty.channel.ChannelFuture;
      import io.netty.channel.ChannelInitializer;
      import io.netty.channel.EventLoopGroup;
      import io.netty.channel.nio.NioEventLoopGroup;
      import io.netty.channel.socket.nio.NioSocketChannel;
      import io.netty.handler.codec.LineBasedFrameDecoder;
      
      import java.net.InetSocketAddress;
      
      /**
       * 作者:Mark
       * 创建日期:2018/08/26
       * 类说明:
       */
      public class LineBaseEchoClient {
              
              
      
          private final String host;
      
          public LineBaseEchoClient(String host) {
              
              
              this.host = host;
          }
      
          public void start() throws InterruptedException {
              
              
              EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
              try {
              
              
                  final Bootstrap b = new Bootstrap();;/*客户端启动必须*/
                  b.group(group)/*将线程组传入*/
                          .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/
                          .remoteAddress(new InetSocketAddress(host,LineBaseEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/
                          .handler(new ChannelInitializerImp());
                  ChannelFuture f = b.connect().sync();
                  System.out.println("已连接到服务器.....");
                  f.channel().closeFuture().sync();
              } finally {
              
              
                  group.shutdownGracefully().sync();
              }
          }
      
          private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
              
              
      
              @Override
              protected void initChannel(Channel ch) throws Exception {
              
              
                  // 修改点LineBasedFrameDecoder
                  ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                  ch.pipeline().addLast(new LineBaseClientHandler());
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              
              
              new LineBaseEchoClient("127.0.0.1").start();
          }
      }
      
      
    • LineBasedFrameDecoder,解码

    • package cn.enjoyedu.nettybasic.splicing.linebase;
      
      import io.netty.bootstrap.ServerBootstrap;
      import io.netty.channel.Channel;
      import io.netty.channel.ChannelFuture;
      import io.netty.channel.ChannelInitializer;
      import io.netty.channel.EventLoopGroup;
      import io.netty.channel.nio.NioEventLoopGroup;
      import io.netty.channel.socket.nio.NioServerSocketChannel;
      import io.netty.handler.codec.LineBasedFrameDecoder;
      
      import java.net.InetSocketAddress;
      
      /**
       * 作者:Mark
       * 创建日期:2018/08/25
       * 类说明:
       */
      public class LineBaseEchoServer {
              
              
      
          public static final int PORT = 9998;
      
          public static void main(String[] args) throws InterruptedException {
              
              
              LineBaseEchoServer lineBaseEchoServer = new LineBaseEchoServer();
              System.out.println("服务器即将启动");
              lineBaseEchoServer.start();
          }
      
          public void start() throws InterruptedException {
              
              
              final LineBaseServerHandler serverHandler = new LineBaseServerHandler();
              EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
              try {
              
              
                  ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/
                  b.group(group)/*将线程组传入*/
                      .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                      .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/
                      /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                      所以下面这段代码的作用就是为这个子channel增加handle*/
                      .childHandler(new ChannelInitializerImp());
                  ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
                  System.out.println("服务器启动完成,等待客户端的连接和数据.....");
                  f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
              } finally {
              
              
                  group.shutdownGracefully().sync();/*优雅关闭线程组*/
              }
          }
      
          private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
              
              
      
              @Override
              protected void initChannel(Channel ch) throws Exception {
              
              
                  // 修改点
                  ch.pipeline().addLast(new LineBasedFrameDecoder(1024));
                  ch.pipeline().addLast(new LineBaseServerHandler());
              }
          }
      
      }
      
      
    • package cn.enjoyedu.nettybasic.splicing.linebase;
      
      import io.netty.buffer.ByteBuf;
      import io.netty.buffer.Unpooled;
      import io.netty.channel.ChannelHandlerContext;
      import io.netty.channel.SimpleChannelInboundHandler;
      import io.netty.util.CharsetUtil;
      
      import java.util.concurrent.atomic.AtomicInteger;
      
      /**
       * 作者:Mark
       * 创建日期:2018/08/26
       * 类说明:
       */
      public class LineBaseClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
              
              
      
          private AtomicInteger counter = new AtomicInteger(0);
      
          /*** 客户端读取到网络数据后的处理*/
          protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
              
              
              System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8)
                      +"] and the counter is:"+counter.incrementAndGet());
              ctx.close();
          }
      
          /*** 客户端被通知channel活跃后,做事*/
          @Override
          public void channelActive(ChannelHandlerContext ctx) throws Exception {
              
              
              ByteBuf msg = null;
              String request = "Mark,Lison,King,James,Deer"
                      + System.getProperty("line.separator");
              for(int i=0;i<10;i++){
              
              
                  msg = Unpooled.buffer(request.length());
                  msg.writeBytes(request.getBytes());
                  ctx.writeAndFlush(msg);
              }
      
          }
      
          /*** 发生异常后的处理*/
          @Override
          public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
              
              
              cause.printStackTrace();
              ctx.close();
          }
      }
      
      
    • channelRead0中的ctx.close()需要注释掉,关早了

自定义分隔符----应用层是文本协议(FTP)

首先将定义的分割符转成byteBuffer,并增加一个新的解码器DelimiterBasedFrameDecoder

  • package cn.enjoyedu.nettybasic.splicing.delimiter;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.DelimiterBasedFrameDecoder;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/25
     * 类说明:
     */
    public class DelimiterEchoServer {
          
          
    
        public static final String DELIMITER_SYMBOL = "@~";
        public static final int PORT = 9997;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            DelimiterEchoServer delimiterEchoServer = new DelimiterEchoServer();
            System.out.println("服务器即将启动");
            delimiterEchoServer.start();
        }
    
        public void start() throws InterruptedException {
          
          
            final DelimiterServerHandler serverHandler = new DelimiterServerHandler();
            EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
            try {
          
          
                ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/
                b.group(group)/*将线程组传入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                    .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/
                    /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                    所以下面这段代码的作用就是为这个子channel增加handle*/
                    .childHandler(new ChannelInitializerImp());
                ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
                System.out.println("服务器启动完成,等待客户端的连接和数据.....");
                f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
            } finally {
          
          
                group.shutdownGracefully().sync();/*优雅关闭线程组*/
            }
        }
    
        private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
          
          
    
            @Override
            protected void initChannel(Channel ch) throws Exception {
          
          
                ByteBuf delimiter = Unpooled.copiedBuffer(DELIMITER_SYMBOL.getBytes());
                ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,
                        delimiter));
                ch.pipeline().addLast(new DelimiterServerHandler());
            }
        }
    
    }
    
    

服务器读取数据

  • package cn.enjoyedu.nettybasic.splicing.delimiter;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelFutureListener;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/25
     * 类说明:自己的业务处理
     */
    @ChannelHandler.Sharable
    public class DelimiterServerHandler extends ChannelInboundHandlerAdapter {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 服务端读取到网络数据后的处理*/
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          
          
            ByteBuf in = (ByteBuf) msg;
            // 指定编码格式
            String request = in.toString(CharsetUtil.UTF_8);
            System.out.println("Server Accept["+request
                    +"] and the counter is:"+counter.incrementAndGet());
            // System.getProperty("line.separator")改成了DelimiterEchoServer.DELIMITER_SYMBOL
            String resp = "Hello,"+request+". Welcome to Netty World!"
                    + DelimiterEchoServer.DELIMITER_SYMBOL;
            ctx.writeAndFlush(Unpooled.copiedBuffer(resp.getBytes()));
        }
    
        /*** 服务端读取完成网络数据后的处理*/
        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
          
          
            ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
        }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    
  • System.getProperty(“line.separator”)改成了DelimiterEchoServer.DELIMITER_SYMBOL

客户端handler

  • package cn.enjoyedu.nettybasic.splicing.delimiter;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/26
     * 类说明:
     */
    public class DelimiterClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 客户端读取到网络数据后的处理*/
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
          
          
            System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8)
                    +"] and the counter is:"+counter.incrementAndGet());
        }
    
        /*** 客户端被通知channel活跃后,做事*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
          
          
            ByteBuf msg = null;
            String request = "Mark,Lison,King,James,Deer"
                    + DelimiterEchoServer.DELIMITER_SYMBOL;
            for(int i=0;i<10;i++){
          
          
                msg = Unpooled.buffer(request.length());
                msg.writeBytes(request.getBytes());
                ctx.writeAndFlush(msg);
            }
        }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    

客户端client

  • package cn.enjoyedu.nettybasic.splicing.delimiter;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.DelimiterBasedFrameDecoder;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/26
     * 类说明:
     */
    public class DelimiterEchoClient {
          
          
    
        private final String host;
    
        public DelimiterEchoClient(String host) {
          
          
            this.host = host;
        }
    
        public void start() throws InterruptedException {
          
          
            EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
            try {
          
          
                final Bootstrap b = new Bootstrap();;/*客户端启动必须*/
                b.group(group)/*将线程组传入*/
                        .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/
                        .remoteAddress(new InetSocketAddress(host,DelimiterEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/
                        .handler(new ChannelInitializerImp());
                ChannelFuture f = b.connect().sync();
                System.out.println("已连接到服务器.....");
                f.channel().closeFuture().sync();
            } finally {
          
          
                group.shutdownGracefully().sync();
            }
        }
    
        private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
          
          
    
            @Override
            protected void initChannel(Channel ch) throws Exception {
          
          
                ByteBuf delimiter = Unpooled.copiedBuffer(
                        DelimiterEchoServer.DELIMITER_SYMBOL.getBytes());
                ch.pipeline().addLast(new DelimiterBasedFrameDecoder(1024,
                        delimiter));
                ch.pipeline().addLast(new DelimiterClientHandler());
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
          
          
            new DelimiterEchoClient("127.0.0.1").start();
        }
    }
    
    

消息定长------协商好消息长度后可以用这种模式,简单方便,物联网企业对性能要求不高的情况下

  • 跟之前类似,也是需要加一个handler来处理定长

服务器端handler

  • package cn.enjoyedu.nettybasic.splicing.fixed;
    
    import cn.enjoyedu.nettybasic.splicing.delimiter.DelimiterEchoServer;
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandler;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.ChannelInboundHandlerAdapter;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/25
     * 类说明:自己的业务处理
     */
    @ChannelHandler.Sharable
    public class FixedLengthServerHandler extends ChannelInboundHandlerAdapter {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 服务端读取到网络数据后的处理*/
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
          
          
            ByteBuf in = (ByteBuf) msg;
            String request = in.toString(CharsetUtil.UTF_8);
            System.out.println("Server Accept["+request
                    +"] and the counter is:"+counter.incrementAndGet());
            // 修改了应答内容
            // 固定发送FixedLengthEchoServer.RESPONSE
            ctx.writeAndFlush(Unpooled.copiedBuffer(
                    FixedLengthEchoServer.RESPONSE.getBytes()));
        }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    
  • 修改了应答内容,固定发送FixedLengthEchoServer.RESPONSE

服务器client

  • package cn.enjoyedu.nettybasic.splicing.fixed;
    
    import io.netty.bootstrap.ServerBootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioServerSocketChannel;
    import io.netty.handler.codec.FixedLengthFrameDecoder;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/25
     * 类说明:
     */
    public class FixedLengthEchoServer {
          
          
    
        public static final String RESPONSE = "Welcome to Netty!";
        public static final int PORT = 9996;
    
        public static void main(String[] args) throws InterruptedException {
          
          
            FixedLengthEchoServer fixedLengthEchoServer = new FixedLengthEchoServer();
            System.out.println("服务器即将启动");
            fixedLengthEchoServer.start();
        }
    
        public void start() throws InterruptedException {
          
          
            final FixedLengthServerHandler serverHandler = new FixedLengthServerHandler();
            EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
            try {
          
          
                ServerBootstrap b = new ServerBootstrap();/*服务端启动必须*/
                b.group(group)/*将线程组传入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO进行网络传输*/
                    .localAddress(new InetSocketAddress(PORT))/*指定服务器监听端口*/
                    /*服务端每接收到一个连接请求,就会新启一个socket通信,也就是channel,
                    所以下面这段代码的作用就是为这个子channel增加handle*/
                    .childHandler(new ChannelInitializerImp());
                ChannelFuture f = b.bind().sync();/*异步绑定到服务器,sync()会阻塞直到完成*/
                System.out.println("服务器启动完成,等待客户端的连接和数据.....");
                f.channel().closeFuture().sync();/*阻塞直到服务器的channel关闭*/
            } finally {
          
          
                group.shutdownGracefully().sync();/*优雅关闭线程组*/
            }
        }
    
        private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
          
          
    
            @Override
            protected void initChannel(Channel ch) throws Exception {
          
          
                ch.pipeline().addLast(
                        new FixedLengthFrameDecoder(
                                FixedLengthEchoClient.REQUEST.length()));
                ch.pipeline().addLast(new FixedLengthServerHandler());
            }
        }
    
    }
    
    
  • FixedLengthFrameDecoder

客户端client

  • package cn.enjoyedu.nettybasic.splicing.fixed;
    
    import io.netty.bootstrap.Bootstrap;
    import io.netty.channel.Channel;
    import io.netty.channel.ChannelFuture;
    import io.netty.channel.ChannelInitializer;
    import io.netty.channel.EventLoopGroup;
    import io.netty.channel.nio.NioEventLoopGroup;
    import io.netty.channel.socket.nio.NioSocketChannel;
    import io.netty.handler.codec.FixedLengthFrameDecoder;
    
    import java.net.InetSocketAddress;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/26
     * 类说明:
     */
    public class FixedLengthEchoClient {
          
          
    
        public final static String REQUEST = "Mark,Lison,Peter,James,Deer";
    
        private final String host;
    
        public FixedLengthEchoClient(String host) {
          
          
            this.host = host;
        }
    
        public void start() throws InterruptedException {
          
          
            EventLoopGroup group = new NioEventLoopGroup();/*线程组*/
            try {
          
          
                final Bootstrap b = new Bootstrap();;/*客户端启动必须*/
                b.group(group)/*将线程组传入*/
                        .channel(NioSocketChannel.class)/*指定使用NIO进行网络传输*/
                        .remoteAddress(new InetSocketAddress(host,FixedLengthEchoServer.PORT))/*配置要连接服务器的ip地址和端口*/
                        .handler(new ChannelInitializerImp());
                ChannelFuture f = b.connect().sync();
                System.out.println("已连接到服务器.....");
                f.channel().closeFuture().sync();
            } finally {
          
          
                group.shutdownGracefully().sync();
            }
        }
    
        private static class ChannelInitializerImp extends ChannelInitializer<Channel> {
          
          
    
            @Override
            protected void initChannel(Channel ch) throws Exception {
          
          
                ch.pipeline().addLast(
                        new FixedLengthFrameDecoder(
                                FixedLengthEchoServer.RESPONSE.length()));
                ch.pipeline().addLast(new FixedLengthClientHandler());
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
          
          
            new FixedLengthEchoClient("127.0.0.1").start();
        }
    }
    
    
  • 固定长度FixedLengthEchoServer.RESPONSE.length()

客户端handler

  • package cn.enjoyedu.nettybasic.splicing.fixed;
    
    import io.netty.buffer.ByteBuf;
    import io.netty.buffer.Unpooled;
    import io.netty.channel.ChannelHandlerContext;
    import io.netty.channel.SimpleChannelInboundHandler;
    import io.netty.util.CharsetUtil;
    
    import java.util.concurrent.atomic.AtomicInteger;
    
    /**
     * 作者:Mark
     * 创建日期:2018/08/26
     * 类说明:
     */
    public class FixedLengthClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
          
          
    
        private AtomicInteger counter = new AtomicInteger(0);
    
        /*** 客户端读取到网络数据后的处理*/
        protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
          
          
            System.out.println("client Accept["+msg.toString(CharsetUtil.UTF_8)
                    +"] and the counter is:"+counter.incrementAndGet());
        }
    
        /*** 客户端被通知channel活跃后,做事*/
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
          
          
            ByteBuf msg = null;
            for(int i=0;i<10;i++){
          
          
                msg = Unpooled.buffer(FixedLengthEchoClient.REQUEST.length());
                msg.writeBytes(FixedLengthEchoClient.REQUEST.getBytes());
                ctx.writeAndFlush(msg);
            }
        }
    
        /*** 发生异常后的处理*/
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
          
          
            cause.printStackTrace();
            ctx.close();
        }
    }
    
    

把消息里面做点动作,消息头+消息体,消息头告诉消息体有多长

  • 最常用、最灵活的一种

  • LengthFieldBasedFrameDecoder

猜你喜欢

转载自blog.csdn.net/Markland_l/article/details/113840743
今日推荐