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權威指南】這本書,講解的很詳細。
最後附上源碼鏈接【源碼】