RPC:Remote Procudure Call
简单来说就是客户端远程调用服务端的方法
设计为三个角色分别为服务器端接口,服务注册中心(包含所有接口),客户端。
所需要
- socket链接服务器与客户端
- 注册中心包含所有接口的值
- 服务器通过反射解析信息
- 服务端返回接口,通过动态代理
服务器端接口
- 接口
package server;
public interface HelloService {
void sayHi(String name);
}
- 实现类
package server;
public class HelloServiceImpl implements HelloService {
public void sayHi(String name) {
// TODO Auto-generated method stub
System.out.println("hello"+name);
}
}
服务器中心接口
package server;
public interface Server {
void start()throws Exception;
void stop();
void register(Class<?> service,Class<?> serviceImpl);
}
start开启服务
stop停止服务
register注册接口入MAP中
服务器中心接口实现
//map 注册接口到MAP
private static HashMap<String, Class> serviceRegiser=new HashMap<>();
设置一个Hashmap 其KEY值为接口名 value值为接口对象
public void register(Class<?> service,Class<?> serviceImpl) {
serviceRegiser.put(service.getName(), serviceImpl);
// TODO Auto-generated method stub
}
将接口注册入map中,方便起见直接使用getName设置KEY值
private static final Integer PORT=9999;
public void start() {
// TODO Auto-generated method stub
ServerSocket servers=null;
ObjectOutputStream output=null;
ObjectInputStream input=null;
设置固定端口号
ServerSocket准备接收链接
ObjectOutputStream output=null;ObjectInputStream input=null;
为方便使用本对象进行数据交换
servers = new ServerSocket();
servers.bind(new InetSocketAddress(PORT));
Socket socket=servers.accept();
//接收到客户端链接请求
input=new ObjectInputStream(socket.getInputStream());
接收对象并接受客户端传参
input=new ObjectInputStream(socket.getInputStream());
//顺序接收
String intername=input.readUTF();
String methodname=input.readUTF();
Class<?>[] methodparatype=(Class[])input.readObject();
Object[] parameters=(Object[])input.readObject();
按顺序分别为接口名,对应方法,参数列表,参数
//开始找接口
Class<?> SeviceClass=serviceRegiser.get(intername);
Method metod=SeviceClass.getMethod(methodname, methodparatype);
Object resObject = metod.invoke(SeviceClass.newInstance(), parameters);
获取接口,通过方法名获取方法,将参数及列表调用方法获得返回值
output=new ObjectOutputStream(socket.getOutputStream());
output.writeObject(resObject);
将返回值返回客户端
finally {
try {
if(output!=null)
output.close();
if(input!=null)
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
关闭资源
客户端
//获取代表服务端接口的动态代理对象
//serviceName接口名
@SuppressWarnings("unchecked")
public static <T> T getRemoteProxyObject(Class serviceNameInterface,InetSocketAddress in) {
/*
* newProxyInstance(a,b,c)
* a:类加载器 :代理类的类加载器 单独String不可以获得 因此使用Class类型获得其类加载器
* b:代理对象具备方法 接口 多接口传入数组
* c:handler 对象
*/
return (T)Proxy.newProxyInstance(serviceNameInterface.getClassLoader(), new Class<?>[] {serviceNameInterface}, new InvocationHandler() {
/*
* proxy:代理对象 method方法 args(方法参数列表)
*/
采用动态代理方式来调用
* proxy:代理对象 method方法 args(方法参数列表)
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) {
// TODO Auto-generated method stub
重写handler对象
ObjectOutputStream output=null;
ObjectInputStream input=null;
与服务器端一样为方便使用本对象进行数据交换
Socket socket=new Socket();
socket.connect(in);
output=new ObjectOutputStream(socket.getOutputStream()) ;//发送序列化
将对应参数发送至服务器
//发送接口名方法名
output.writeUTF(serviceNameInterface.getName());
output.writeUTF(method.getName());
//方法参数类型 方法参数
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//等待服务端返回
按顺序写入流
input=new ObjectInputStream(socket.getInputStream());
return input.readObject();
接受返回流并返回 例行关闭资源就不写了
后续优化
- 保持开启并循环监听
外置
ServerSocket servers=servers = new ServerSocket();
servers.bind(new InetSocketAddress(PORT));
循环
while(true){
....
//仅仅如此会造成阻塞
}
通过多线程来实现并发解决阻塞
private static ExecutorService executor=Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
其中Runtime.getRuntime().availableProcessors()
获取处理器数量来初始化线程池容量。
private static class ServiceTask implements Runnable{
ObjectOutputStream output=null;
ObjectInputStream input=null;
Socket socket=null;
public ServiceTask(Socket socket) {
this.socket=socket;
}
@Override
public void run() {try {
//接收到客户端链接请求
input=new ObjectInputStream(socket.getInputStream());
//顺序接收
String intername=input.readUTF();
String methodname=input.readUTF();
Class<?>[] methodparatype=(Class[])input.readObject();
Object[] parameters=(Object[])input.readObject();
//开始找接口
Class<?> SeviceClass=serviceRegiser.get(intername);
Method metod=SeviceClass.getMethod(methodname, methodparatype);
Object resObject = metod.invoke(SeviceClass.newInstance(), parameters);
//返回客户端
output=new ObjectOutputStream(socket.getOutputStream());
output.writeObject(resObject);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchMethodException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SecurityException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InstantiationException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
try {
if(output!=null)
output.close();
if(input!=null)
input.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}}
}
// TODO Auto-generated method stub
}
将执行操作转移入内部实现Runable方法中实现run方法。并将socket作为参数传入该对象。
修改后while中代码块为
while(true) {
Socket socket=null;
try {
socket = servers.accept();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
executor.execute(new ServiceTask(socket));
}
设置状态量判断服务是否开启
private static boolean isRunning=false;
。。。。。。
isRunning=true;
stop方法用来关闭服务
public void stop() {
// TODO Auto-generated method stub
isRunning=false;
executor.shutdown();
}
设置状态量为关闭,并关闭线程池
测试
服务器端 并且通过线程启动
package server;
public class TEst {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
Server server=new ServerCenter();
server.register(HelloService.class, HelloServiceImpl.class);
try {
server.start();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}).start();
};
}
客户端
package client;
import java.net.InetSocketAddress;
import server.HelloService;
public class Ctest {
public static void main(String[] args) throws ClassNotFoundException {
HelloService service=Client.getRemoteProxyObject(Class.forName("server.HelloService"), new InetSocketAddress("127.0.0.1",9999));
System.out.println(service.sayHi("aa"));
}
}
整体思路:
rpc:
- 客户端通过socket请求服务器端,并且通过字符串形式将需要请求的接口的发送给服务端
- 服务端将可以提供的接口注册到服务中心
- 服务端接受请求,在服务中心找对应实现类
- 返回至客户端