RPC是Remote Procedure Call的简称,即远程过程调用,通过rpc,我们可以像调用本地接口一样,调用远程接口。在这个过程中,我们不需要关注接口的实现。实现rpc的框架很多,比如著名的dubbo框架,这里介绍通过netty来实现一个简单的rpc。
//1、定义测试Service接口IHelloService
//2、定义一个参数传输类,RpcRequestInfo
//3、实现接口HelloServiceImpl
//4、添加注解RpcService,后面可以交给Spring直接扫描所有的服务提供者
//5、RpcServer,实现InitializingBean 和 ApplicationContextAware,启动扫描所有的服务提供者,并注册到Map中,同时启动netty来处理后面发送的请求
//6、定义逻辑处理的RpcServerChannelHandler,继承Netty的ChannelInboundHandlerAdapter 从写channelRead方法,通过反射实现调用逻辑。
//7、定义SpringConfig类,通过AnnotationConfigApplicationContext启动工程。
//8、客户端定义RpcClientProxy动态代理来调用IHelloService
//9、RemoteInvocationHandler实现InvocationHandler 的接口,从写invoke方法,来实现动态调用
//10、RpcTransportHandler客户端的netty调用类
//11、RpcClientChannelHandler远程调用结果处理类。
首先先创建三个工程,netty-rpc-api、netty-rpc-server、netty-rpc-client。
一、netty-rpc-api
netty-rpc-api主要是定义接口和公用类。
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">
<parent>
<artifactId>rpc-study</artifactId>
<groupId>com.lh.study.rpc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-rpc-api</artifactId>
<dependencies>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.6.Final</version>
</dependency>
</dependencies>
</project>
IHelloService.java
package com.lh.study.rpc.netty;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 4:30 PM
**/
public interface IHelloService {
String sayHello(String name);
}
RpcRequestInfo.java
这个类主要用于传输调用信息,会被序列化和反序列化,所以需要实现Serializable接口
package com.lh.study.rpc.netty;
import java.io.Serializable;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 4:30 PM
**/
public class RpcRequestInfo implements Serializable {
private static final long serialVersionUID = 134567L;
private String className;
private String methodName;
private Object[] args;
private String version;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
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 Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
}
二、netty-rpc-server
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">
<parent>
<artifactId>rpc-study</artifactId>
<groupId>com.lh.study.rpc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-rpc-server</artifactId>
<dependencies>
<dependency>
<groupId>com.lh.study.rpc</groupId>
<artifactId>netty-rpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.14.RELEASE</version>
</dependency>
</dependencies>
</project>
HelloServiceImpl.java
接口具体实现类,使用了一个自定义的@RpcService注解,用来注解实现接口类型和版本号
package com.lh.study.rpc.netty;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 4:38 PM
**/
@RpcService(clazz = IHelloService.class , version = "V1.0")
public class HelloServiceImpl implements IHelloService {
@Override
public String sayHello(String name) {
System.out.println("hello "+name);
return "hello "+name;
}
}
RpcService.java
自定义注解
package com.lh.study.rpc.netty;
import org.springframework.stereotype.Component;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:43 PM
* @email: hao.liang.ext@siemens.com
**/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface RpcService {
Class clazz();
String version();
}
RpcServerChannelHandler.java
继承netty的ChannelInboundHandlerAdapter类,后面具体会用到,主要的处理逻辑都在重写的channelRead方法实现。
package com.lh.study.rpc.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.lang.reflect.Method;
import java.util.Map;
/**
* @description:
* @author: lianghao
* @create: 8/9/2019 3:25 PM
**/
public class RpcServerChannelHandler extends ChannelInboundHandlerAdapter {
private Map<String , Object> objectHashMap;
public RpcServerChannelHandler(Map<String, Object> objectHashMap) {
this.objectHashMap = objectHashMap;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
RpcRequestInfo rpcRequestInfo = (RpcRequestInfo) msg;
Object[] args = rpcRequestInfo.getArgs();
String methodName = rpcRequestInfo.getMethodName();
String className = rpcRequestInfo.getClassName();
Class<?>[] types = null;
if(null !=args && args.length >0){
types = new Class[args.length];
for(int i = 0 ; i < args.length ; i++){
types[i] = args[i].getClass();
}
}
Class<?> clazz = Class.forName(className);
Method method ;
if(types == null){
method = clazz.getMethod(methodName);
}else{
method = clazz.getMethod(methodName , types);
}
Object object = method.invoke(objectHashMap.get(className+rpcRequestInfo.getVersion()) , args);
ctx.writeAndFlush(object);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.flush();
ctx.close();
}
}
RpcServer.java
实现InitializingBean 和 ApplicationContextAware 接口,启动spring的时候会加载方法,这里主要时将类交给spring管理更方便
package com.lh.study.rpc.netty;
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;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 4:32 PM
* @email: hao.liang.ext@siemens.com
**/
@Component
public class RpcServer implements InitializingBean , ApplicationContextAware {
public Map<String , Object> handlerMap = new HashMap<String, Object>();
/**
* 启动后会调用该方法
*/
@Override
public void afterPropertiesSet(){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
try {
bootstrap.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch)
throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new
LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast("encoder",new ObjectEncoder());
pipeline.addLast("decoder",new
ObjectDecoder(Integer.MAX_VALUE,
ClassResolvers.cacheDisabled(null)));
pipeline.addLast(new RpcServerChannelHandler(handlerMap));
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(8080).sync();
System.out.println("server listener at port : "+8080);
future.channel().closeFuture().sync();
} catch (Exception e) {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
Map<String , Object> objectMap = applicationContext.getBeansWithAnnotation(RpcService.class);
for(Object objectBean :objectMap.values()){
RpcService rpcService = objectBean.getClass().getAnnotation(RpcService.class);
handlerMap.put(rpcService.clazz().getName()+rpcService.version() ,objectBean);
}
}
}
SpringConfig.java
这个没什么可以说的,就是后面通过spring的启动,来启动netty
package com.lh.study.rpc.netty;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:59 PM
**/
@Configurable
@ComponentScan(basePackages = "com.lh.study.rpc")
public class SpringConfig {
@Bean("rpcServer")
public RpcServer getRpcService(){
return new RpcServer();
}
}
RpcServerDemo.java
spring启动类
package com.lh.study.rpc.netty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:32 PM
**/
public class RpcServerDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
((AnnotationConfigApplicationContext) applicationContext).start();
}
}
三、netty-rpc-client
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">
<parent>
<artifactId>rpc-study</artifactId>
<groupId>com.lh.study.rpc</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>netty-rpc-client</artifactId>
<dependencies>
<dependency>
<groupId>com.lh.study.rpc</groupId>
<artifactId>netty-rpc-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
RemoteInvocationHandler.java
动态代理的执行类 ,会封装调用信息,通过netty发送给服务端,并传输服务端返回的信息
package com.lh.study.rpc.netty;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:17 PM
**/
public class RemoteInvocationHandler implements InvocationHandler {
private String host;
private int port;
public RemoteInvocationHandler(String host, int port) {
this.host = host;
this.port = port;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
RpcRequestInfo rpcRequestInfo = new RpcRequestInfo();
rpcRequestInfo.setArgs(args);
rpcRequestInfo.setClassName(method.getDeclaringClass().getName());
rpcRequestInfo.setMethodName(method.getName());
rpcRequestInfo.setVersion("V1.0");
RpcTransportSocket rpcTransportSocket = new RpcTransportSocket(host, port);
return rpcTransportSocket.sendRequestSocket(rpcRequestInfo);
}
}
RpcClientChannelHandler.java
package com.lh.study.rpc.netty;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @description:
* @author: lianghao
* @create: 8/9/2019 3:13 PM
**/
public class RpcClientChannelHandler extends ChannelInboundHandlerAdapter {
private Object response;
public Object getResponse(){
return this.response;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
System.out.println("client receive message: "+msg);
response = msg;
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.flush();
ctx.close();
}
}
RpcTransportHandler.java
客服端的netty
package com.lh.study.rpc.netty;
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;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:26 PM
**/
public class RpcTransportHandler {
private String host;
private int port;
public RpcTransportHandler(String host, int port) {
this.host = host;
this.port = port;
}
public Object sendRequestSocket(RpcRequestInfo rpcRequestModel) {
final RpcClientChannelHandler handler = new RpcClientChannelHandler();
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
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("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", handler);
}
});
ChannelFuture future = bootstrap.connect(host, port).sync();
future.channel().writeAndFlush(rpcRequestModel).sync();
future.channel().closeFuture().sync();
return handler.getResponse();
} catch (Exception e) {
e.printStackTrace();
group.shutdownGracefully();
}
return null;
}
}
RpcClientProxy.java
代理生成类
package com.lh.study.rpc.netty;
import java.lang.reflect.Proxy;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:15 PM
**/
public class RpcClientProxy {
public <T> T clientProxy(final Class<T> interfaceClass , final int port , final String host){
return (T) Proxy.newProxyInstance(interfaceClass.getClassLoader() , new Class[]{interfaceClass} , new RemoteInvocationHandler(host , port));
}
}
RpcClientDemo.java
客户端测试类
package com.lh.study.rpc.netty;
/**
* @description:
* @author: lianghao
* @create: 7/26/2019 5:31 PM
**/
public class RpcClientDemo {
public static void main(String[] args) {
RpcClientProxy rpcClientProxy = new RpcClientProxy();
IHelloService iHelloService = rpcClientProxy.clientProxy(IHelloService.class , 8080 , "127.0.0.1");
System.out.println(iHelloService.sayHello("Tom"));
}
}