netty大名鼎鼎,现在应用广泛,本文主要介绍netty的概述,API以及几个小demo,希望能记录自己的学习笔记,分享大家,共同进步。
1.概述
Netty是由JBOSS提供的一个Java开源框架,提供异步的、基于事件驱动的网络应用程序框架,用以快速开发高性能、高可靠性的网络IO程序。
作为当前最流行的NIO框架,Netty在互联网领域、大数据分布式计算领域、游戏行业、通信行业等获得广泛应用,Elasticsearch、Dubbo框架内部都采用Netty。
相关概念
异步
线程同步、异步都是相对的,请求或执行过程中,如果会阻塞等待,就是同步操作,反之就是异步。
核心架构
如下图所示,核心 和传输服务以及支持的各种协议
核心:
- 可扩展的事件模型
- 统一的通信api,简化通信编码
- 零拷贝机制和丰富的字节缓冲区
传输服务
- 支持socket和datagram(数据报)
- http传输服务
- in-VM Pipe(管道协议,是jvm的一种进程)
协议支持
- http和websocket
- SSL安全套接字协议支持
- Google protobuf(序列化框架)
- 支持zlib、gzip压缩
- 支持大文件的传输
- RTSP(实时流传输协议)
- 支持二进制协议并提供完整的单元测试
2. Netty的整体设计
1. Reactor模型
Reactor模型,不是java或netty专属的,而是一种并发编程思想,具有指导意义。
该模型定义三种角色
- Reactor: 负责监听和分配事件,将IO事件分派给对应的handler,新的事件包含连接建立就绪、读就绪、写就绪等。
- Accept:处理客户端新连接,并分派请求到处理器链中
- Handler:将自身与事件绑定,执行非阻塞读/写任务,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel
Nio下reactor单线程
所有的接收连接,处理数据的操作都在一个线程中完成,性能有瓶颈。
单reactor-单线程
随着业务量增大,演化出分工明确的reactor、handler、acceptor,但还是单reactor
单reactor-多线程
Nio下reactor多线程
将耗时的数据编解码、运算操作放入线程池,提升了性能
对应模型
缺点:仍无法应对大量客户端的请求,客户端请求仍需排队,导致部分请求的响应时间过长
主从reactor-多线程
主从多线程,对服务器来说,接收客户端的连接较为重要,因此,单独拆分个线程执行
主从reactor的工作模式
- reactor主线程MainReactor对象通过select监听客户端连接事件,收到事件,通过Acceptor处理client连接事件
- 当Acceptor处理完client连接事件后,MainReactor将连接分配给SubReactor,SubReactor监听后续IO事件
- SubReactor等连接加入到自己的连接队列后进行监听,并创建Handler对各种事件处理
- 当连接上有新的事件发生时,SubReactor调对应的Handler处理
- Handler通过read从连接上读取请求数据,将请求数据分发给worker线程池处理业务
- worker线程池分配独立线程完成业务处理,将处理结果返回Handler,Handler通过send向client发送响应数据
- 一个MainReactor可对应多个SubReactor
优势明显,可应对更高的并发请求,被nginx、memcached和netty等运用
也叫做服务器的1+M+N线程模式,即采用1个(或多个,1表示相对较少)连接建立线程+M个IO线程+N个业务处理线程。
netty中的Reactor实现
netty对三种模式都支持,一般在server端采用主从架构模型
另一种演进思路:单线程-线程池-netty的多路复用
单线程
服务器端用一个线程通过多路复用搞定所有的IO操作(包括连接、读写等),编码简单,清晰明了,如果客户端连接数量较多,无法支撑,之前的NIO案例就属于该模型。
线程池模型
服务器端采用一个线程专门处理客户端连接请求,采用一个线程池负责IO操作,在绝大多数场景中,该模型都能满足使用。
Netty模型
比较类似于上面的线程池模型,netty抽象出两组线程池,BossGroup专门负责接收客户端的链接,WorkerGroup专门负责网络读写操作,NioEventLoop表示一个不断循环执行处理任务的线程,每个NioEventLoop都有一个selector,用于监听绑定在其上的socket网络通道,NioEventLoop内部采用串行化设计(一条龙服务),从消息的读取->解码->处理->编码->发送,始终由IO线程NioEventLoop负责。
- 一个NioEventLoopGroup下包含多个NioEventLoop
- 每个NioEventLoop中包含一个Selector,一个taskQueue
- 每个NioEventLoop的Selector上可以注册监听多个NioChannel
- 每个NioChannel只会绑定在唯一的NioEventLoop上。
- 每个NioChannel都绑定有一个自己的ChannelPipeline
2. 异步模型
Future Callback和Handler
Netty的异步模型是建立在future和callback之上的。callback我们都知道,future的核心思想是:假设一个方法fun,可能是个耗时操作,在调用fun时,立马返回一个future,后续通过future监控fun的处理过程。
小栗子:网上外卖,手机可以监控外卖小哥路径。
使用netty编程时,拦截操作和转换出入站数据只需要提供callback或者利用future即可。使得链式操作简单高效,利于编写可重用的、通用的代码,将业务逻辑从网络基础应用编码中分离出来、解脱出来。
3. 核心API
ChannelHandler及其实现类
channelHandler接口定义了许多事件处理的方法,通过重写这些方法实现具体业务逻辑。API关系如图
我们经常需要自定义一个Handler类去继承ChannelInboundHandlerAdapter,然后重写相应方法实现业务逻辑,下边看一般需要重写哪些方法:
public void channelActive(ChannelHandlerContext ctx):
通道就绪事件public void channelRead(ChannelHandlerContext ctx,Object msg):
通道读取数据事件public void channelReadComplete(ChannelHandlerContext ctx):
数据读取完毕事件public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause):
通道发生异常事件
pipeline和ChannelPipeline
channelPipeline是一个Handler的集合,负责处理和拦截inbound或者outbound的事件和操作,相当于一个贯穿Netty的链。
ChannelPipeline addFirst(ChannelHandler... handlers):
把一个业务处理类(handler)添加到链中的第一个位置ChannelPipeline addLast(ChannelHandler... handlers):
把一个业务处理类(handler)添加到链中的最后一个位置
ChannelHandlerContext
这是事件处理器上下文对象,Pipeline链中的实际处理节点。每个处理节点ChannelHandlerContext中包含一个具体的事件处理器ChannelHandler,同时ChannelHandlerContext中也绑定了对应的pipeline和Channel的信息,方便对ChannelHandler进行调用。常用方法如下
ChannelFuture close():
关闭通道ChannelOutboundInvoker flush():
刷新ChannelFuture writeAndFlush(Object msg):
将数据写到ChannelPipeline中当前ChannelHandler的下一个ChannelHandler开始处理(出站)
ChannelOption
netty在创建Channel实例后,一般都需要设置ChannelOption参数,ChannelOption是Socket的标准参数,非netty独创。常用参数配置:
ChannelOption.SO_BACKLOG:
对应TCP/IP协议listen函数的backlog参数,初始化服务器可连接队列大小。服务器端处理客户端连接请求是顺序处理的,同一时间只能处理一个客户端连接。多个客户端来的时候,服务器端将不能处理的客户端连接请求放在队列中等待处理,backlog参数指定队列的大小。ChannelOption.SO_KEEPALIVE:
一直保持连接活动状态
ChannelFuture
表示Channel的异步IO操作的结果,在netty中所有的IO操作都是异步的,IO的调用会直接返回,调用者不能立刻获取结果,但可以通过ChannelFuture获取IO操作的处理状态。常用方法:
Channel channel():
返回当前正在进行IO操作的通道ChannelFuture sync():
等待异步操作执行完毕
EventLoopGroup及其实现类NioEventLoopGroup
EventLoopGroup是一组EventLoop的抽象,netty为了更好的利用多核CPU资源,一般会有多个EventLoop同时工作,每个EventLoop维护一个Selector实例。
EventLoopGroup提供next接口,可以从组里面按照一定规则获取其中一个EventLoop来处理任务。在Netty服务器端编程中,一般都需要提供两个EventLoopGroup。例如:BossEventLoopGroup和WorkerEventLoopGroup
通常一个服务端口即一个ServerSocketChannel对应一个Selector和一个EventLoop线程。BossEventLoop负责接收客户端的连接并将SocketChannel交给WorkerEventLoopGroup来进行IO操作。
BossEventLoopGroup通常是一个单线程的EventLoop,EventLoop维护一个注册了ServerSocketChannel的Selector实例,BossEventLoop不断轮询Selector将连接事件分离出来,通常是OP_ACCEPT事件,然后将接收到的SocketChannel交给WorkerEventLoopGroup,WorkerEventLoopGroup会由next选择其中一个EventLoopGroup将这个SocketChannel注册到其维护的Selector并对后续的IO事件处理。
常用方法:
public NioEventLoopGroup():
构造方法public Future<?>shutdownGracefully():
断开连接,关闭线程
ServerBootstrap和Bootstrap
ServerBootstrap是netty中的服务器端启动助手,通过它可以完成服务器端的各种配置,Bootstrap是netty中的客户端启动助手,通过它客户端的各种配置。常用方法:
public ServerBootstrap group(EventLoopGroup parentGroup,EventLoopGroup childGroup):
该方法用于服务器端,用来设置两个EventLooppublic B group(EventLoopGroup group):
用于客户端,用来设置一个EventLooppublic B channel(Class<? extends C> channelClass):
用于设置一个服务器端的通道实现public <T> B option(ChannelOption<T> option,T value):
用来给ServerChannel添加配置public <T> ServerBootstrap childOption(ChannelOption<T> childOption,T value):
用来给接收到的通道添加配置public ServerBoootstrap childHandler(ChannelHandler childHandler):
用来设置业务处理类(早定义的handler)public ChannelFuture bind(int inetPort):
用于服务器端,用来设置占用的端口号public ChannelFuture connect(String inetHost,int inetPort):
该方法用于客户端,用来连接服务器端
Unpooled类
这是netty提供的一个专门用来操作缓冲区的工具类,常用方法:
public static ByteBuf copiedBuffer(ChanSequence string,Charset charset):
通过给定的数据和字符编码返回一个ByteBuf对象(类似NIO的ByteBuffer对象)
4. 入门案例
0. pom文件的准备
...
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.8.Final</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.2</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<showWarnings>true</showWarnings>
</configuration>
</plugin>
</plugins>
</build>
1. basic demo
netty一般需要4个类,NettyClient NettyClientHandler(业务处理类) NettyServer NettyServerHandler(业务处理类)
NettyServerHandler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// 读取数据事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Server: "+ctx);
ByteBuf buf = (ByteBuf)msg; //需要强转为ByteBuf,缓冲区
System.out.println("客户端发来的消息:"+buf.toString(CharsetUtil.UTF_8));
}
// 数据读取完毕事件
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(Unpooled.copiedBuffer("就是没钱",CharsetUtil.UTF_8));
}
// 异常发生事件
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close(); //关闭上下文,上下文是所有信息的汇总
super.exceptionCaught(ctx, cause);
}
}
NettyServer
public class NettyServer {
public static void main(String[] args) throws Exception {
// 1. 创建一个线程组:接收客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 2. 创建一个线程组:处理网络操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 3. 创建服务器端启动助手来配置参数
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//5.使用NioServerSocketChannel作为服务器端通道的实现
.option(ChannelOption.SO_BACKLOG,128)//6.设置线程队列中等待连接的个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//7.保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//8.创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {//9.往pipeline链添加自定义的handler类
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println(".......Server is ready......");
ChannelFuture cf = b.bind(9999).sync();//绑定端口 非阻塞
System.out.println(".......Server is starting.........");
// 11.关闭通道,关闭线程组
cf.channel().closeFuture().sync();//异步
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
// 通道就绪事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("Client: "+ctx);
ctx.writeAndFlush(Unpooled.copiedBuffer("老板,还钱吧", CharsetUtil.UTF_8));
}
// 读取数据事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
System.out.println("服务器端发来的消息:"+buf.toString(CharsetUtil.UTF_8));
}
}
NettyClient
public class NettyClient {
public static void main(String[] args) throws Exception {
// 1. 创建一个线程组
EventLoopGroup group = new NioEventLoopGroup();
// 2. 创建客户端的启动助手,完成相关配置
Bootstrap b = new Bootstrap();
b.group(group) // 3.设置线程组
.channel(NioSocketChannel.class) //4.设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {// 5.创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new NettyClientHandler());//6.往pipeline链中添加自定义的handler
}
});
System.out.println("........Client is ready............");
// 7.启动客户端去连接服务器端,connect方法是异步的 ,sync方法是同步阻塞的
ChannelFuture cf = b.connect("127.0.0.1",9999).sync();
// 8.关闭连接(异步非阻塞)
cf.channel().closeFuture().sync();
}
}
2. 网络聊天demo
ChatServerHandler
业务处理类,继承ChannelInboundHandler
的子类SimpleChannelInboundHandler<String>
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
public static List<Channel> channels = new ArrayList<>();
// 通道就绪
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
Channel inChannel = ctx.channel();
channels.add(inChannel);
System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"上线");
}
// 通道未就绪
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Channel inChannel = ctx.channel();
channels.remove(inChannel);
System.out.println("[Server]:"+inChannel.remoteAddress().toString().substring(1)+"离线");
}
// 读取数据
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
Channel inChannel = ctx.channel();
for (Channel channel : channels) {
if(channel!=inChannel){
channel.writeAndFlush("["+inChannel.remoteAddress().toString().substring(1)+"]说: "+s+"\n");
}
}
}
}
ChatServer
注意:往pipeline链中添加处理字符串的编码器和解码器,加入到pipeline链会自动工作,不用人工处理ByteBuf
public class ChatServer {
private int port; //服务器端端口号
public ChatServer(int port){
this.port = port;
}
public void run() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 往pipeline链中添加一个解码器
pipeline.addLast("decoder",new StringDecoder());
// 往pipeline链中添加一个编码器,字符串->二进制
pipeline.addLast("encoder",new StringEncoder());
// 往pipeline链中添加自定义的handler业务处理类
pipeline.addLast(new ChatServerHandler());
}
});
System.out.println("Netty Chat Server启动...");
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
}finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("Netty Chat Server关闭");
}
}
public static void main(String[] args) throws Exception {
new ChatServer(9999).run();
}
}
ChatClientHandler
public class ChatClientHander extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
System.out.println(s.trim());
}
}
ChatClient
字符串的编解码同server
public class ChatClient {
private final String host;//服务器端IP地址
private final int port;//服务器端端口号
public ChatClient(String host,int port){
this.host = host;
this.port = port;
}
public void run(){
EventLoopGroup group = new NioEventLoopGroup();
try{
Bootstrap bootstrap = new Bootstrap()
.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 往pipeline链中添加一个解码器
pipeline.addLast("decoder",new StringDecoder());
// 往pipeline链中添加一个编码器,字符串->二进制
pipeline.addLast("encoder",new StringEncoder());
// 往pipeline链中添加自定义的handler业务处理类
pipeline.addLast(new ChatClientHander());
}
});
ChannelFuture cf = bootstrap.connect(host,port).sync();
Channel channel = cf.channel();
System.out.println("-------"+channel.localAddress().toString().substring(1)+"-----");
Scanner sc = new Scanner(System.in);
while(sc.hasNextLine()){
String msg = sc.nextLine();
channel.writeAndFlush(msg+"\r\n");
}
}catch (Exception e){
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new ChatClient("127.0.0.1",9999).run();
}
}
5. 编解码
1. 概述
在编写网络应用程序时需要注意codec(编解码器),因为数据网络传输的时候是二进制字节码数据,而拿到的目标数据往往不是字节码数据,因此发送的时候要编码,接收的时候要解码。
codec的组成部分有两个:decoder和encoder。其实Java的序列化就可以作为codec,但是缺陷太多:
- 无法跨语言
- 序列化后体积太大,是二进制编码的5倍多。
- 序列化性能太低。
netty提供的解码器:
- StringDecoder,对字符串数据进行解码
- ObjectDecoder,对java对象进行解码
- …
编码器对应解码器
netty自带的ObjectDecoder和ObjectEncoder可以用来实现POJO对象或各种业务对象的编码和解码,但是内部使用的还是java序列化技术。
2. Google的Protobuf
谷歌开源项目,特点:
- 支持跨平台、多语言(支持绝大多数语言,如C++、C#、Java、python等)
- 高性能、高可靠性
- 使用protobuf编译器自动生成代码,Protobuf是将类的定义使用.proto文件进行描述,然后通过protoc.exe编译器根据.proto自动生成.java文件
目前使用netty开发时,经常会结合Protobuf作为codec去使用。可以用idea的插件,也可以在命令行直接编译器生成java文件,推荐后者。
具体用法:
1. pom文件引入坐标
<dependencies>
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.8.Final</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.6.1</version>
</dependency>
</dependencies>
2. 编写proto文件
假设处理的是图书信息
Book.proto
syntax = "proto3";
option java_outer_classname = "BookMessage";
message Book{
int32 id = 1;
string name = 2;
}
- 设置版本号
- 设置生成的Java类名
- 内部类的类名,真正的POJO
- 设置类中的属性,等号后是序号,不是属性值
3. 通过protoc.exe根据描述文件生成Java类
BookMessage.java
package cn.itcast.netty.codec;// Generated by the protocol buffer compiler. DO NOT EDIT!
// source: Book.proto
public final class BookMessage {
private BookMessage() {}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistryLite registry) {
}
public static void registerAllExtensions(
com.google.protobuf.ExtensionRegistry registry) {
registerAllExtensions(
(com.google.protobuf.ExtensionRegistryLite) registry);
}
// ...
private Book(com.google.protobuf.GeneratedMessageV3.Builder<?> builder) {
super(builder);
}
private Book() {
id_ = 0;
name_ = "";
}
// ...
}
将BookMessage复制到同一个包下。
5. netty中使用
客户端和服务器端添加业务处理类时使用
NettyClient
public class NettyClient {
public static void main(String[] args) throws Exception {
// 1. 创建一个线程组
EventLoopGroup group = new NioEventLoopGroup();
// 2. 创建客户端的启动助手,完成相关配置
Bootstrap b = new Bootstrap();
b.group(group) // 3.设置线程组
.channel(NioSocketChannel.class) //4.设置客户端通道的实现类
.handler(new ChannelInitializer<SocketChannel>() {// 5.创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast("encoder",new ProtobufEncoder());
socketChannel.pipeline().addLast(new NettyClientHandler());//6.往pipeline链中添加自定义的handler
}
});
System.out.println("........Client is ready............");
// 7.启动客户端去连接服务器端,connect方法是异步的 ,sync方法是同步阻塞的
ChannelFuture cf = b.connect("127.0.0.1",9999).sync();
// 8.关闭连接(异步非阻塞)
cf.channel().closeFuture().sync();
}
}
NettyClientHandler
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
// 通道就绪事件
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
BookMessage.Book book = BookMessage.Book.newBuilder().setId(1).setName("Java23种设计模式").build();
ctx.writeAndFlush(book);
}
}
NettyServer
public class NettyServer {
public static void main(String[] args) throws Exception {
// 1. 创建一个线程组:接收客户端连接
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 2. 创建一个线程组:处理网络操作
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 3. 创建服务器端启动助手来配置参数
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup,workerGroup)//设置两个线程组
.channel(NioServerSocketChannel.class)//5.使用NioServerSocketChannel作为服务器端通道的实现
.option(ChannelOption.SO_BACKLOG,128)//6.设置线程队列中等待连接的个数
.childOption(ChannelOption.SO_KEEPALIVE,true)//7.保持活动连接状态
.childHandler(new ChannelInitializer<SocketChannel>() {//8.创建一个通道初始化对象
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {//9.往pipeline链添加自定义的handler类
socketChannel.pipeline().addLast("decoder",new ProtobufDecoder(BookMessage.Book.getDefaultInstance()));
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
System.out.println(".......Server is ready......");
ChannelFuture cf = b.bind(9999).sync();//绑定端口 非阻塞
System.out.println(".......Server is starting.........");
// 11.关闭通道,关闭线程组
cf.channel().closeFuture().sync();//异步
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
NettyServerHandler
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
// 读取数据事件
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
BookMessage.Book book = (BookMessage.Book)msg;
System.out.println("客户端发来数据: "+book.getName());
}
}
6. 自定义RPC
1. 概述
RPC(Remote Procedure Call),即远程过程调用。是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络实现的技术。常见RPC框架有:阿里的Dubbo,Spring的Spring Cloud,Google的grpc等。
实现步骤
- 服务消费方client以本地调用方式调用服务
- client stub接收到调用后负责将方法、参数等封装成能够进行网络传输的消息体
- client stub将消息进行编码并发送到服务端
- server stub收到消息后进行解码
- server stub根据解码结果调用本地的服务
- 本地服务server执行并将结果返回给本地的服务
- server stub将返回导入结果进行编码并发送到消费方
- client stub接收消息并进行解码
- client得到结果
RPC的目标就是讲2-8步骤封装,用户无需关心这些细节,可以像调用本地方法一样即可完成远程服务调用。
基于netty我们diy一个RPC
2. 结构设计
- Client:两个接口+一个包含main方法的测试类
- Client Stub:一个客户端代理类+一个客户端业务处理类
- Server:两个接口+两个实现类
- Server Stub:一个网络处理服务器+一个服务器业务处理类
注意:服务调用方的接口必须跟服务提供方的接口保持一致(包路径可以不一致)
最终要实现的目标:在TestNettyRPC中远程调用HelloRPCImpl或HelloNettyImpl中的方法
3. 代码实现
Client部分
public interface HelloNetty {
String hello();
}
public interface HelloRPC {
String hello(String name);
}
public class TestNettyRPC {
public static void main(String[] args) {
// 1.第一次远程调用
HelloNetty helloNetty = (HelloNetty) NettyRPCProxy.create(HelloNetty.class);
System.out.println(helloNetty.hello());
// 2. 第二次远程调用
HelloRPC helloRPC = (HelloRPC)NettyRPCProxy.create(HelloRPC.class);
System.out.println(helloRPC.hello("RPC"));
}
}
Client Stub
客户端代理类
public class NettyRPCProxy {
// 根据接口创建代理对象
public static Object create(Class target){
return Proxy.newProxyInstance(target.getClassLoader(), new Class[]{target}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 封装ClassInfo
ClassInfo classInfo = new ClassInfo();
classInfo.setClassName(target.getName());
classInfo.setMethodName(method.getName());
classInfo.setObjects(args);
classInfo.setTypes(method.getParameterTypes());
// 开始用Netty发送数据
EventLoopGroup group = new NioEventLoopGroup();
ResultHandler resultHandler = new ResultHandler();
try{
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 编码器
pipeline.addLast("encoder",new ObjectEncoder());
// 解码器 构造方法第一个参数设置二进制数据最大字节数;第二个设置具体使用哪个类解析器
pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
// 客户端业务处理类
pipeline.addLast("handler",resultHandler);
}
});
ChannelFuture future = b.connect("127.0.0.1",9999).sync();
future.channel().writeAndFlush(classInfo).sync();
future.channel().closeFuture().sync();
}finally {
group.shutdownGracefully();
}
return resultHandler.getResponse();
}
});
}
}
客户端业务处理类
public class ResultHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse(){
return response;
}
// 读取完服务器端返回的数据(远程调用的结果)
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
response = msg;
ctx.close();
}
}
Server部分
public interface HelloNetty {
String hello();
}
public class HelloNettyImpl implements HelloNetty {
@Override
public String hello() {
return "hello,netty";
}
}
public interface HelloRPC {
String hello(String name);
}
public class HelloRPCImpl implements HelloRPC {
@Override
public String hello(String name) {
return "hello,"+name;
}
}
Server Stub
封装类信息
public class ClassInfo implements Serializable {
private static final long serialVersionUID = 1L;
private String className;//类名
private String methodName;//方法名
private Class<?>[] types;//参数类型
private Object[] objects;//参数列表
public static long getSerialVersionUID() {
return serialVersionUID;
}
// ... 各种getter/setter
网络处理服务器
public class NettyRPCServer {
private int port;
public NettyRPCServer(int port){
this.port = port;
}
public void start(){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.localAddress(port).childHandler(
new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 编码器
pipeline.addLast("encoder",new ObjectEncoder());
// 解码器
pipeline.addLast("decoder",new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
// 服务器端业务处理类
pipeline.addLast(new InvokeHandler());
}
});
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println("........server is ready.........");
}catch (Exception e){
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new NettyRPCServer(9999).start();
}
}
服务器端业务处理类
public class InvokeHandler extends ChannelInboundHandlerAdapter {
// 得到某接口下某个实现类的名字
private String getImplClassName(ClassInfo classInfo) throws Exception{
// 服务方接口和实现类的包路径
String interfacePath = "cn.itcast.rpc.server";
int lastDot = classInfo.getClassName().lastIndexOf(".");
String interfaceName = classInfo.getClassName().substring(lastDot);
Class superClass = Class.forName(interfacePath+interfaceName);
Reflections reflections = new Reflections(interfacePath);
//得到某接口下的所有实现类
Set<Class> ImplClassSet = reflections.getSubTypesOf(superClass);
if(ImplClassSet.size()==0){
System.out.println("未找到实现类");
return null;
}else if(ImplClassSet.size()>1){
System.out.println("找到多个实现类,未明确使用哪一个");
return null;
}else{
// 把集合转换为数组
Class[] classes = ImplClassSet.toArray(new Class[0]);
return classes[0].getName();//得到实现类的名字
}
}
// 读取客户端发来的数据并通过反射调用实现类的方法
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ClassInfo classInfo = (ClassInfo)msg;
Object clazz = Class.forName(getImplClassName(classInfo)).newInstance();
Method method = clazz.getClass().getMethod(classInfo.getMethodName(),classInfo.getTypes());
// 通过反射调用实现类的方法
Object result = method.invoke(clazz,classInfo.getObjects());
ctx.writeAndFlush(result);
}
}