Netty---线程模型,核心组件和编解码

Netty—线程模型,核心组件和编解码

优点

  • 设计优雅,适用于各种传输类型的统一API阻塞和非阻塞Socket,基于灵活大且可扩展的实际爱你模型,可以清晰地分离关注点;高度可定制地线程模型,但线程,一个或多个线程池
  • 使用方便,详细记录地javadoc
  • 高性能,吞吐量更高,延迟更低;减少资源消耗,最小化不必要地内存复制
  • 安全,完整地SSL/TLS和startTLS支持

线程模型

目前存在的线程模型:

  • 传统阻塞I/O服务模型
  • Reactor模式(反应器模式或者分发者模式)
    • 根据Reactor的数量和处理资源池线程的数量不同,分为3种
      • 单Reactor单线程
      • 单Reactor多线程
      • 主从Reactor多线程
  • Netty线程模式(主要基于主从Reactor多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor)

传统阻塞I/O服务模型的缺点:

  • 当并发数很大,就会创建大量的线程,占用很大系统资源
  • 连接创建后,如果当前线程暂时没有数据可读,该线程会阻塞在read操作,造成线程资源的浪费。

Reactor模式,针对传统IO模型缺点的解决方案

  • 基于IO复用模型,多个连接共用一个阻塞对象,应用程序只需要在一个阻塞对象等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理
  • 基于线程池复用线程资源,不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。

Reactor模式核心组成

  • Reactor:Reactor在一个单独线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件做出反应
  • Handler:处理程序执行IO事件要完成的实际事件

单Reactor单线程

优点:

  • 模型简单,没有多线程,进程通信,竞争的问题,全部在一个线程中完成

缺点:

  • 性能问题,只有一个线程,整个进程无法处理娶其他连接事件,容易导致性能瓶颈
  • 可靠性问题,线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息,造成节点故障

单Reactor多线程

模型说明

  1. Reactor对象通过select监控客户端请求事件,收到事件收,通过dispatch进行分发
  2. 如果是建立连接事件,则通过acceptor通过accpet处理连接事件,然后创建一个hadler对象处理完成连接后的各种事件
  3. 如果不是连接请求,则由reactor分发调用俩姐对应的handler来处理
  4. handler只负责响应事件,不做具体的业务处理,通过read读取数据后,会分发给后面的work线程池的某个线程处理业务
  5. worker线程池会分配独立线程完成真正的业务,并将结果返回给handler

优点:

  • 可以充分利用多核cpu的处理能力

缺点:

  • 多线程数据共享和访问比较复杂,reactor处理的事件的监听和响应,在单线程运行,在高并发场景容易出现性能瓶颈。

主从Reactor模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nEptycjN-1619253120261)(C:\Users\ASUS\AppData\Roaming\Typora\typora-user-images\image-20210415192829753.png)]

方案说明:

  1. Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过acceptor处理连接事件。
  2. 当accept处理连接事件后,MainReactor将连接分配给SubReactor
  3. subreactor将连接事件加入到连接队列进行监听,并创建handler进行各种事件处理
  4. 当有新事件发生时,subreactor就会调用对应的handler处理
  5. handler通过read读取数据,分发给后面的worker线程池处理
  6. worker线程池分配独立的worker线程进行业务处理,并返回结果。
  7. handler收到响应的结果后,在通过send将结果返回给client

优点:

  • 父线程与子线程的数据交互简单职责明确,父线程只需接收新连接,子线程完成后续的业务处理
  • reactor主线程只需要把新连接传给子线程,子线程无需返回数据

缺点:

  • 编程复杂度高

Netty模型

