轻量级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);

这里写图片描述

4.高性能NIO框架-Netty

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

扫描二维码关注公众号,回复: 916476 查看本文章

以下简单的提供几个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的消费处理放在最后一个处理

猜你喜欢

转载自blog.csdn.net/u013985664/article/details/79783555