自己简单写了个Java RMI(远程方法调用)的实现案例。 为了更好理解RMI(远程方法调用)、序列化的意义等等,花费三天多的时间肝了一个Java RMI的实现案例。 !!!高能预警!!!
自己简单写了个Java RMI(远程方法调用)的实现案例。
为了更好理解RMI(远程方法调用)、序列化的意义等等,花费三天多的时间肝了一个Java RMI的实现案例。
!!!高能预警!!!代码量有点大,先附上了简图用于理解
整个过程分为两大步
- 第一步--注册过程:客户端通过指定路由获取注册中心指定的远程客户端对象;
- 第二部--服务调用过程:客户端通过远程客户端对象访问远程服务端(代理服务)从而访问到真实服务的实现
1.定义远程标记接口调整为舒适的姿势,慢慢看…… 废话少说,上代码!!!
面向接口编程,具体作用看后面的代码怎么使用
// 标记接口:直接或间接实现MyRMI接口将获得远程调用的能力
public interface MyRMI{
}
2.编写RMI 服务注册中心
注册中心类:用于注册服务和获取服务,核心是hashMap路由表对象
/**
* 注册中心:维护服务发布的注册表
*/
public class MyRMIRegistry {
// 默认端口
public final int REGISTRY_PORT = 10099;
private String host;
private int port;
private Map<String, MyRMI> bindings;
public MyRMIRegistry(int port){
this.port = port;
}
public MyRMIRegistry(String host, int port){
this.host=host;
this.port=port;
}
public void createRegistry(String serverName,MyRMI myRMI){
// 注册服务,并开启服务
this.bindings = new HashMap<>();
String host = null;
try {
host = Inet4Address.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
// 路由规则可自行定义,只要能确保Key唯一即可
String binding = "myrmi://"+host+":"+port+"/"+serverName;
this.bindings.put("myrmi://"+host+":"+port+"/"+serverName,myRMI);
System.out.println("注册的服务有:"+bindings.keySet().toString());
MyRMIRegistryServer myRMIRegistryServer = new MyRMIRegistryServer(this.port, this.bindings);
Executors.newCachedThreadPool().submit(myRMIRegistryServer); // 线程池启动服务
}
public MyRMI getRegistry(String serverName){
Socket socket = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
MyRMI myRMI = null;
// 通过
try {
socket = new Socket(host, port);
out = new ObjectOutputStream(socket.getOutputStream());
out.writeObject("myrmi://"+host+":"+port+"/"+serverName);
in = new ObjectInputStream(socket.getInputStream());
myRMI = (MyRMI)in.readObject();
} catch (IOException e) {
e.printStackTrace();
}catch (ClassNotFoundException e) {
e.printStackTrace();
}
return myRMI;
}
}
RMI 注册中心获取服务的线程:启动注册中心服务,等待客户端来获取路由表中的远程客户端
/**
* RMI注册中心获取服务线程
*/
public class MyRMIRegistryServer implements Runnable {
private int port;
private Map<String, MyRMI> bindings;
public MyRMIRegistryServer(Integer port,Map<String, MyRMI> bindings){
this.port = port;
this.bindings = bindings;
}
@Override
public void run() {
ServerSocket serverSocket = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
serverSocket = new ServerSocket(this.port);
while(true){
Socket socket = serverSocket.accept();
in = new ObjectInputStream(socket.getInputStream());
out = new ObjectOutputStream(socket.getOutputStream());
// 看看客户端想要什么服务
String serverName = (String)in.readObject();
Iterator iterator = bindings.keySet().iterator();
while (iterator.hasNext()){
String key = (String) iterator.next();
if(serverName.equals(key)){
// 给客户端响应服务对象
MyRMI myRMI = bindings.get(key);
out.writeObject(myRMI);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
// 异常后进入
try {
if (out!=null) out.close();
if (in!=null) in.close();
if (serverSocket!=null) serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
3.定义要发布的服务接口
需要提供RMI服务的接口,必须继承自定义的MyRMI标记接口
/**
* 服务接口
*/
public interface Hello extends MyRMI {
public String sayHello(String name);
}
4.服务用到的实体类
/**
* 对象数据类:Person
*/
public class Person implements Serializable {
// 序列化版本UID
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public String toString() {
return "Person{" + "name='" + name + ", age=" + age + ", sex='" + sex + '}';
}
public Person() {
}
public Person(String name, Integer age, String sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
}
5.实现要发布的服务接口
/**
* 对外提供的服务实现
*/
public class HelloImpl implements Hello {
private static File file = new File("D:/HelloRMI.txt");
private static List<Person> list = new ArrayList<>();
@Override
public String sayHello(String name) {
String result = "没有获取到"+name+"的信息";
try {
List<Person> personList = readList();
for(Person person:personList){
if (person.getName().equals(name)){
result = "Hello , welcome to the RMI! "
+ "姓名:"+name + " 年龄:"+person.getAge()+" 性别:"+person.getSex();
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return result;
}
/**
* 生成数据,为测试做准备
* @param args
* @throws IOException
* @throws ClassNotFoundException
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
//数据准备:集合类都实现了序列化接口Serializable
list.add(new Person("张三", 38, "男"));
list.add(new Person("李四", 38, "男"));
list.add(new Person("如花", 18, "女"));
// 持久化对象数据
writerList(list);
// 查询持久化对象数据
List<Person> personList = readList();
System.out.println("遍历持久化对象数据>");
for (Person person : personList) {
System.out.println(person);
if (person.getAge() == 38) {
person.setAge(18);
}
}
}
public static void writerList(List<Person> list) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
objectOutputStream.writeObject(list);
objectOutputStream.close();
}
public static List<Person> readList() throws IOException, ClassNotFoundException {
// 读取普通文件反序列化
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
List<Person> personList = (List<Person>) objectInputStream.readObject();
objectInputStream.close();
return personList;
}
}
6.远程客户端的线程类
用于自动生成服务接口(继承了MyRMI标记接口)的远程客户端类:这个类原本是通用类实现,为了方便实现,就直接实现Hello接口了
/**
* 远程客户端的线程类的生成:
* 为了方便实现,这边直接实现服务接口编写
*/
public class HelloClientThread implements Hello,Serializable {
// 序列化版本UID
private static final long serialVersionUID = 1L;
private Map<String,Object> map = new HashMap<>(); // 报文对象:方法名和参数对象
private String ip;
private int port;
public HelloClientThread(String ip, int port){
this.ip = ip;
this.port = port;
}
@Override
public String sayHello(String name) {
map.put("sayHello",name);
String result = (String)send();
return result;
}
private Object send(){
Object o =null;
Socket socket = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
socket = new Socket(ip, port);
out = new ObjectOutputStream(socket.getOutputStream());
in = new ObjectInputStream(socket.getInputStream());
// 告诉服务端我要调用什么服务
out.writeObject(map);
// 获取服务实现对象
o = in.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
try {
if (out!=null) out.close();
if (in!=null) in.close();
if (socket!=null) socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return o;
}
}
7.远程服务端的线程类
用于自动生成服务接口(继承了MyRMI标记接口)的远程服务端类:这个类原本也是通用类实现,为了方便实现,部分代码尚未做到解耦通用
/**
* 远程服务端的线程类的生成:
* 为了方便实现,这边直接实现服务线程类
*/
public class HelloServerThread implements Runnable {
private Integer port;
private MyRMI myRMI;
public HelloServerThread(Integer port, MyRMI myRMI){
this.port = port;
this.myRMI = myRMI;
}
@Override
public void run() {
ServerSocket serverSocket = null;
ObjectOutputStream out = null;
ObjectInputStream in = null;
try {
serverSocket = new ServerSocket(this.port);
while(true){
Socket socket = serverSocket.accept();
in = new ObjectInputStream(socket.getInputStream());
out = new ObjectOutputStream(socket.getOutputStream());
// 看看客户端想要什么服务
Map map = (Map)in.readObject();
Iterator iterator = map.keySet().iterator();
while (iterator.hasNext()){
String key = (String) iterator.next();
if("sayHello".equals(key)){
// 给客户端响应服务对象
Hello hello = (Hello)myRMI;
String result = hello.sayHello((String) map.get(key));
out.writeObject(result);
}
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}finally {
// 异常后进入
try {
if (out!=null) out.close();
if (in!=null) in.close();
if (serverSocket!=null) serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
8.远程客户端生成和远程服务端生成和启动的类
/**
* 远程客户端生成和远程服务端生成和启动的类
*/
public class RemoteSocketObject{
// 默认端口
private int port=18999;
// 指定远程通讯端口和代理服务
public MyRMI createRemoteClient(MyRMI myRMI,int port){
if (port > 0)
this.port=port;
MyRMI myRMIClient = null;
try {
// 生成底层通讯服务端,并启动
HelloServerThread helloServerThread = new HelloServerThread(this.port, myRMI);
Executors.newCachedThreadPool().submit(helloServerThread); // 线程池启动服务
// 生成底层通讯客户端
String localHost = Inet4Address.getLocalHost().getHostAddress();
System.out.println("host="+localHost+",port="+this.port);
myRMIClient= new HelloClientThread(localHost, this.port);
} catch (Exception e) {
e.printStackTrace();
}
return myRMIClient;
}
}
9.服务发布类
/**
* RMI 服务发布类
*/
public class HelloServer {
public static void main(String[] args) {
System.out.println("Create Hello Remote Method Invocation...");
// 实例化一个Hello
Hello hello = new HelloImpl();
// 转换成远程服务,并提供远程客户端
Hello remoteClient = (Hello)new RemoteSocketObject().createRemoteClient(hello, 0);
// 将服务实现托管到Socket服务
MyRMIRegistry myRMIRegistry = new MyRMIRegistry(16000);
// 开启线程服务
myRMIRegistry.createRegistry("Hello",remoteClient);
}
}
10.客户端测试类
/**
* 客户端测试类
* 客户端只知道服务接口、服务发布的地址和服务发布的名称
*/
public class TestHello {
public static void main(String[] args) {
// 注意不是127.0.0.1,不知道host的看server端启动后打印的信息
// 端口16000是注册中心的端口,底层代理服务的端口客户端无需知道
MyRMIRegistry client = new MyRMIRegistry("192.168.233.1", 16000);
Hello hello = (Hello) client.getRegistry("Hello");
System.out.println(hello.sayHello("张三"));
}
}
11.总结
所有代码整下来,在真正的场景中:
客户端只知道:TestHello类、Hello接口定义、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中只知道Key,不知道具体值);
服务端只知道:Hello接口、HelloImpl服务实现类、MyRMI标记接口、MyRMIRegistry注册类代码(路由表中知道Key和具体值);
关于其他的代码实现都是无感的,为了简单实现远程客户端和远程服务端,将服务接口耦合到两者上了,未做到解耦通用。
Java往期文章
Java全栈学习路线、学习资源和面试题一条龙
我心里优秀架构师是怎样的?
免费下载经典编程书籍
更多优质文章和资源