说明:

  • netty抽象出两组线程池
    • BossGroup专门负责接收客户端的连接
    • WorkGroup专门负责网络的读写
    • 都是NioEventLoopGroup,相当于事件循环组,含有多个事件循环,每一个事件循环是NioEventLoop
  • NioEventLoop表示一个不断循环的执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket的网络通讯
  • NioEventLoopGroup可以含有多个线程,即可以含有多个NioEventLoop
  • Boss NioEventLoop循环执行步骤
    1. 轮询accept事件
    2. 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个Work NioEventLoop上的selector
    3. 处理任务队列的任务,即runAllTask
  • 每个Work NioEventLoop循环执行的步骤
    1. 轮询read,write事件
    2. 处理IO事件,在对应的NioSocketChannel处理
    3. 处理任务队列的任务,即NioSocketChannel
  • 每个Work NioEventLoop处理业务时,会使用pipeline(管道),pipeline中包含了channel,即通过pipeline可以获得对应的通道,管道中维护了很多处理器。

示例:

server:

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        //创建BossGroup 和WorkGroup
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        //创建服务器断案的启动对象,配置参数
        ServerBootstrap bootstrap = new ServerBootstrap();

        //使用链式编程来配置
        bootstrap.group(boosGroup,workGroup)    //设置两个线程组
                .channel(NioServerSocketChannel.class)      //使用NioServerSocketChannel作为服务器的通道实现
                .option(ChannelOption.SO_BACKLOG,128)   //设置线程队列得到的连接个数
                .childOption(ChannelOption.SO_KEEPALIVE,true)   //设置保持活动连接状态
                .childHandler(new ChannelInitializer<SocketChannel>(){
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new NettyServerHandler());
                    }
                }); //给workGroup的EventLoop对应的管道设置处理器
        System.out.println("server is ready ...");
        //启动服务器,并绑定端口
        ChannelFuture channelFuture = bootstrap.bind(8888).sync();
        //关闭通道进行监听
        channelFuture.channel().closeFuture().sync();
    }
}

serverHandler:

/*
自定义的handler需要继承netty规定好的某个handlerAdapter
* */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //读取数据实际(这里可以读取客户端发送的消息)
    /*
    * ChannelHandlerContext ctx上下文对象,含有管道(pipeline),通道(channel),地址
    * Object msg 就是客户端发送的数据
    * */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("上下文对象为 : " + ctx);
        ByteBuf byteBuf=(ByteBuf) msg;
        System.out.println("客户端发送的消息是: " + byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址 :" + ctx.channel().remoteAddress());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        //将数据写入缓存,并刷新
        System.out.println("已经读取完毕");
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,client",CharsetUtil.UTF_8));
    }
    
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //发生异常,关闭通道
        ctx.channel().close();
    }
}

client:

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel socketChannel) throws Exception {
                        socketChannel.pipeline().addLast(new NettyClientHandler());
                    }
                });

        System.out.println("client is ok");

        ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8888).sync();
        channelFuture.channel().close().sync();
    }
}

