本文以極簡的Java程式碼演示RPC框架的基本原理。
完整教程請訪問:https://bigbird.blog.csdn.net/
需求描述:
1.客戶端呼叫遠端服務ProductService、UserSerivce的介面,並列印結果
2.服務端提供具體的服務實現類,接受客戶端請求,並返回響應
3.客戶端像使用本地方法一樣呼叫遠端介面,對網路通訊、序列化等細節無感知
廢話少說,直接上程式碼!
### 遠端介面定義
```
public interface IProductService {
public Product findProductById(Integer id);
}
```
```
public interface IUserSerivce {
public User findUserById(Integer id);
}
```
```
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
public Product(Integer id, String name) {
this.name = name;
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Product{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
```
```
public class User implements Serializable {
private static final long serialVersionUID = 2L;
private Integer id;
private String name;
public User(Integer id, String name) {
this.name = name;
this.id = id;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
```
### 客戶端程式碼
服務呼叫者( RPC客戶端)獲取服務提供方(RPC服務端)提供的服務,並像呼叫本地介面一樣呼叫遠端方法。
```
public class Client {
public static void main(String[] args) {
//客戶端指定需要呼叫的遠端服務(客戶端可以注入服務提供方的服務)、呼叫服務的介面方法
IProductService productService = (IProductService) Stub.getstub(IProductService.class);
System.out.println(productService.findProductById(1));
IUserSerivce userSerivce = (IUserSerivce) Stub.getstub(IUserSerivce.class);
System.out.println(userSerivce.findUserById(123));
}
}
```
```
/**
* Stub用於代理服務端的方法,對服務呼叫者(客戶端)遮蔽socket連線、序列化等細節
*/
public class Stub {
public static Object getstub(Class clazz) {
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//模擬服務發現,直接socket連線到服務提供者的機器上,把要呼叫的服務名和方法名、方法引數傳到服務端,服務端收到後解析請求並執行方法呼叫返回資料
Socket socket = new Socket("127.0.0.1", 9999);
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
String clazzName = clazz.getName();
String methodName = method.getName();
Class<?>[] parameterTypes = method.getParameterTypes();
oos.writeUTF(clazzName);
oos.writeUTF(methodName);
oos.writeObject(parameterTypes);
oos.writeObject(args);
oos.flush();
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object o = ois.readObject();
oos.close();
socket.close();
return o;
}
};
Object o = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
return o;
}
}
```
### 服務端程式碼
```
//具體的服務實現類
public class ProductServiceImpl implements IProductService {
@Override
public Product findProductById(Integer id) {
return new Product(id, "productA");
}
}
public class UserServiceImpl implements IUserSerivce {
@Override
public User findUserById(Integer id) {
return new User(id, "UserA");
}
}
```
```
/**
* 服務端不斷接受客戶端請求,按照雙方約定的格式(協議)反序列化服務端發來的訊息、完成服務端本地介面呼叫、返回序列化資料
*/
public class Server {
private static boolean running = true;
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);
while (running) {
Socket socket = serverSocket.accept();
process(socket);
socket.close();
}
serverSocket.close();
}
private static void process(Socket socket) throws Exception {
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
ObjectInputStream ois = new ObjectInputStream(inputStream);
//按照服務端寫入的格式來反序列化資料
String clazzName = ois.readUTF();
String methodName = ois.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) ois.readObject();
Object[] args = (Object[]) ois.readObject();
//模擬從服務登錄檔查詢到具體的服務實現類
Class clazz = findService(clazzName);
Method method = clazz.getMethod(methodName, parameterTypes);
Object o = method.invoke(clazz.newInstance(), args);
ObjectOutputStream oos = new ObjectOutputStream(outputStream);
oos.writeObject(o);
oos.flush();
}
private static Class findService(String clazzName) throws Exception {
if (IProductService.class.getName().equals(clazzName)) {
return ProductServiceImpl.class;
} else if (IUserSerivce.class.getName().equals(clazzName)) {
return UserServiceImpl.class;
} else {
throw new Exception("no service found");
}
}
}
```
### 執行演示
先執行Server、再執行Client
一次完整的RPC呼叫流程如下:
1)服務消費方(client)呼叫以本地呼叫方式呼叫服務;
2)client stub接收到呼叫後負責將方法、引數等組裝成能夠進行網路傳輸的訊息實體;
3)client stub找到服務地址,並將訊息傳送到服務方;
4)服務提供方server 端收到訊息後進行解碼;
5)server 端根據解碼結果呼叫本地介面;
6)server 端將本地介面呼叫結果組裝成訊息併發送至消費方;
7)client stub接收到訊息,並進行解碼、反序列化;
8)服務消費方得到最終結果。