TCP/IP学习笔记八:RPC(Netty和Spring)实现webServer框架
标签(空格分隔): RPC webService
基于RPC和NIO实现webService框架
webService的核心就是解决远程调用,现在基于RPC实现这种远程调用。
主要使用的是动态代理和反射机制实现客户端基于接口的远程调用。使用Netty和Spring进行开发RPC架构的webService。
详细开发思路和步骤在代码中以注释的形式展现。
说明:本实例没有考虑服务器端的接口有多个实现类的情况。
一、服务器端
将服务的启动(NIOServerBootstrap)交给Spring管理,Spring启动时进行初始化服务。
1.NIOServerBootstrap中的动态数据(port,channelHandlerAdapter)通过构造注入的方式进行传递。
2.NIOServerBootstrap 的start方法用子线程启动。如果不使用线程开启,会导致主线程阻塞。因为我们将启动引导交给Spring管理,在Spring初始化时开启,NIOServerBootstrap的启动会导致初始化阻塞,无法初始化其他启动项。
3.ServerRequestResponseHander实现ApplicationContextAware 拿到Spring工厂,实现ApplicationContextAwareSpring会自动注入工厂到ApplicationContext中
4.ServerRequestResponseHander的channelRead方法中,判断返回值是否为空(有些方法有返回值,有些方法返回值为void。result为空返回给客户端会报空指针)引入一个NullWritable类(单例【饿汉式】)。
服务的启动引导类
package com.motui.rpc.Server; import com.motui.rpc.common.ObjectCodec; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandlerAdapter; 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.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; /** * 服务的引导类 * @author MOTUI * */ public class NIOServerBootstrap { private int port; //端口号 private ChannelHandlerAdapter channelHandlerAdapter; //IO事件处理类 private ServerBootstrap serverBootstrap; //NIOServerSocketChannel的服务引导 private EventLoopGroup boss; //线程池 boss(请求转发) worker(IO事件处理) private EventLoopGroup worker; //worker(IO事件处理) /** * 构造函数 * 初始化参数 * @param port 服务器端口号 * @param channelHandlerAdapter IO事件处理类 */ public NIOServerBootstrap(int port, ChannelHandlerAdapter channelHandlerAdapter) { super(); this.port = port; this.channelHandlerAdapter = channelHandlerAdapter; //初始化参数 serverBootstrap = new ServerBootstrap(); boss = new NioEventLoopGroup(); worker = new NioEventLoopGroup(); //绑定线程池 serverBootstrap.group(boss, worker); //设置服务类 serverBootstrap.channel(NioServerSocketChannel.class); } /** * 开启服务 * 如果不使用线程开启,会导致主线程阻塞。 * 因为我们将启动引导交给Spring管理,在Spring初始化时开启,NIOServerBootstrap的启动会导致初始化阻塞, * 无法初始化其他启动项。 */ public void start(){ new Thread(){ @Override public void run() { try { //绑定IO处理事件 serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>(){ @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldPrepender(2)); ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)); ch.pipeline().addLast(new ObjectCodec()); //将channelHandlerAdapter添加到队列 ch.pipeline().addLast(NIOServerBootstrap.this.channelHandlerAdapter); } }); //绑定服务器启动端口 System.out.println("服务器开始监听:"+NIOServerBootstrap.this.port+"端口···"); ChannelFuture future = serverBootstrap.bind(NIOServerBootstrap.this.port).sync(); //等待关闭 future.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } } }.start(); } /** * 关闭线程池 * * 关闭跟随工厂的关闭(销毁)而关闭 */ public void close(){ worker.shutdownGracefully(); boss.shutdownGracefully(); } }
事件处理类
package com.motui.rpc.Server; import java.lang.reflect.Method; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import com.motui.rpc.MethodInvokeMeta; import com.motui.rpc.NullWritable; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandler.Sharable; /** * 服务器端 事件处理类 * * 实现ApplicationContextAware 拿到Spring工厂, * 实现ApplicationContextAwareSpring会自动注入工厂到ApplicationContext中 * @Sharable 使用该注解式为了告诉Netty这个类是安全的。Sharable注解式Netty提供 * * @author MOTUI * */ @Sharable public class ServerRequestResponseHander extends ChannelHandlerAdapter implements ApplicationContextAware { private ApplicationContext applicationContext; @SuppressWarnings("unchecked") @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //客户端传递的参数就是MethodInvokeMeta类型 MethodInvokeMeta invokeMeta = (MethodInvokeMeta) msg; //利用反射机制调用具体方法 //获取目标对象 invokeTargetClass Object targetObject = applicationContext.getBean(invokeMeta.getInvokeTargetClass()); //获取目标方法对象 Method method = targetObject.getClass().getDeclaredMethod(invokeMeta.getMethodName(),invokeMeta.getParameterTypes()); //调用方法 Object result = method.invoke(targetObject, invokeMeta.getArgs()); //判断返回值是否为空(有些方法有返回值,有些方法返回值为void。result为空返回给客户端会报空指针) if (result instanceof NullWritable) { ctx.writeAndFlush(NullWritable.get()); }else{ ctx.writeAndFlush(result); } //关闭连接【必须关闭,不然客户端不能接收到返回的数据】 ctx.close(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.err.println("服务器连接异常···异常信息:"+cause.getMessage()); } @Override public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext = applicationContext; } }
二、客户端
客户端只知道服务器端的接口,并不知道具体的实现。 我们都知道动态代理有两种方式:JDK的Proxy基于接口的代理模式、Spring的CGLIB基于实现类的代理模式。因为现在我们只知道服务器端暴露的接口,只能选择使用JDK的Proxy基于接口的代理模式。 这时候就想到我们如何传递参数和接收参数能够调用服务器端的具体实现呢?我们会想到反射机制,服务器端通过客户端发送的参数使用反射调用具体的实现,但是我们都需要哪些参数呢?首先我们需要知道调用哪个类哪个方法、这个类的类型、这个类的方法的参数、这个类的方法的参数类型?通过分析我们封装一个参数类:MethodInvokeMeta用于服务器端和客户端的调用方法信息和参数传递。有了调用方法的信息就可以进行设计了。
MethodInvokeMeta类的设计
package com.motui.rpc; import java.io.Serializable; @SuppressWarnings("serial") public class MethodInvokeMeta implements Serializable { private String methodName; //方法名称 private Object[] args; //参数列表 private Class<?>[] parameterTypes; //参数类型 private Class invokeTargetClass; //哪个类 //getter/setter方法····· public MethodInvokeMeta(String methodName, Object[] args, Class<?>[] parameterTypes, Class invokeTargetClass) { super(); this.methodName = methodName; this.args = args; this.parameterTypes = parameterTypes; this.invokeTargetClass = invokeTargetClass; } public MethodInvokeMeta() { super(); } }
动态代理类的设计
实现FactoryBean接口,由Spring管理动态代理类
package com.motui.rpc.client; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import org.springframework.beans.factory.FactoryBean; import com.motui.rpc.MethodInvokeMeta; import com.motui.rpc.NullWritable; /** * 动态代理类 * * 将FactoryBeanProxy交给Spring管理,实现FactoryBean接口 * @author MOTUI * */ @SuppressWarnings("rawtypes") public class FactoryBeanProxy implements InvocationHandler, FactoryBean { private Class targetInterface; private RPCClient client; public FactoryBeanProxy(Class targetInterface, RPCClient client) { super(); this.targetInterface = targetInterface; this.client = client; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //获得方法名称 String methodName = method.getName(); //获得参数类型 Class<?>[] parameterTypes = method.getParameterTypes(); //MethodInvokeMeta包装类 MethodInvokeMeta methodInvokeMeta = new MethodInvokeMeta(methodName, args, parameterTypes, this.targetInterface); //TCP/IP发送MethodInvokeMeta对象 Object res = this.client.remoteCall(methodInvokeMeta,5); if (res instanceof NullWritable) { return null; }else{ return res; } } @Override public Object getObject() throws Exception { return Proxy.newProxyInstance(FactoryBeanProxy.class.getClassLoader(), new Class<?>[]{this.targetInterface}, this); } @Override public Class getObjectType() { return this.targetInterface; } @Override public boolean isSingleton() { return true; } }
客户端的启动引导
package com.motui.rpc.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelInitializer; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import com.motui.rpc.MethodInvokeMeta; import com.motui.rpc.common.ObjectCodec; /** * 客户端启动引导类 * @author MOTUI * */ public class RemoteRPCClient implements RPCClient { private String host; //主机地址 private int port; //服务器端口号 private Bootstrap bootstrap; //客户端引导 private NioEventLoopGroup boss; //线程池 boss(IO事件处理) /** * 构造函数 * 初始化参数 * @param host 主机地址 * @param port 服务器端口好 */ public RemoteRPCClient(String host, int port) { super(); this.host = host; this.port = port; //初始化参数 bootstrap = new Bootstrap(); boss = new NioEventLoopGroup(); //绑定线程 bootstrap.group(boss); //设置服务类 bootstrap.channel(NioSocketChannel.class); } /** * 可能由于网路延迟或者其他情况导致一次连接错误,设置连接次数retry, * 超过retry次就说明无法连接抛出异常。 */ @Override public Object remoteCall(MethodInvokeMeta methodInvokeMeta,int retry) { try { final ClientRequestResponseHander channelHandlerAdapter = new ClientRequestResponseHander(methodInvokeMeta); bootstrap.handler(new ChannelInitializer<SocketChannel>(){ @Override protected void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new LengthFieldPrepender(2)); ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(65535, 0, 2, 0, 2)); ch.pipeline().addLast(new ObjectCodec()); //将ChannelHandlerAdapter添加到队列 ch.pipeline().addLast(channelHandlerAdapter); } }); //连接服务器 ChannelFuture future = bootstrap.connect(RemoteRPCClient.this.host, RemoteRPCClient.this.port).sync(); //等待关闭 future.channel().closeFuture().sync(); return channelHandlerAdapter.getResult(); } catch (InterruptedException e) { try { if (retry == 0) { throw new RuntimeException("连接超时·····"); } retry--; return remoteCall(methodInvokeMeta, retry); } catch (Exception e1) { e1.printStackTrace(); } } return null; } @Override public void close() { boss.shutdownGracefully(); } }
客户端的事件处理
package com.motui.rpc.client; import com.motui.rpc.MethodInvokeMeta; import io.netty.channel.ChannelHandlerAdapter; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandler.Sharable; /** * 客户端 事件处理类 * @author MOTUI * */ @Sharable public class ClientRequestResponseHander extends ChannelHandlerAdapter { private MethodInvokeMeta methodInvokeMeta; private Object result; public ClientRequestResponseHander(MethodInvokeMeta methodInvokeMeta) { super(); this.methodInvokeMeta = methodInvokeMeta; } /** * 提供getter方法,将客户端接收的服务器端返回值暴露出去 * @return 服务器端返回的值 */ public Object getResult() { return result; } /** * 异常调用 */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.err.println("客户端发生异常了···异常信息:"+cause.getMessage()); } /** * 发送请求 */ @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { //发送请求 ctx.writeAndFlush(this.methodInvokeMeta); } /** * 读数据 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { this.result = msg; } }
NullWritable类
package com.motui.rpc; /** * 饿汉式 判断是否为空类 * @author MOTUI * */ public class NullWritable { private static NullWritable instance = new NullWritable(); private NullWritable(){} public static NullWritable get(){ return instance; } }
三、配置文件
RPCService.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 配置服务启动类 --> <bean class="com.motui.rpc.Server.NIOServerBootstrap" init-method="start" destroy-method="close"> <constructor-arg index="0" value="8989"/> <constructor-arg index="1" ref="serverRequestResponseHander"/> </bean> <!-- 配置请求处理类 --> <bean id="serverRequestResponseHander" class="com.motui.rpc.Server.ServerRequestResponseHander"/> <!-- 扫描组件 --> <context:component-scan base-package="com.motui.service"/> </beans>
RPCClient.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd "> <!-- 配置代理bean --> <bean id="demoService" class="com.motui.rpc.client.FactoryBeanProxy"> <constructor-arg index="0" value="com.motui.service.IDemoService"/> <constructor-arg index="1" ref="client"/> </bean> <bean id="userService" class="com.motui.rpc.client.FactoryBeanProxy"> <constructor-arg index="0" value="com.motui.service.IUserService"/> <constructor-arg index="1" ref="client"/> </bean> <bean id="client" class="com.motui.rpc.client.RemoteRPCClient"> <constructor-arg index="0" value="192.168.0.117"/> <constructor-arg index="1" value="8989"/> </bean> </beans>
四、总结
整体流程
由于webService的过度封装导致传输性能降低,虽然webService一直在改进,但是互联网的发展需求更加大,不得不寻求更好的解决方式。在JDK1.4之前java并不支持NIO网络编程,在这之前都是由C,C++占据着网络编程的传输。在JDK1.4之后java引入了NIO网络编程,弥补了原来同步阻塞的不足。渐渐Netty框架得到了很好的发展,java的网络编程得到了更好的发展。在JDK1.7之后引入NIO2.0版本增加了更多的特性。Netty是现在使用较多的一款网络编程框架。
想深入了解Netty,推荐学习【Netty权威指南】这本书,讲解的很详细。
最后附上源码链接【源码】