Lập trình phân tán – Cách chạy một ứng dụng Java RMI
RMI (Remote Method Invocation) làm một API cung cấp kỹ thuật tạo ứng dụng phân tán trong Java. RMI cho phép một đối tượng gọi các phương thức trên một đối tượng khác đang chạy trên một máy ảo Java khác (JVM)
RMI chung cấp giao tiếp từ xa giữa các ứng dụng sử dụng hai đối tượng stub và skeleton.
Stub: stub là một đối tượng, hoạt động như một cổng ở phía máy khách. Tất cả các yêu cầu gửi đi đều được chuyển qua nó. Nó cư trú tại phía Client và đại diện cho đối tượng remote. Khi người gọi gọi phương thức trên đối tượng stub, nó sẽ thực hiện các công việc sau:
- Khởi tạo kết nối với máy ảo từ xa (JVM)
- Ghi và truyền các tham số tới máy ảo từ xa (JVM)
- Đợi kết quả trả về
- Đọc giá trị trả về hoặc các ngoại lệ (exception)
- Cuối cùng, trả về giá trị cho người gọi.
Skeleton: là một đối tượng, hoạt động như một cổng cho đối tượng phía máy chủ. Tất cả các yêu cầu đến được chuyển qua nó. Khi skeleton nhận được yêu cầu tới, nó sẽ thực hiện các công việc sau:
- Đọc các tham số cho phương thức remote
- Goi phương thức trên đối tượng remote thực tế
- Viết và truyền kết quả tới người gọi.
Để viết một ứng dụng với Java RMI, bạn cần phải theo các bước sau:
- Định nghĩa một lớp giao diện (Remote Interface)
- Triển khai lớp thực thi của lớp giao diện (remote object)
- Viết code cho chương trình chạy ở Server
- Viết code cho chương trình chạy ở Client
- Biên dịch ứng dụng
- Chạy ứng dụng
Cách xử lý và giao tiếp diễn ra trong RMI
Các bước chạy một ứng dụng Java RMI bằng dòng lệnh
Yêu cầu: Viết một chương trình tính giai thừa bằng lập trình phân tán với Java RMI
1. Tạo các lớp (Class) và các lớp giao diện (Interface) cho bài toán
Bước 1: Tạo một Interface kết thừa từ lớp java.rmi.Remote
Remote Interface định nghĩa đối tượng có thể được gọi từ xa bởi Client. Interface này có thể được kết nối với chương trình của Client. Interface này phải được thừa kế từ java.rmi.Remote.
//Code: timoday.edu.vn
import java.math.BigInteger;
//Tạo một lớp Interface
public interface Factorial
extends java.rmi.Remote {
//Khai báo một phương thức tính giai thừa
public BigInteger fact(int num)
throws java.rmi.RemoteException;
}
Bước 2: Tạo một Class thừa kế từ java.rmi.server.UnicastRemoteObject và thực thi lớp Interface ở bước 1
Lớp này sẽ triển khai Interface bước trước. Thực hiện các tính toán cần thiết cho bài toán tính gia thừa.
//Code: timoday.edu.vn
import java.math.BigInteger;
//Class này thừa kế và thực thi Interface ở bước 1
public class FactorialImpl
extends java.rmi.server.UnicastRemoteObject
implements Factorial {
// Khai báo Constructor
public FactorialImpl()
throws java.rmi.RemoteException
{
super();
}
// Tính toán thuật toán giai thừa
// Thực thi phương thức fact()
// để tính giai thừa của một số
public BigInteger fact(int num)
throws java.rmi.RemoteException
{
BigInteger factorial = BigInteger.ONE;
for (int i = 1; i <= num; ++i) {
factorial = factorial.multiply(BigInteger.valueOf(i));
}
return factorial;
}
}
Bước 3: Tạo một Class chạy ở phía Server (với địa chỉ localhost và tên Service)
Để cung cấp Service, chương trình Server được tạo bằng cách sử dụng phương thức java.rmi.Naming.rebind () có thể được gọi với hai đối số: đối tượng tham chiếu (tên dịch vụ) và tham chiếu đến đối tượng thực thi.
//Code: timoday.edu.vn
import java.rmi.Naming;
public class FactorialServer {
//Triển khai constructor của class
public FactorialServer()
{
try {
//Tạo một object tham chiếu tới Interface
Factorial c = new FactorialImpl();
//Gắn địa chỉ localhost với Service này
Naming.rebind("rmi://localhost/FactorialService", c);
}
catch (Exception e) {
//Nếu có lỗi xảy ra
System.err.println("Lỗi khởi tạo Server, chi tiết lỗi: " + e);
}
}
public static void main(String[] args)
{
//Tạo một Object
new FactorialServer();
}
}
Bước 4: Tạo một Class chạy ở phía Client (với địa chỉ localhost và tên Service)
Chương trình Client sẽ gọi phương thức java.rmi.Naming.lookup() với URL RMI và trả về một instance của kiểu đối tượng (Interface Factorial). Tất cả RMI được thực hiện trên đối tượng này
//Code: timoday.edu.vn
import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
public class FactorialClient {
public static void main(String[] args)
{
try {
//Tạp một Remote Object với cùng tên
//Truyền kết quả tra cứu đến giao diện
Factorial c = (Factorial)Naming.lookup("rmi://localhost/FactorialService");
//Gọi phương thức và in kết quả
System.out.println(c.fact(30));
}
//Nếu có lỗi xảy ra
catch (MalformedURLException murle) {
System.out.println("\nMalformedURLException: "+ murle);
}
catch (RemoteException re) {
System.out.println("\nRemoteException: "+ re);
}
catch (NotBoundException nbe) {
System.out.println("\nNotBoundException: "+ nbe);
}
catch (java.lang.ArithmeticException ae) {
System.out.println("\nArithmeticException: " + ae);
}
}
}
2. Biên dịch toàn bộ chương trình
Sử dụng javac để biên dịch tất cả bốn chương trình và rmic (RMI Compiler) để tạo các lớp Stub và Skeleton.
3. Chạy ứng dụng
Sau giai đoạn biên dịch, hệ thống đã sẵn sàng để chạy. Để chạy hệ thống, hãy mở ba màn hình Console (di chuyển đến đường dẫn nơi chứa chương trình). Một cho Client, một cho Server và một cho Registry RMI.
Bước 1: Bắt đầu với registry, sử dụng rmiregistry
Nếu không có lỗi registry sẽ bắt đầu chạy và bây giờ chuyển sang màn hình thứ hai.
Bước 2: Chạy chương trình Server và để bật Service
Nó sẽ khởi động và đợi kết nối Client và nó sẽ tải quá trình thực thi vào bộ nhớ.
Bước 3: Chạy chương trình Client
Với cách này, RMI có thể được chạy trên ba màn hình Console cho localhost. RMI sử dụng Ngăn xếp Network và Ngăn xếp TCP/IP để giao tiếp ba chương trình JVM khác nhau.
Bài tập
-
- Viết chương trình tính tổng sử dụng kỹ thuật lập trình với RMI Java. Client gửi cho Server một số nguyên k nhập vào từ bàn phím đến Server. Server tính tổng từ 1 tới k số tự nhiên và gửi lại kết quả tính toán cho Client.
- Viết chương trình tính số mũ của giá trị x và y sử dụng kỹ thuật lập trình với RMI. Client gửi cho server giá trị x, y để tính x mũ y thực hiện tính tại phía Server.
- Triển khai hai đối tượng ObjetString và ObjetCalcul: một đối tượng thực hiện các hoạt động trên chuỗi ký tự, một đối tượng thực hiện các phép tính số. Để đơn giản hóa, đối tượng ObjetString cung cấp một phương thức NbOccurrences(s,w) để tính toán số lần xuất hiện của một ký tự w trong chuỗi ký tự s và đối tượng ObjetCalcul cung cấp một phương thức cộng Add (a, b) của hai số a và b, xem giải thuật hai phương thức ở bên dưới. Sử dụng RMI để triển khai hai đối tượng với các phương thức tương ứng.
public int Add (int a, int b) {
return a+b;
}
public int NbOccurrences(String s, String w) {
int len = s.length();
int count=0;//Biến đếm khởi tạo ban đầu = 0
for (int i=0; i<len; i++)
{
if ((s.substring(i, i+1)).equals(w))
count++;//Nếu bằng thì tăng biến đếm thêm 1
}
return count;
}