clientHandler:

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    //当通道就绪就会触发该方法
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client ctx:" + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server".toString(), CharsetUtil.UTF_8));
    }

    //当通道有读取事件,触发该方法
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf=(ByteBuf) msg;
        System.out.println("server send info :" + byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("server address : "+ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

任务队列

  • 用户自定义的普通任务

    •     ctx.channel().eventLoop().execute(new Runnable() {
              @Override
              public void run() {
                  try {
                      System.out.println("开始休眠");
                      Thread.sleep(3000);
                      ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server", CharsetUtil.UTF_8));
                      System.out.println("休眠结束");
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
          });
      
  • 用户自定义定时任务

    •     ctx.channel().eventLoop().schedule(new Runnable() {
              @Override
              public void run() {
                  ctx.writeAndFlush(Unpooled.copiedBuffer("hello,server2",CharsetUtil.UTF_8));
              }
          },5, TimeUnit.SECONDS);
      
  • 非当前Reactor线程调用Channel的各种方法

方案再说明

  • NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的scoket网络通道。
  • NioEventLoop内部采用串行化设计,从消息的读取—>解码—>处理—>编码—>发送,始终由IO线程NioEventLoop负责
  • NioEventLoopGroup下包含多个NioEventLoop
  • 每个NioEventLoop中包含一个selector,一个taskQueue
  • 每个NioEventLoop的selector上可以注册监听多个NioChannel
  • 每个NioChannel只会绑定在一个NioEventLoop上
  • 每个NioChannel都会 绑定有一个自己的ChannelPipeline

Future说明

  • 表示异步的执行结果,可以通过它提供的方法来检测执行是否完成,比如检索计算等
  • ChannelFuture是一个接口,我们可以设置监听器,当监听的事件发生时,就会通知到监听器

异步模型

工作原理示意图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qoOMGSJd-1619253120266)(E:\Typora\img\20190430191016658.png)]

说明:

  • 在使用netty编程时,拦截操作和转换出入站数据只需要提供callback或利用future即可。这使得链式操作简单,高效,并有利于编写可重用的,通用的代码。
  • netty框架的目标就是让你的业务逻辑从网络基础应用编码中分离出来。

Future-Listener机制

  • 当Future对象刚刚创建时,处于非完成状态,调用者可以通过返回的channelFuture来获取操作执行的状态,注册监听函数执行完成后的操作。

  • 常用方法:

    • isDone():判断操作是否完成
    • isSuccess():判断操作是否成功
    • getCase():获取已完成,但操作失败的原因
    • isCancelled():判断操作是否被取消
    • addListener():添加监听器
  • 示例:

    •     channelFuture.addListener(new ChannelFutureListener() {
              @Override
              public void operationComplete(ChannelFuture future) throws Exception {
                  if (channelFuture.isSuccess()) {
                      System.out.println("监听8888端口成功");
                  }else {
                      System.out.println("监听8888端口失败");
                  }
              }
          });
      
  • 优点:

    • 相比于传统的阻塞IO,执行IO操作后线程会被阻塞住,直到操作完成;异步处理的好处就是不会操作线程阻塞,线程在IO操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量。

http服务程序示例

TestHttpServer:

public class TestHttpServer {
    public static void main(String[] args) {
        EventLoopGroup boosGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boosGroup,workGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new TestHttpServerChannelInitializer());

            serverBootstrap.bind(8888).sync();
            System.out.println("服务器开启...");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //让服务器一直运行,就不需要关闭bossgroup和workgroup
//            System.out.println("服务器关闭...");
//            boosGroup.shutdownGracefully();
//            workGroup.shutdownGracefully();
        }


    }
}

TestHttpServerChannelInitializer:

public class TestHttpServerChannelInitializer extends ChannelInitializer<SocketChannel> {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new TestHttpServerHandler());
    }
}

TestHttpServerHandler

public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {

        //判断请求是否是request
        if (msg instanceof HttpRequest) {
            System.out.println("msg 类型:" + msg.getClass());
        }
        System.out.println(ctx.channel().remoteAddress());

        //返回消息给客户端
        ByteBuf buf= Unpooled.copiedBuffer("this is a msg", CharsetUtil.UTF_8);

        //构建一个http的响应
        DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH,buf.readableBytes());
        //将消息返回
        ctx.channel().writeAndFlush(response);
    }
}

资源过滤:在handler中进行资源的过滤(示例如下)

        if (msg instanceof HttpRequest) {
            System.out.println("msg 类型:" + msg.getClass());
            HttpRequest request= (HttpRequest) msg;
            URI uri = new URI(request.uri());
            if ("/favicon.ico".equals(uri.getPath())) {
                System.out.println("浏览器请求了图标,这里不做任何响应");
                return;
            }
        }

