Netty实现RPC
RPC基本介绍
-
RPC(Remote Procedure Call)–远程过程调用,是一个计算机通信协议。该协议允许运行一台计算机的程序调用另一台计算机的子程序,而程序员无需额外地为这个交互作用编程
-
两个或多个应用程序都分布在不同的服务器上,它们之间的调用像是本地方法调用一样
-
-
常见的RPC框架有:比较知名的有阿里的Dubbo、google的gRPC,Go语言的rpcx、Apache的thrift,Spring旗下的Spring Cloud
RPC调用流程说明
-
-
流程说明
- 服务消费放(client)以本地调用方式调用服务
- client stub接收调用后负责将方法、参数等封装成能够进行网络传输的消息体
- client stub将消息进行编码并发送到服务端
- server stub收到消息后进行解码
- server stub根据解码结果调用本地的服务
- 本地服务执行并将结果返回给server stub
- server stub将返回导入结果进行编码并发送至消费放
- client stub接收到消息并进行解码
- 服务消费放(client)得到结果
-
小结:RPC的目标就是将2-8这些步骤都封装起来,用户无需关系这些细节,可以像调用本地方法一样即可完成远程服务调用
使用Netty实现dubbo RPC(基于Netty)
-
需求说明
- dubbo底层使用了Netty作为网络通讯框架,要求用Netty实现一个简单的RPC框架
- 模仿dubbo,消费者和提供者约定接口和协议,消费者远程调用提供者,提供者返回一个字符串,消费者打印提供者返回数据。底层网络通信使用Netty 4.x
-
设计说明
- 创建一个接口,定义抽象方法。用于消费者和提供者之间的约定。
- 创建一个提供者,该类需要监听消费者的请求,并按照约定返回数据。
- 创建一个消费者,该类需要透明的调用自己不存在的方法,内部需要使用Netty请求提供者返回数据
-
代码
-
创建一个接口存根接口提供给客户端服务端使用,HelloService
-
package com.jl.java.web.dubborpc.stub; /** * 接口 * 服务提供方和消费者方都需要的 * @author jiangl * @version 1.0 * @date 2021/5/30 10:57 */ public interface HelloService { String hello(String msg); }
-
-
服务端HelloServiceImpl实现HelloService接口,供消费者调用
-
package com.jl.java.web.dubborpc.provider; import com.jl.java.web.dubborpc.stub.HelloService; /** * @author jiangl * @version 1.0 * @date 2021/5/30 10:58 */ public class HelloServiceImpl implements HelloService { /** * 当有消费方调用该方法时,就返回一个结果 * @param msg * @return */ @Override public String hello(String msg) { System.out.println("收到客户端消息="+msg); if(msg != null){ return "你好客户端,已经收到消息,["+msg+"]"; }else{ return "你好客户端,已经收到消息"; } } }
-
-
ServerBootstrap
-
package com.jl.java.web.dubborpc.provider; import com.jl.java.web.dubborpc.netty.NettyServer; /** * 会启动一个服务的提供者就是Netty Server * @author jiangl * @version 1.0 * @date 2021/5/30 11:00 */ public class ServerBootstrap { public static void main(String[] args) { NettyServer.startServer("127.0.0.1",7000); } }
-
-
NettyServer
-
package com.jl.java.web.dubborpc.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; /** * @author jiangl * @version 1.0 * @date 2021/5/30 11:01 */ public class NettyServer { /** * 编写一个方法,完成对NettyServer的初始化和启动 * @param hostname * @param port */ private static void startServer0(String hostname,int port){ NioEventLoopGroup bossGroup = new NioEventLoopGroup(1); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup,workerGroup) .channel(NioServerSocketChannel.class) .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 NettyServerHandler()); } }); ChannelFuture channelFuture = serverBootstrap.bind(hostname, port).sync(); System.out.println("服务提供方启动...开始提供符"); channelFuture.channel().closeFuture().sync(); }catch (Exception e){ }finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } public static void startServer(String hostname,int port){ startServer0(hostname,port); } }
-
-
NettyServerHandler
-
package com.jl.java.web.dubborpc.netty; import com.jl.java.web.dubborpc.provider.HelloServiceImpl; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; /** * @author jiangl * @version 1.0 * @date 2021/5/30 11:22 */ public class NettyServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //客户端发送的消息,并调用服务 System.out.println("msg="+msg); //客户端在调用服务器的api时,需要定义一个协议 //比如,要求 每次发消息是都是必须以某个字符串开头"HelloService#hello#" if(msg.toString().startsWith("HelloService#hello#")){ String result = new HelloServiceImpl().hello(msg.toString().substring(msg.toString().lastIndexOf("#") + 1)); ctx.writeAndFlush(result); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } }
-
-
ClientBootstrap
-
package com.jl.java.web.dubborpc.customer; import com.jl.java.web.dubborpc.netty.NettyClient; import com.jl.java.web.dubborpc.stub.HelloService; /** * @author jiangl * @version 1.0 * @date 2021/5/30 12:36 */ public class ClientBootstrap { //这里定义协议头 private static final String providerName = "HelloService#hello#"; public static void main(String[] args) { //创建一个消费者 NettyClient nettyClient = new NettyClient(); HelloService helloService = (HelloService) nettyClient.getBean(HelloService.class, providerName); String result = helloService.hello("你好"); System.out.println(result); } }
-
-
NettyClient
-
package com.jl.java.web.dubborpc.netty; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @author jiangl * @version 1.0 * @date 2021/5/30 12:16 */ public class NettyClient { //创建线程池 private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); private static NettyClientHandler client; //编写方法使用代理模式,获取一个代理对象 public Object getBean(final Class<?> serviceClass,final String providerName){ return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[]{serviceClass}, new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //单例 if(client == null){ initClient(); } //设置要发送给服务器端的信息 //providerName 协议头 args[0] 就是客户端调用api hello(???) ,参数 client.setPara(providerName+args[0]); return executor.submit(client).get(); } }); } //初始化客户端 private static void initClient(){ client = new NettyClientHandler(); //创建EventLoopGroup NioEventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap().group(group) .channel(NioSocketChannel.class) // .option(ChannelOption.TCP_NODELAY,true) .handler(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(client); } }); ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 7000).sync(); // channelFuture.channel().closeFuture().sync(); } catch (Exception e) { e.printStackTrace(); }finally { // group.shutdownGracefully(); } } }
-
-
NettyClientHandler
-
package com.jl.java.web.dubborpc.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import java.util.concurrent.Callable; /** * @author jiangl * @version 1.0 * @date 2021/5/30 11:46 */ public class NettyClientHandler extends ChannelInboundHandlerAdapter implements Callable { private ChannelHandlerContext context;//上下文 private String result;//返回结果 private String para;//客户端调用方法时,传入参数 /** * 被代理对象调用,发送数据给服务器->wait,等待被唤醒-> 返回结果 * @return * @throws Exception */ @Override public synchronized Object call() throws Exception { context.writeAndFlush(para); //进行wait wait();//等待channelRead 方法获取到服务器的结果后,唤醒 return result; } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //需要在其他方法会使用到ctx context = ctx; } @Override public synchronized void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { result = msg.toString(); notify(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.close(); } public String getPara() { return para; } public void setPara(String para) { this.para = para; } }
-
-