轻量级RPC框架-NIO及Netty简述

6 篇文章 1 订阅
1 篇文章 0 订阅

轻量级RPC框架-NIO及Netty简述

1.什么是RPC?

 以下是网上对RPC概念的描述:
 RPC(Remote Procedure Call)—远程过程调用,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在,如TCP或UDP,为通信程序之间携带信息数据。在OSI网络通信模型中,RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
 RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,客户机调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用进程接收答复信息,获得进程结果,然后调用执行继续进行。
 简单来说远程过程调用就是允许一台计算机去调用另一台计算机上的程序得到结果,而代码层面不需要做额外的扩展,就像本地调用方法一样。
 再者又因为当今互联网应用的量级越来越大,单台计算机的能力有限,需要借助可扩展的计算机集群来完成,分布式的应用可以借助RPC来完成机器之间的调用。

2.RPC原理

这里写图片描述

3.NIO原理
3.1 简介

 java.nio全称java non-blocking IO(非阻塞IO),是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。
 为所有的原始类型提供(Buffer)缓存支持。字符集编码解码解决方案。 Channel :一个新的原始I/O 抽象。 支持锁和内存映射文件的文件访问接口。 提供多路(non-bloking) 非阻塞式的高伸缩性网络I/O 。

3.2 NIO与传统IO区别

 使用传统I/O程序读取文件内容,并写入到另一个文件(或Socket),如下程序:
  File.read(fileDesc,buf,len);
  Socket.send(socket,buf,len);
 会有较大的性能开销,主要表现在以下两个方面:

  1. 上下文切换(context switch),此处有四次用户态和内核态的切换
  2. Buffer内存开销,一个是应用程序buffer,另一个是系统读取buffer以及socket buffer

 以下是运行示意图:
这里写图片描述

  1. 先将文件内容从磁盘中拷贝到操作系统buffer
  2. 再从操作系统buffer拷贝到程序应用buffer
  3. 从程序buffer拷贝到socket buffer
  4. 从socket buffer拷贝到协议引擎
NIO

 NIO技术省去了将操作系统的read buffer拷贝到程序的buffer,以及从程序的buffer拷贝到socket buffer的步骤,直接将read buffer拷贝到socket buffer,java的FileChannel.transferTo()方法就是这样的实现,这个实现是依赖于操作系统底层的sendFile()实现的。
 public void transferTo(long position,long count,WritableByteChannel target);底层调用的是系统调用sendFile()方法,sendfile(int out_fd,int in_fd,off_t*offset,size_t,count)
这里写图片描述

 传统IO单线程情况下只能有一个客户端,用线程池可以有多个客户端连接,但是非常消耗性能,这里给出传统IO以及NIO的代码实现,大家可以对比理解一下

OioServer.java
package com.proto.server;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 传统IO
 * @author hzk
 * @date 2018/8/14
 */
public class OioServer {

    private static Integer port = 10010;