核心模块组件

  • BootStrap,ServerBootStrap

    • BootStrap意思是引导,一个netty应用通常由一个bootstrap开始,主要作用是配置整个netty程序,串联各个组件,netty中bootstrap类是客户端程序的启动引导类,serverbootstrap是服务端启动的引导类
  • Future,channelFuture

    • netty中所有IO操作都是异步的,不能立刻得知消息是否被正确处理。但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和channelFutures,他们可以注册一个监听,当操作执行成功或者失败监听会自动触发注册的监听事件
    • 常用方法:
      • channel():返回当前正在进行IO操作的通道
      • sync():等待异步操作执行完毕
  • channel

    • netty网络通信的组件,能够用于执行网络的IO操作
    • 通过channel可获得当前网络连接的通道的状态
    • 通过channel可获得网络连接的配置参数
    • channel提供异步的网络IO操作,异步调用意味着任何IO调用都将立即返回,并且不保证在调用结束时所请求的IO操作已完成
    • 调用立即返回一个channelFuture实例,通过注册监听器到channelFuture上,可以IO操作成功,失败,消失时回调通知通知调用方
    • 支持关联IO操作对应的处理程序
    • 不同协议,不同阻塞类型的连接都有不同的channel与之对应
  • channelHandler

    • channelHandler是一个接口,处理 IO事件或者拦截IO操作,并将其转发到其channelPipeline(业务处理链)中的下一个处理程序
    • channelHandler本身没有提供太多的方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类
    • ChannelInboundHandlerAdapter用于处理入站操作
    • ChanneloutboundHandlerAdapter用于处理出站操作
    • channelDupexHandler用于处理入站和出站操作(一般不建议使用,容易混淆)
  • channelPipeline和pipeline

    • channelPipeline是一个handler的集合,它负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿netty的链(也可以理解为channelPipeline是保存channelHandler的list,用于处理和拦截channel的入站事件和出站事件)
    • channelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及channel中各个的channelHandler如何交互
    • 一个channel包含一个channelPipeline,而channelPipeline中又维护了一个又channelHandlerContext组成的双向链表,并且每个channelHandlerContext中又关联着一个channelHandler
    • 入站事件和出站事件在一个双向链表中,入站事件从链表head完后传递到最后一个入站的handler,出站事件从链表的tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。
  • channelHandlerContext

    • 保存channel相关的所有上下文信息,同时关联一个channelHandler对象
    • channelHandlerContext中包含一个具体的事件处理器channelHandler,同时channelHandlerContext中也绑定了对应的pipeline和channel的信息,方便对channelHandler进行调用
  • channelOption

    • netty在创建channel实例后,一般需要设置channelOption参数
      • ChannelOption.SO_BACKLOG
        • 对应TCP/IP协议listen函数中的backlog参数,用来初始化服务器可连接队列大小。服务端处理客户端连接请求时顺序处理的。所以同一时间只能处理一个客户端请求。多个客户端来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定队列的大小
      • ChannelOption.SO_KEEPALIVE
        • 一直保持连接的活动状态
  • EventLoopGroup和NioEventLoopGroup

    • EventLoopGroup是一组抽象,netty为了更好的利用多核cpu资源,一般会有多个eventLoop同时工作,每个EventLoop维护着一个selector实例
    • EventLoopGroup提供next接口,可以从组里按照一定的规则获取其中一个eventLoop来处理任务
      • BossEventLoopGroup通常是一个单线程的eventLoop,eventLoop维护了一个注册了serverSocketChannel的selector实例BossEventLoop不断轮询slector将连接事件分离出来。
      • 然后将接收到的socketChannel转交给WorkEventLoopGroup
      • WorkEventLoopGroup会由next选择其中一个eventLoopGroup来将这个socketChannel注册到其维护的selector并对其后续的IO事件进行处理。
  • Unpooled类

    • netty提供一个专门用来操作缓冲区(netty的数据容器)的工具类

    • 常用方法:

      • copiedBuffer(CharSequence string, Charset charset):通过给定的数据和字符编码返回一个byteBuf对象
      • 通过上述方法获取到的buffer并不是netty中的byteBuffer,而是unpooled类中的内部类,其实际容量是大于字符串长度的。
    • 注意:

      • 在netty中buffer不需要flip进行读写切换,因为底层维护了一个readerIndex和writerIndex
      • 写入时(writeByte),writerIndex会随之变化
      • 读取时(readByte),readerIndex会随之变化
      • 获取指定位置的数据(getByte),readerIndex不会变化
      • 通过readerIndex和writerIndex,将buffer分为三个区域
        • 0==>readerIndex:已经读取的区域
        • readerIndex==>writerIndex:可以读取的区域
        • writerIndex==>capacity:可写区域

Netty练习:群聊功能的实现

GroupChatServer:

public class GroupChatServer {
    private int port;

    public GroupChatServer(int port){
        this.port=port;
    }

