RPC全名为Remote Procedure Call,顾名思义为远程调用,既然是远程调用,那就再熟悉不过了,IO、socket这些是逃不掉的。
国内非常流行的rpc框架无疑是阿里巴巴开源框架—dubbo。
dubbo底层就是利用netty进行网络IO实现通信。
那么如何快速启动一个netty服务呢。
我觉得,大家都是伸手党,直接copy代码跑起来是最爽的,那么就来吧。
public class RPCServer {
private int port;
public RPCServer(int port) {
this.port = port;
}
public void start() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap().group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
.localAddress(port).childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 忽略前4个字节的解码器(目的是得到不带长度的数据域)
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
// 4个字节表示数据长度的编码器
pipeline.addLast(new LengthFieldPrepender(4));
// netty自带object编码与解码器(所以这是为什么我们dubbo接口的参数要序列化)
pipeline.addLast("encoder", new ObjectEncoder());
pipeline.addLast("decoder", new ObjectDecoder(Integer.MAX_VALUE, ClassResolvers.cacheDisabled(null)));
// 自定义handler
pipeline.addLast(new InvokerHandler());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = serverBootstrap.bind(port).sync();
System.out.println("Server start listen at " + port);
future.channel().closeFuture().sync();
} catch (Exception e) {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
new RPCServer(port).start();
}
}
这是一个很常见的启动netty服务端的代码(百度上一大片哦),主要定义了一些RPC调用需要用到的解码编码器。
其中自定义的handler代码。
public class InvokerHandler extends ChannelInboundHandlerAdapter {
public static ConcurrentHashMap classMap = new ConcurrentHashMap();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message message = (Message) msg;
Object clazz = null;
if (!classMap.containsKey(message.getClassName())) {
try {
clazz = Class.forName(message.getClassName()).newInstance();
classMap.put(message.getClassName(), clazz);
} catch (Exception e) {
e.printStackTrace();
}
} else {
clazz = classMap.get(message.getClassName());
}
Method method = clazz.getClass().getMethod(message.getMethodName(), message.getTypes());
Object result = method.invoke(clazz, message.getObjects());
ctx.write(result);
ctx.flush();
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
message。
public class Message implements Serializable {
private static final long serialVersionUID = -8970942815543515064L;
private String className;//类名
private String methodName;//函数名称
private Class[] types;//参数类型
private Object[] objects;//参数列表
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[] getTypes() {
return types;
}
public void setTypes(Class[] types) {
this.types = types;
}
public Object[] getObjects() {
return objects;
}
public void setObjects(Object[] objects) {
this.objects = objects;
}
@Override
public String toString() {
return "Message{" +
"className='" + className + '\'' +
", methodName='" + methodName + '\'' +
", types=" + Arrays.toString(types) +
", objects=" + Arrays.toString(objects) +
'}';
}
}
通过以上代码可以暂时先分析出基本逻辑:
那么基本可以猜测出大致逻辑了。
客户端发送一个message告诉服务端,我要调用你的什么类的什么方法,我的参数类型是什么,我的参数值是什么,你(服务端)通过反射然后执行,执行完了给我(客户端)一个response即可!
----------------------------------------------------------------------------------------------------------------
接下来就是客户端如何编写了。
我们先想一下之前使用dubbo的场景,先通过配置文件注册reference的bean,然后通过bean调用方法。这么一看好像并没有发送任何IO的请求去访问服务端。
其实这里是用到了动态代理,看似执行了此方法,但此方法非彼方法。
我们来看客户端的代码。
public class RPCProxy {
@SuppressWarnings("unchecked")
public static T create(final Object target) {
// 生成代理对象
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 组装message
Message message = new Message();
message.setClassName(target.getClass().getName());
message.setMethodName(method.getName());
message.setObjects(args);
message.setTypes(method.getParameterTypes());
final ResultHandler resultHandler = new ResultHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
@Override
public 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)));
// 注册自定义handler
pipeline.addLast("handler", resultHandler);
}
});
// 客户端通过IP+端口号去访问netty服务端
ChannelFuture future = b.connect("localhost", 8080).sync();
// 向服务器发送message
future.channel().writeAndFlush(message).sync();
future.channel().closeFuture().sync();
} finally {
group.shutdownGracefully();
}
// 从handler中拿到服务端的返回值
return resultHandler.getResponse();
}
});
}
}
客户端handler。
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;
System.out.println("client接收到服务器返回的消息:" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("client exception is general");
}
}
于是乎我们带着测试一下心情写出了类似hello-world的代码。
定义一个接口。
public interface Hello {
String print(String name);
}
实现类。
public class HelloImpl implements Hello {
public String print(String name) {
return "hello " + name;
}
}
测试类。
public class Test {
public static void main(String [] args){
Hello hello2 = RPCProxy.create(new HelloImpl());
System.out.println(hello2.print("world!"));
}
}
跑之前记得先把netty服务端的server启动了。
结果令人很满意。
此项目的git地址:
https://github.com/feiyangmemeda/rpc.git