    public static void main(String[] args) throws IOException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        ServerSocket serverSocket = new ServerSocket(port);
        System.out.println("Socket server start...");
        while(true){
            //获取一个套接字(阻塞)
            final Socket accept = serverSocket.accept();
            System.out.println("Have new client into...");
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    handler(accept);
                }
            });
        }
    }

    /**
     * 读取数据
     * @param socket
     */
    public static void handler(Socket socket){
        try {
            byte[] bytes = new byte[1024];
            InputStream inputStream = socket.getInputStream();
            while(true){
                //读取数据
                int read = inputStream.read(bytes);
                if(-1 != read){
                    System.out.println(new String(bytes,0,read));
                }else{
                    break;
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                System.out.println("Socket server close...");
                socket.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

}

NioServer.java
package com.proto.server;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * 非阻塞IO new io
 * @author hzk
 * @date 2018/8/15
 */
public class NioServer {

    /**
     * 通道管理器
     */
    private Selector selector;

    public void initServer(int port) throws IOException {
        //获取一个ServerSocket通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        //设置为非阻塞
        serverSocketChannel.configureBlocking(false);
        //将该通道对应的serverSocket绑定端口
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        //获取一个通道管理器
        selector = Selector.open();
        //将通道管理器和该通道绑定,并向该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后当该事件出发到达时
        //selector.select()会返回,如果未到达会一直阻塞
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有则进行处理
     * @throws IOException
     */
    public void listen() throws IOException {
        System.out.println("Server start success...");
        //轮询访问selecor
        while(true){
            //当注册的事件到达时,方法返回,否则一直阻塞
            int select = selector.select();
            //获取selector选中项的迭代器,选中项为注册的事件
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey next = iterator.next();
                //删除已选的key,已防重复处理
                iterator.remove();
                handler(next);
            }
        }
    }

    /**
     * 处理请求
     * @param selectionKey
     * @throws IOException
     */
    public void handler(SelectionKey selectionKey) throws IOException {
        //客户端请求连接事件
        if(selectionKey.isAcceptable()){
            handlerAceept(selectionKey);
        }else if(selectionKey.isReadable()){
            //获得了可读事件
            handlerRead(selectionKey);
        }
    }

    /**
     * 处理连接请求
     * @param selectionKey
     * @throws IOException
     */
    public void handlerAceept(SelectionKey selectionKey) throws IOException {
        ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
        //获取客户端连接的通道
        SocketChannel accept = channel.accept();
        //设置非阻塞
        accept.configureBlocking(false);
        //此处可给客户端发送信息
        System.out.println("Have new client into....");
        //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限
        accept.register(selector,SelectionKey.OP_READ);
    }

    /**
     * 处理读事件
     * @param selectionKey
     * @throws IOException
     */
    public void handlerRead(SelectionKey selectionKey) throws IOException {
        //服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel)selectionKey.channel();
        //创建读取的缓冲区
        ByteBuffer allocate = ByteBuffer.allocate(1024);
        int read = channel.read(allocate);
        if(read >0){
            byte[] array = allocate.array();
            String msg = new String(array).trim();
            System.out.println("Server receive msg:"+msg);
            //回写数据
            ByteBuffer wrap = ByteBuffer.wrap("ok!".getBytes());
            //将消息回送给客户端
            channel.write(wrap);
        }else{
            System.out.println("Client close...");
            channel.close();
        }
    }

    /**
     * telnet之后全黑 ctrl+]可以调出命令输入行
     * @param args
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioServer nioServer = new NioServer();
        nioServer.initServer(8888);
        nioServer.listen();
    }


}

4.高性能NIO框架-Netty

 Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。
 “快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议的实现经验,这些协议包括FTP,SMTP,HTTP,各种二进制,文本协议,并经过相当精心设计的项目,最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性。
 以下简单的提供几个Netty Api使用参考,之后博客会对Netty做更详细的记录介绍

4.1 单Handler处理
pom导包
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-all</artifactId>
  <version>4.1.6.Final</version>
</dependency>
Netty服务端
EchoServerHandler
package com.ithzk.netty.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.Date;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoServerHandler extends ChannelInboundHandlerAdapter{


    /**
     * 从客户端接收到数据后调用
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server read data...");
        //读取数据
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String str = new String(bytes, "UTF-8");
        System.out.println("Server receive Client data:"+str);
        //向客户端发送数据
        System.out.println("Server send data To Client...");
        String currentTime = new Date(System.currentTimeMillis()).toString();
        ByteBuf byteBuf = Unpooled.copiedBuffer(currentTime.getBytes());
        ctx.write(byteBuf);
    }

    /**
     * 读取数据结束
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Server read data compelete!");
        //刷新后才将数据发送到SocketChannel
        ctx.flush();
    }

    /**
     * 发生异常时被调用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

EchoServer
package com.ithzk.netty.server;

import com.ithzk.netty.client.EchoClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoServer {

    private final int port;

    public EchoServer(Integer port) {
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup eventLoopGroup = null;
        try {
            //服务端引导类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //NioEventLoopGroup可以理解为一个线程池,这个线程池用来处理连接,接收数据,发送数据
            eventLoopGroup = new NioEventLoopGroup();
            //装配bootstrap
            serverBootstrap.group(eventLoopGroup).//多线程处理
                    channel(NioServerSocketChannel.class).//指定通道类型为NioServerSocketChannel
                    localAddress("localhost",port)//地址端口
                    .childHandler(new ChannelInitializer<SocketChannel>() {//设置child

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoServerHandler());//注册handler
                        }
                    });
            //绑定服务器直到绑定完成。调用sync方法会阻塞直到服务器绑定完成,然后服务器等待通道关闭
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            System.out.println("Start Listening port:"+channelFuture.channel());
            channelFuture.channel().closeFuture().sync();
        }finally {
            eventLoopGroup.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoServer(20000).start();
    }
}

Netty客户端
EchoClient
package com.ithzk.netty.client;

import io.netty.bootstrap.Bootstrap;
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.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.net.InetSocketAddress;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoClient{

    private String host;

    private Integer port;

    //NioEventLoopGroup nioEventLoopGroup = null;

    public EchoClient(String host, Integer port) {
        this.host = host;
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup eventLoopGroup = null;
        try {
            //客户端引导类
            Bootstrap bootstrap = new Bootstrap();
            //NioEventLoopGroup可以理解为一个线程池,这个线程池用来处理连接,接收数据,发送数据
            eventLoopGroup = new NioEventLoopGroup();
            bootstrap.group(eventLoopGroup).//多线程处理
                    channel(NioSocketChannel.class).//指定通道类型为NioSocketChannel
                    remoteAddress(new InetSocketAddress(host,port))//地址端口
                    .handler(new ChannelInitializer<SocketChannel>() {//业务处理

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new EchoClientHandler());//注册handler
                        }
                    });
            //连接服务器
            ChannelFuture channelFuture = bootstrap.connect().sync();
            channelFuture.channel().closeFuture().sync();
        }finally {
            eventLoopGroup.shutdownGracefully().sync();
        }

    }

    public static void main(String[] args) throws InterruptedException {
        new EchoClient("localhost",20000).start();
    }
}

EchoClientHandler
package com.ithzk.netty.client;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{

    /**
     * 从服务器接收到数据后调用
     * @param channelHandlerContext
     * @param byteBuf
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) throws Exception {
        System.out.println("Client read Server data...");
        //服务端返回数据后
        ByteBuf msg = byteBuf;
        byte[] bytes = new byte[msg.readableBytes()];
        String str = new String(bytes, "UTF-8");
        System.out.println("Server data :"+str);
    }

    /**
     * 客户端连接服务端后被调用
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client connect start! Send Data....");
        byte[] bytes = "Query Data List".getBytes();
        ByteBuf buffer = Unpooled.buffer(bytes.length);
        //发送
        buffer.writeBytes(bytes);
        //flush
        ctx.writeAndFlush(buffer);
    }

    /**
     * 发生异常时被调用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("Client exceptionCaught...");
        //释放资源
        ctx.close();
    }
}

4.2 多Handler处理
Netty服务端
EchoServer
package com.ithzk.netty.sendorder.server;

import io.netty.bootstrap.ServerBootstrap;
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.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoServer {

    private final int port;

    public EchoServer(Integer port) {
        this.port = port;
    }

    public void start() throws InterruptedException {
        EventLoopGroup eventLoopGroup = null;
        try {
            //服务端引导类
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            //NioEventLoopGroup可以理解为一个线程池,这个线程池用来处理连接,接收数据,发送数据
            eventLoopGroup = new NioEventLoopGroup();
            //装配bootstrap
            serverBootstrap.group(eventLoopGroup).//多线程处理
                    channel(NioServerSocketChannel.class).//指定通道类型为NioServerSocketChannel
                    localAddress("localhost",port)//地址端口
                    .childHandler(new ChannelInitializer<SocketChannel>() {//设置child

                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            // inBoundHandler先注册的先执行 outBoundHandler后注册的先执行
                            socketChannel.pipeline().addLast(new EchoInServerHandler1());//注册handler
                            socketChannel.pipeline().addLast(new EchoOutServerHandler1());//注册handler
                            socketChannel.pipeline().addLast(new EchoOutServerHandler2());//注册handler
                            socketChannel.pipeline().addLast(new EchoInServerHandler2());//注册handler
                        }
                    });
            //绑定服务器直到绑定完成。调用sync方法会阻塞直到服务器绑定完成,然后服务器等待通道关闭
            ChannelFuture channelFuture = serverBootstrap.bind().sync();
            System.out.println("Start Listening port:"+channelFuture.channel());
            channelFuture.channel().closeFuture().sync();
        }finally {
            eventLoopGroup.shutdownGracefully().sync();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new EchoServer(20000).start();
    }
}

EchoInServerHandler1
package com.ithzk.netty.sendorder.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoInServerHandler1 extends ChannelInboundHandlerAdapter{


    /**
     * 从客户端接收到数据后调用
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("in1");
        //通知下一个InBoundHandler
        ctx.fireChannelRead(msg);
    }

    /**
     * 读取数据结束
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 发生异常时被调用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

EchoInServerHandler2
package com.ithzk.netty.sendorder.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.util.Date;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoInServerHandler2 extends ChannelInboundHandlerAdapter{


    /**
     * 从客户端接收到数据后调用
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("in2");
        //读取数据
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String str = new String(bytes, "UTF-8");
        System.out.println("Server receive Client data:"+str);
        //向客户端发送数据
        System.out.println("Server send data To Client...");
        ByteBuf byteBuf = Unpooled.copiedBuffer("Hello".getBytes());
        通知下一个outBoundHandler out2
        ctx.write(byteBuf);
    }

    /**
     * 读取数据结束
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 发生异常时被调用
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

EchoOutServerHandler1
package com.ithzk.netty.sendorder.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoOutServerHandler1 extends ChannelOutboundHandlerAdapter {

    /**
     * 向客户端发送消息
     * @param ctx
     * @param msg
     * @param promise
     * @throws Exception
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("out1");
        ByteBuf resp = Unpooled.copiedBuffer("HE".getBytes());
        //通知下一个outBoundHandler out1
        ctx.write(resp);
        ctx.flush();
    }
}

EchoOutServerHandler2
package com.ithzk.netty.sendorder.server;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;

import java.util.Date;

/**
 * @author hzk
 * @date 2018/4/2
 */
public class EchoOutServerHandler2 extends ChannelOutboundHandlerAdapter {

    /**
     * 向客户端发送消息
     * @param ctx
     * @param msg
     * @param promise
     * @throws Exception
     */
    @Override
    public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
        System.out.println("out2");
        ByteBuf resp = Unpooled.copiedBuffer("HE2".getBytes());
        ctx.write(resp);
        ctx.flush();
    }
}

 Netty客户端同4.1一致,该注册顺序下,执行结果如下:
这里写图片描述
 Netty中,可以注册多个handler。ChannelInboundHandler按照注册的先后顺序执行,ChannelOutboundHandler
按照注册的先后顺序逆序执行。
这里写图片描述

4.3 Netty总结

 在使用Handler的过程中,需要注意以下几点:

  1. ChannelInboundHandler之间的传递,通过调用ctx.fireChannelRead(msg)实现,调用
    ctx.write(msg)将传递到ChannelOutboundHandler
  2. ctx.write()执行后,需要调用flush才能使其立即执行
  3. ChannelOutboundHandler在注册的时候需要放在最后一个ChannelInboundHandler之前,
    否则无法传递到ChannelOutboundHandler(即流水线pipeline中的outhandler不能放在最后否则不生效)
  4. Handler的消费处理放在最后一个处理
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值