    public void run() throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workGroup)
                    .channel(NioServerSocketChannel.class)
                    .option(ChannelOption.SO_BACKLOG, 128)
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            //添加解码器
                            pipeline.addLast(new StringDecoder());
                            //添加编码器
                            pipeline.addLast(new StringEncoder());
                            //添加自定义处理器
                            pipeline.addLast(new GroupChatServerHandler());
                        }
                    });
            System.out.println("group server is already run");
            ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
//            channelFuture.channel().close();
        }catch (Exception e){
            e.printStackTrace();
        }
//        finally {
//            bossGroup.shutdownGracefully();
//            workGroup.shutdownGracefully();
//        }
    }

    public static void main(String[] args) {
        try {
            new GroupChatServer(8888).run();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

GroupChatServerHandler:

public class GroupChatServerHandler extends SimpleChannelInboundHandler<String> {

    //定义一个channel组,管理所有的channel
    //GlobalEventExecutor.INSTANCE是全局的事件执行器,是一个单例
    private static ChannelGroup channelGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    
    //表示连接建立,一旦建立,第一个被执行
    //将当前的channel加入到channelGroup中
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.add(channel);
        //将该客户加入聊天的信息推送给在线的客户端
        /*
        writeAndFlush 不需要我们自己遍历channelGroup,他会自行遍历channelGroup,然后发送数据
        * */
        channelGroup.writeAndFlush("[client]:"+channel.remoteAddress()+" join chat\n");
    }

    //表示该客户处理连接状态
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[client]:" + channel.remoteAddress() + " online");
    }

    //表示该客户处于离线状态
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[client]:" + channel.remoteAddress() + " outline");
    }

    //表示该客户退出群聊
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        channelGroup.writeAndFlush("[client]:"+channel.remoteAddress()+" already out");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        Channel channel = ctx.channel();
        //群聊发送信息时,要排除自己
        channelGroup.forEach(ch->{
            if (channel!=ch) {
                ch.writeAndFlush("[client]:"+channel.remoteAddress()+"send info "+msg+"\n");
            }else {
                ch.writeAndFlush("[self]:"+channel.remoteAddress()+"send info "+msg+"\n");
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {

        ctx.channel().close();
    }
}

GroupChatClient:

public class GroupChatClient {
    private final String host;
    private final int port;

    public GroupChatClient(String host,int port){
        this.host=host;
        this.port=port;
    }
    public void run() throws InterruptedException {
        NioEventLoopGroup eventExecutors = new NioEventLoopGroup();
        Bootstrap bootstrap = new Bootstrap();
        bootstrap.group(eventExecutors)
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new GroupChatClientHandler());
                    }
                });
        ChannelFuture channelFuture = bootstrap.connect(host, port).sync();
        Channel channel = channelFuture.channel();
        System.out.println("----------" + channel.remoteAddress() + "----------");
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNextLine()){
            String msg = scanner.nextLine();
            channel.writeAndFlush(msg+"\r\n");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        GroupChatClient groupChatClient = new GroupChatClient("127.0.0.1", 8888);
        groupChatClient.run();
    }
}

GroupChatClientHandler:

public class GroupChatClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("get info: " + msg);
    }
}

Netty心跳检测机制

IdleStateHandler(long readerIdleTime, l ong writerIdleTime, long allIdleTime,TimeUnit unit)

IdleStateHandler:是netty提供的处理空闲状态的处理器,当IdleStateHandler触发后,就会传递给管道的下一个handler去处理,通过触发下一个handler的userEventTiggered,在该方法去处理IdlestateEvent。

参数说明:

  • readerIdleTime:表示多长时间没有读,就会发送一个心跳检测包,检测是否连接
  • writerIdleTime:表示多长时间没有写,就会发送一个心跳检测包,检测是否连接
  • allIdleTime:表示多长时间没有读写,就会发送一个心跳检测包,检测是否连接

当IdleStateHandler触发后,下一个handler的处理,

