自己簡單寫了個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和具體值);
關於其他的程式碼實現都是無感的,為了簡單實現遠端客戶端和遠端服務端,將服務介面耦合到兩者上了,未做到解耦通用。
更多優質文章和資源?
原創不易、三聯支援:分享,點贊,在看?