基于上一节的内容RPC-准备阶段,自己实现一个基本的RPC框架。
一、服务端代码
1. 可以注册的服务端框架
public class RpcServerFrameReg {
//线程池
private static ExecutorService executorService = Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors() );
//用来容纳 接口名和接口实现类的 的容器
private static final Map<String, Class> serviceHolder = new HashMap<>();
private int port;
public RpcServerFrameReg(int port) {
this.port = port;
}
/**
* 注册服务,就是将接口和实现类存储到上边定义的serviceHolder中,
* @param serviceInterface 接口
* @param impl 接口的实现类
* @throws IOException
*/
public void registerService(Class<?> serviceInterface, Class impl) throws IOException {
//初始化Sockt
Socket socket = null;
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
try {
socket = new Socket();
// 连接服务器,我们像这个服务器传入
// 接口的名字,发送的ip,端口,以便将来能够通过接口名查询到往哪个服务器上发送信息
// 同时将接口和实现类的映射在serviceHolder实现
socket.connect( new InetSocketAddress( "127.0.0.1", 9999 ) );
//初始化 ObjectOutputStream
outputStream = new ObjectOutputStream( socket.getOutputStream() );
//写索引
outputStream.writeBoolean( false );
//写接口名
outputStream.writeUTF( serviceInterface.getName() );
//写1P
outputStream.writeUTF( "127.0.0.1" );
//写端口
outputStream.writeInt( port );
// 清理缓存
outputStream.flush();
// 初始化 ObjectInputStream,但是他的意义
inputStream = new ObjectInputStream( socket.getInputStream() );
if (inputStream.readBoolean()) {
serviceHolder.put( serviceInterface.getName(), impl );
}
} finally {
if (socket != null) socket.close();
if (inputStream != null) inputStream.close();
if (outputStream != null) outputStream.close();
}
}
//任务类,服务器启动服务后,如果有请求过来了,就会执行run方法
private static class ServerTask implements Runnable {
private Socket client = null;
// 构造函数,用来初始化
public ServerTask(Socket client) {
this.client = client;
}
@Override
public void run() {
try {
ObjectInputStream inputStream = new ObjectInputStream( client.getInputStream() );
ObjectOutputStream outputStream = new ObjectOutputStream( client.getOutputStream() );
//获取方法的类名
String className = inputStream.readUTF();
//获取方法的接口名
String methodName = inputStream.readUTF();
//获取方法参数类型
Class[] paramType = (Class[]) inputStream.readObject();
//获取方法的传入参数
Object param = inputStream.readObject();
//得到类
Class serverClass = serviceHolder.get( className );
if (serverClass == null) {
throw new ClassNotFoundException( className + "not found" );
}
//根据类,得到方法
Method method = serverClass.getMethod( methodName, paramType );
//根据方法,类实例,参数得到结果
Object result = method.invoke( serverClass.newInstance(), param );
//写到流里边。
outputStream.writeObject( result );
outputStream.flush();//返回去
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (client != null) client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//启动服务的方法
public void startService() throws IOException {
//创建服务端对象,
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind( new InetSocketAddress( port ) );
try {
while (true) {
//线程池处理任务
executorService.execute( new ServerTask( serverSocket.accept() ) );
}
} finally {
serverSocket.close();
}
}
}
2、服务端启动的时候,调用该框架
public class Test {
public static void main(String[] args) throws IOException {
RpcServerFrameReg regFram = new RpcServerFrameReg( 9999 );
//这里可以是很多很多的实现类,需要依次列下来
regFram.registerService( SendSms.class,SendSmsImpl.class );
regFram.startService();
}
}
二、客户端代码
1、客户端的框架
public class RpcClientFrameReg {
//获取远程代理对象的方法,这个方法是简单的动态代理的方法,语法糖可以见上一章,动态代理的用法
public static <T> T getRemoteProxyObj(final Class<T> serviceInterface){
final InetSocketAddress inetSocketAddress = new InetSocketAddress( "127.0.0.1",9999 );
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(),new Class[]{serviceInterface.getClass()},new DynProxy( serviceInterface,inetSocketAddress ));
}
/**
* 动态代理类
* 这个类的作用就是,传入对应的方法,以及参数,类找到对应的服务器,将类,方法,参数穿过去, 拿到返回的结果。
*/
private static class DynProxy implements InvocationHandler {
private final Class<?> serviceInterface;
private final InetSocketAddress inetSocketAddress;
private RegisterServiceVo[] serviceArray;//这个里边放的是远程服务器的地址信息
//构造函数,传入的是接口,和地址信息
public DynProxy(Class<?> serviceInterface, InetSocketAddress inetSocketAddress) {
this.serviceInterface = serviceInterface;
this.inetSocketAddress = inetSocketAddress;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectInputStream inputStream = null;
ObjectOutputStream outputStream = null;
//第一步: 这个if判断用来获取远程服务器的信息,根据传入的接口,判断有哪些服务器能够调用
if (serviceArray == null) {
try {
socket = new Socket();
socket.connect( inetSocketAddress );//去这台服务器,通过接口名获取要访问的接口信息
outputStream = new ObjectOutputStream( socket.getOutputStream() );
outputStream.writeBoolean( true );
outputStream.writeUTF( serviceInterface.getName() );//传入接口的名字
outputStream.flush();
inputStream = new ObjectInputStream( socket.getInputStream() );
Set<RegisterServiceVo> set = (Set<RegisterServiceVo>) inputStream.readObject();//拿到接口信息
serviceArray = new RegisterServiceVo[set.size()];
set.toArray( serviceArray );//将服务器信息塞进去。
} finally {
if (socket != null) socket.close();
if (outputStream != null) outputStream.close();
if (inputStream != null) inputStream.close();
}
}
//第二部 、从上一步拿到的服务器列表中,随机(可以用负载均衡的算法)获取一台服务器,然后传入接口名, 方法名,参数类型,参数,拿到返回值,返回即可
Random random = new Random();
int index = random.nextInt( serviceArray.length );
RegisterServiceVo vo = serviceArray[index];//随机获取一个服务器
InetSocketAddress socketAddress = new InetSocketAddress( vo.getHost(), vo.getPort() ); //远程服务器链接地址。
try {
socket = new Socket();
socket.connect( socketAddress );
outputStream = new ObjectOutputStream( socket.getOutputStream() );
outputStream.writeUTF( serviceInterface.getName() );//将方法名和
outputStream.writeUTF( method.getName() );
outputStream.writeObject( method.getParameterTypes() );
outputStream.writeObject( args );
outputStream.flush();
inputStream = new ObjectInputStream( socket.getInputStream() );
return inputStream.readObject();
} finally {
if (socket != null) socket.close();
if (outputStream != null) outputStream.close();
if (inputStream != null) inputStream.close();
}
}
}
}
上边代码中用到了RegisterServiceVo这个类,这个类就是一个简单的ip端口的bean,如下
private final String host;
private final int port;
public RegisterServiceVo(String host, int port) {
this.host = host;
this.port = port;
}
public String getHost() {
return host;
}
public int getPort() {
return port;
}
当客户端需要访问服务端的方法的时候,这样处理
SendSms sendSms =RpcClientFrameReg.getRemoteProxyObj(SendSms.class);
//然后就可以调用服务端的sendSms这个类的各个方法了
sendSms.sendMail("123");//SendSms是自己实现的一个类