最近公司让跑一个基于Dubbo的 C/S 架构的项目,之前对于rpc的印象是基于rmi协议的,rmi帮我们屏蔽了底层通信的细节,让我们使用远程对象像使用本地对象一样(基于两个虚拟机之间的通信)。rmi和dubbo之后会会不断更新。这章主要说说rpc。
RPC
RPC【Remote Procedure Call】是指远程过程调用,是一种进程间通信方式,他是一种技术的思想,而不是规范,它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,而不用程序员显示编码这个远程调用的细节,即程序员无论是调用本地的还是远程的函数,本质上编写的调用代码基本相同。
比如我本地要调用另外一台与我网络连通的机器上的一个方法,那么我需要先通过流的形式告诉那台机器,我要调用什么方法,然后那个机器上处理后通过流的形式返回到我这台机器上,然后我通过反序列化拿到返回的结果。这样就实现了基本的rpc过程。
本片文章主要参考了alibaba Dubbo框架作者 梁飞的技术文章:RPC框架几行代码就够了。
因为先接触的动态代理,偶尔看到梁大的这篇文章,于是又专门去看了看Socket编程。结合自己的理解也手撸一个。
知识点储备:
- Socket编程 (两台机器交互的基础)
- java动态代理 (这个可能不太好理解,需要先去了解.java源文件是怎么加载到JVM中并生成class对象的过程,在这个上面我做了不少功课)
如果没有了解以上两块内容的道友,需要先去了解一下。(后续我会补充以上两块的内容)
废话不多说,上代码。
公共API接口
/**
* 公共Api 接口
* @author Qiu Ping
*/
public interface Hello {
String hello(String name);
}
服务端Imp
/**
* Server API IMP
* @author Qiu Ping
*/
public class myimp implements Hello {
@Override
public String hello(String name) {
System.out.println("我是服务端实现类方法: Hello "+name);
return "You name is " + name;
}
}
接口Server和Client都要引入,具体实现只需在Server端实现即可,客户端远程调用。
SocketServer
try块很多,不要着急,大部分我都写了注释,慢慢看。
/**
* Single RpcFm Server
* @author Qiu Ping
* 知识点:Socket编程 IO流 反射
*/
public class SocketServer {
/**
* 暴露服务
* @param port 服务端口
* @throws IOException
*/
public static void ServiceExport(Object serviceimp,int port) throws IOException{
//判断
if(serviceimp == null){
throw new IllegalArgumentException("serviceimp is null !");
}
if(port<0 || port > 65536){
throw new IllegalArgumentException("port is error !");
}
System.out.println(serviceimp.getClass().getName()+"is Export on port "+port);
//监听端口
ServerSocket serversocket = new ServerSocket(port);
//还有一种写法 for(;;) 效果一样 死循环
for(;;){
//持续监听Client端,构建服务端Socket
//通信是双方Socket通信,ServerSocket 提供了端口监听
//final关键字 指定该Socket只能指向一个地址
final Socket socket = serversocket.accept();
//起线程,约束通信规则
new Thread(new Runnable(){
//重写Runnable接口run方法
@Override
public void run() {
try {
System.out.println("我是线程内方法!");
//获取Client Object流
ObjectInputStream objinp = new ObjectInputStream(socket.getInputStream());
//以UTF8的编码读取调用方法名
String methodname = objinp.readUTF();
System.out.println("调用方法名为:"+methodname);
try {
//方法参数类型
Class<?>[] parameterTypes = (Class<?>[])objinp.readObject();
//方法参数
Object[] arguments = (Object[])objinp.readObject();
System.out.println("方法参数为:"+arguments[0].toString());
try {
//根据Client请求信息构造出Method对象
Method method = serviceimp.getClass().getMethod(methodname, parameterTypes);
//获取Output流,返回结果
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
try {
//执行远程方法
Object result = method.invoke(serviceimp, arguments);
System.out.println("我是执行结果:"+result.toString());
output.writeObject(result);
output.flush();
} catch (IllegalAccessException | IllegalArgumentException
| InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
output.close();
}
} catch (NoSuchMethodException | SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
objinp.close();
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
}
}
}
SocketClient
/**
* Single RpcFm Client
* @author Qiu Ping
* 知识点:Socket IO流 动态代理
* 正如梁大所说 Rpc框架核心就是 Socket编程+动态代理
*/
public class SocketClient {
/**
* 服务调用
* @param interfaceClass 接口
* @param Host ServerIP地址
* @param port 服务端口
* @return
*/
public static Object invok(Class<?> interfaceClass,String Host,int port){
if(interfaceClass == null){
throw new IllegalArgumentException("Interface is null");
}
if(!interfaceClass.isInterface()){
throw new IllegalArgumentException("The " + interfaceClass.getName() + " not a interface !");
}
if (Host == null || Host.length() == 0){
throw new IllegalArgumentException("Host == null!");
}
if (port <= 0 || port > 65535) {
throw new IllegalArgumentException("Invalid port " + port);
}
System.out.println("Get service from "+Host+":"+port+" from server !");
//核心的东西来了啊,动态代理
return Proxy.newProxyInstance(
interfaceClass.getClassLoader(),
new Class<?>[] {interfaceClass},
new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("客户端开始调用!");
//SocketClient
Socket socket = new Socket(Host, port);
//获取输出流
ObjectOutputStream output = new ObjectOutputStream(socket.getOutputStream());
//按照双方约束规则写入数据
//1.方法名称
output.writeUTF(method.getName());
//2.参数类型
output.writeObject(method.getParameterTypes());
//3.参数
output.writeObject(args);
//接收流
ObjectInputStream input = new ObjectInputStream(socket.getInputStream());
Object result = input.readObject();
input.close();
socket.close();
return result;
}
});
}
}
编码工作基本结束,接下来测试.
Server port:8001 -------- Client port:8002
服务端已经启动,等到客户端简历socket连接。
查看客户端打印出调用服务端方法后成功返回执行结果。