public class MyServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event=(IdleStateEvent) evt;
            String type=null;
            switch (event.state()){
                case READER_IDLE:
                    type="no_read";
                    break;
                case WRITER_IDLE:
                    type="no_write";
                    break;
                case ALL_IDLE:
                    type="no_all";
                    break;
            }
        }
    }
}

WebSocket长连接

WebSocketTest:

public class WebSocketTest {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workGroup = new NioEventLoopGroup();
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap.group(bossGroup, workGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 128)
                .handler(new LoggingHandler(LogLevel.INFO))
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        //因为基于HTTP协议,使用http的编码和解码
                        pipeline.addLast(new HttpServerCodec());
                        //http是以块的方式写
                        pipeline.addLast(new ChunkedWriteHandler());
                        /*
                        因为http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合
                        这就是为什么当浏览器发送大量数据时,会发出多次http请求
                        * */
                        pipeline.addLast(new HttpObjectAggregator(8192));
                        /*
                        * 对应socket,它的数据是以帧的形式传递
                        * WebSocketServerProtocolHandler的核心功能就是将http协议升级为ws协议,保持长连接
                        *这里需要注意的是WebSocketServerProtocolHandler参数和ws请求参数是相对应的
                        * */
                        pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
                        //自定义handler
                        pipeline.addLast(new WebSocketTestHandler());
                    }
                });
        ChannelFuture channelFuture = serverBootstrap.bind(8888).sync();
    }
}

WebSocketTestHandler:

//TextWebSocketFrame 表示处理的数据是文本帧
public class WebSocketTestHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
        System.out.println("收到的数据为: " + msg.text());
        //回复浏览器
        ctx.channel().writeAndFlush(new TextWebSocketFrame("服务器已经接收到消息: "+msg.text()));
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("浏览器连接上服务器"+ctx.channel().id().asLongText());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("浏览器断开和服务器的连接"+ctx.channel().id().asLongText());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();
    }
}

hello.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form onsubmit="return false">
    <textarea name="message" style="height: 300px;width: 300px"></textarea>
    <input type="button" value="send info" onclick="send(this.form.message.value)">
    <textarea id="response" style="height: 300px;width: 300px"></textarea>
    <input type="button" value="clear" onclick="document.getElementById('response').value=''">
</form>
</body>
<script>
    var socket;
    if(window.WebSocket){
        socket=new WebSocket("ws://localhost:8888/hello");
        //相当于channelRead0,ev收到服务器端送回的消息
        socket.onmessage=function (ev){
            let rt = document.getElementById('response');
            rt.value=rt.value+"\n"+ev.data;
        }
        //相当于连接开启(感知到连接开启)
        socket.onopen=function (ev){
            let rt = document.getElementById('response');
            rt.value="连接已经开启";
        }
        socket.onclose=function (ev){
            let rt = document.getElementById('response');
            rt.value=rt.value+"\n"+"连接已经关闭了";
        }
    }else {
        alert("当前游览器不支持websocket")
    }

    function send(message){
        if(!window.WebSocket){return ;}
        if (socket.readyState==WebSocket.OPEN){
            socket.send(message);
        }else {
            alert("连接没有开启");
        }
    }
</script>
</html>

编码和解码

基本介绍
  • 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
  • codec(编解码器)的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder负责把业务数据转换成字节码数据,decoder负责把字节码数据转化成业务数据
netty提供的编码器
  • StringEncoder,对字符串数据进行编码
  • ObjectEncoder,对java对象进行编码
netty提供的解码器
  • StringDecoder:对字符串数据进行解码
  • ObjectDecoder:对java对象进行解码
问题:

netty本身自带的ObjectEncoder和ObjectDecoder可以用来实现pojo对象和各种业务对象的编码和解码,底层使用的仍是Java序列化技术,而Java序列化技术本身效率就不高,存在以下问题

  • 无法跨语言
  • 序列化后的体积太大,是二进制编码的5倍多

  • 序列化性能太差

ProtoBuf:

针对上述问题,提出解决方案—>protoBuf

protoBuf是google发布的开源项目,全称google protocol buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。很适合做数据存储RPC数据交换格式。支持跨平台,跨语言

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值