前段時間手寫了一個NIO的RPC 簡易通信,今天用netty 重寫了個RPC 通信,目前 已經可以工作,不過對於netty的有些參數還不是很清楚,文章中寫了???的地方就是不清楚的,高手知道的可以在評論中說明 一下,謝謝。
爲了方便,本文中的client和server在同一工程中,不過都是通過netty來訪問的。
另外文章中的可用服務爲了方便也是在本地的class path下搜索並保存的。
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------
現在我記錄一下重寫RPC的思路:
1.RPC 調用是在client只知道接口和方法名的情況要,通過發送給server 由server調用後返回結果的。所以我們要創建一個服務接口。
==本文中有兩個接口:IRpcHelloService, 和IRpcService, 放在包api中。
2. 有了服務接口,那就必須要有實現類,這個在實際中一定是放到server上的。
==本文中實現類RpcHelloServiceImpl, 和RpcServiceImpl, 放在包provider中。
3. 服務器要提供服務,那就要把所有的可用服務顯露出來。
==本文中由類RpcRegistry 類中的start() 通過Port 綁定在netty上來向網絡發佈服務。
==netty中必然要用到childHandler的配置,這裏面就要用到我們自定義的handler (RegistryHandler類), 重寫channelRead()方法
4.client端要請求服務,我們定義一個consumer類.在這個類中因爲client只知道 接口名,我們需要爲client端創建動態代理對象,通過代理對象來執行對應的方法.代理對象中invoke方法通過netty 向server端發送自定義的協議. 裏邊也會有自己定義的handler, 裏面也要重寫channelRead() 來處理收到的報文.
5. 在上一步驟中會有到自定義的協議,所以我們也要定義一個InvokerProtocol, 裏面肯定會接口名,方法名,方法參數.
服務器上會根據收到的接口名,找到可用的服務實現類的實例.
----------------------------------------------------------以下是代碼結構------------------------------------------------------------------------------------------
pom.xml和所有的代碼就在這裏,要練習的同學可以按以下結構構成工程,直接運行即可.
-------------
----------------------------------------------------------以下是pom.xml-------------------------------------------------------------------------------------------------------
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.netty.rpc</groupId>
<artifactId>RPC-NETTY</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.10</version>
</dependency>
</dependencies>
</project>
接口1:
package com.yubo.study.netty.rpc.api;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public interface IRpcHelloService {
public String hello(String name);
}
接口2:
package com.yubo.study.netty.rpc.api;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public interface IRpcService {
public int add(int a, int b);
public int sub(int a, int b);
}
接口實現:
package com.yubo.study.netty.rpc.provider;
import com.yubo.study.netty.rpc.api.IRpcHelloService;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcHelloServiceImpl implements IRpcHelloService {
public String hello(String name) {
return "hello: " + name;
}
}
接口實現:
package com.yubo.study.netty.rpc.provider;
import com.yubo.study.netty.rpc.api.IRpcService;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcServiceImpl implements IRpcService {
public int add(int a, int b) {
return a + b;
}
public int sub(int a, int b) {
return a -b;
}
}
RPC 自定義協議:
package com.yubo.study.netty.rpc.protocol;
import lombok.Data;
import java.io.Serializable;
import java.util.Arrays;
/**
* Created by Administrator on 2019/7/15 0015.
*/
@Data
public class InvokerProtocol implements Serializable {
private String className;
private String methodName;
private Class<?> [] paraTypes;
private Object [] values;
@Override
public String toString() {
return "InvokerProtocol{" +
"className='" + className + '\'' +
", methodName='" + methodName + '\'' +
", paraTypes=" + Arrays.toString(paraTypes) +
", values=" + Arrays.toString(values) +
'}';
}
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParaTypes() {
return paraTypes;
}
public void setParaTypes(Class<?>[] paraTypes) {
this.paraTypes = paraTypes;
}
public Object[] getValues() {
return values;
}
public void setValues(Object[] values) {
this.values = values;
}
}
服務器端發麪程序:
package com.yubo.study.netty.rpc.registry;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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;
import io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcRegistry {
private int port;
public RpcRegistry(int port) {
this.port = port;
}
public void start()
{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ChannelFuture future = null;
try{
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {// 注意這是childHandler, client用的是handler, 都要重寫initChannel方法
@Override
protected void initChannel(SocketChannel sc) throws Exception { // 這裏是寫了一個ChannelInitializer 類的匿名子類,實現了initChannel 方法,當有連接過來時,纔會調用這個函數
System.out.println("sc:" + sc);
ChannelPipeline cp = sc.pipeline();
System.out.println("111111:");
cp.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0, 4, 0, 4));// ???
System.out.println("222:");
cp.addLast(new LengthFieldPrepender(4)); // ???
System.out.println("333");
cp.addLast("encoder", new ObjectEncoder()); // ???
System.out.println("44444:");
cp.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));// ???
System.out.println("555555");
// 這個纔是最重要的, 我們處理從channel 中讀取出來的數據, 有連接過來時,就會new RegistryHandler,
// 這個對象中會從本地搜索所有可用的服務並存到map中。k: interface , v : 實現類的實例
// 當server中的netty 的channelRead()函數中收到自定義協議對象後,就會用msg中的interface Name找到 服務器上對應的實現類的實例,然後用這個實例通過反射調用對應的方法,並把結果寫到client的ctx中。
cp.addLast(new RegistryHandler());
System.out.println("6666666");
}
})
.option(ChannelOption.SO_BACKLOG, 128) // 來不及處理的連接放在緩存中的數目最大數
.childOption(ChannelOption.SO_KEEPALIVE, true); // 保持連接
future = b.bind(port).sync();
System.out.println("GP registry start listen on port:" + port);
future.channel().closeFuture().sync();// ???
}catch (Exception ex){ // 異常了就關閉所有的工作組
ex.printStackTrace();
if(null != future){
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new RpcRegistry(8083).start(); // 發佈註冊
}
}
服務器端用的handler (netty中pipline中加入的)
package com.yubo.study.netty.rpc.registry;
import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RegistryHandler extends ChannelInboundHandlerAdapter { // handler 都是繼承這個類ChannelInboundHandlerAdapter, 並重寫channelRead 和exceptionCaught()兩個方法
public static ConcurrentHashMap<String, Object> registryMap = new ConcurrentHashMap<String, Object>();
private List<String> classNames = new ArrayList<String>();
public RegistryHandler() { // RegistryHandler是用來處理client發來的請求的,所以他必須要知道哪些服務可用,所在在構造函數裏,這把所有要用的服務初始化好。
doScan("com.yubo.study.netty.rpc.provider"); // 這裏爲了方便,是直接從本地的class 路徑下搜索可用的服務,實際使用中可能是從另外的地方獲取
doRegistry();
}
public void doScan(String packagePath) {
URL url = this.getClass().getClassLoader().getResource(packagePath.replaceAll("\\.", "/")); // 通過把 xx.xx.xx 替換成/xx/xx/xx/xx
System.out.println("url:" + url);
File dir = new File(url.getFile());// /xx/xx/xx 變成 e:\xx\xx\xx
System.out.println("dir:" + dir);
for (File f : dir.listFiles()) {
if (f.isDirectory()) {
doScan(packagePath + "." + f.getName()); // 如果是目錄 ,就遞歸調用
} else {
classNames.add(packagePath + "." + f.getName().replace(".class", "").trim()); // 如果是.class文件,就把class的全名放到list中
}
}
}
public void doRegistry() {
if (classNames.size() == 0)
return;
try {
for (String className : classNames) { // 把上邊可用的所有服務(服務的實現類),全部映射成 k: 未實現的接口,v: 實現類的實例 的map 存起來
System.out.println("className:" + className);
Class<?> clazz = Class.forName(className);
Class<?> i = clazz.getInterfaces()[0];
registryMap.put(i.getName(), clazz.newInstance());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
@Override
public void channelRead (ChannelHandlerContext ctx, Object msg)throws Exception { // channel.pipeline.addLast(handler中), 加了這個handler, 那就會自動調用 channelRead()方法,這裏就是最核心的調用地方
//super.channelRead(ctx, msg);
Object result = new Object();
InvokerProtocol request = (InvokerProtocol) msg;
System.out.println("request:" + request);
if(registryMap.containsKey(request.getClassName())) // 從收到的msg中提取出接口名,方法名,方法形參,方法實參,然後找到對應的實現類的實例,用反射調用方法
{
Object clazzInstance = registryMap.get(request.getClassName());
Method method = clazzInstance.getClass().getMethod(request.getMethodName(), request.getParaTypes());
result = method.invoke(clazzInstance, request.getValues());
System.out.println("result:" + result);
ctx.writeAndFlush(result); // 將反射執行後的結果寫回給client
ctx.flush();
ctx.close();
}
}
@Override
public void exceptionCaught (ChannelHandlerContext ctx, Throwable cause)throws Exception {
// super.exceptionCaught(ctx, cause);
ctx.close();
}
}
client端的調用 程序
package com.yubo.study.netty.rpc.consumer;
import com.yubo.study.netty.rpc.api.IRpcHelloService;
import com.yubo.study.netty.rpc.api.IRpcService;
import com.yubo.study.netty.rpc.consumer.proxy.RpcProxy;
/**
* Created by Administrator on 2019/7/16 0016.
*/
public class RpcConsumer {
public static void main(String[] args) {
// client 要調用遠程的服務,他本身只知道接口名,所以他本身如何讓server知道 自己的需要呢
// 就要把接口名和方法名傳給server。
// 但是在本地呢,他還是調用原來的方法名。
// 還有就是在本地只有接口,不能實例化,那我們就只能給他創建一個代理對象
// 根據動態代理的原理,我們先要創建一個代理類,實現InvocationHandler, 並重寫invoke方法
// 在invoke方法裏,把我們自定義的協議對象發給server, 然後再讀取server的返回,然後再把這個返回值返回給本地調用的方法
IRpcHelloService rpcHello = RpcProxy.createProxy(IRpcHelloService.class);
System.out.println("out:" + rpcHello.hello("yubo"));
IRpcService service = RpcProxy.createProxy(IRpcService.class);
int r = service.add(1,1);
System.out.println("result 1 + 1 =" + r);
}
}
----------------------------------------------------------以下是本在代理類的代碼-------------------------------------------------------------------------------------------------------
package com.yubo.study.netty.rpc.consumer.proxy;
import com.yubo.study.netty.rpc.protocol.InvokerProtocol;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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 io.netty.handler.codec.serialization.ClassResolvers;
import io.netty.handler.codec.serialization.ObjectDecoder;
import io.netty.handler.codec.serialization.ObjectEncoder;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* Created by Administrator on 2019/7/15 0015.
*/
public class RpcProxy {
public static <T> T createProxy(Class<T> clazz){
return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new MethodProxy(clazz));
}
private static class MethodProxy implements InvocationHandler{
private Class<?> clazz;
public MethodProxy(Class<?> clazz) {
this.clazz = clazz;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(Object.class.equals(method.getDeclaringClass())){
return method.invoke(this, args);
}
else{
// invoke rpc server to execute method
return rpcInvoke(proxy, method, args);
}
}
public Object rpcInvoke(Object proxy, Method method, Object[] args){ // 遠程調用 部分用netty來實現
// Package the msg and send to server
InvokerProtocol myMsg = new InvokerProtocol(); // 構造自定義協議報文
myMsg.setClassName(this.clazz.getName());
myMsg.setMethodName(method.getName());
myMsg.setParaTypes(method.getParameterTypes());
myMsg.setValues(args);
final RpcProxyHandler consumerHandler = new RpcProxyHandler();// netty 中不可少的handler, 繼承自ChannelInboundHandlerAdaptor, 重寫channelRead() exceptionCaught()
EventLoopGroup workGroup = new NioEventLoopGroup(); // 創建工作組
try{
Bootstrap b = new Bootstrap(); // bootStrap 不可少,server端用的是ServerBootStrap
b.group(workGroup)
.channel(NioSocketChannel.class) // server用的是NioServerSocketChannel
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() { // handler 設置不可少, pipeline 加handler. server用的是childHandler()
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0,4, 0,4));
pipeline.addLast("frameEncoder", new LengthFieldPrepender(4));
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
pipeline.addLast("handler", consumerHandler); // 最後添加我們自己的handler
}
});
try {
ChannelFuture future = b.connect("localhost", 8083).sync(); //連接
System.out.println("write:" + myMsg);
future.channel().writeAndFlush(myMsg);// 寫報文
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
catch (Exception ex){
ex.printStackTrace();
}finally {
workGroup.shutdownGracefully();
}
return consumerHandler.getResponse(); // 把server 返回的結果,返回給client調用的地方
}
}
}
client中netty 用的handler
package com.yubo.study.netty.rpc.consumer.proxy;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* Created by Administrator on 2019/7/16 0016.
*/
public class RpcProxyHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse() {
return response;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
this.response = msg;// 服務器寫回來的東西,直接保存起來
// super.channelRead(ctx, msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
----------------------------------先執行server 端, 結果如下--------------------------------------------------
---------------------------------------再執行client 結果如下------------------------------------