一瞳筏、基本原理
1、RPC模型
流程:當 User 想發(fā)起一個遠程調用時慌盯,它實際是通過本地調用 User-stub周霉。 User-stub 負責將調用的接口、方法和參數(shù)通過約定的協(xié)議規(guī)范進行編碼并通過本地的 RPCRuntime 實例傳輸?shù)竭h端的實例亚皂。 遠端 RPCRuntime 實例收到請求后交給 Server-stub 進行解碼后發(fā)起向本地端 Server 的調用俱箱,調用結果再返回給 User 端。
2灭必、RPC組件細化
1狞谱、RpcServer:負責暴露(export)遠程接口
2乃摹、RpcClient:負責導入(import)遠程接口的代理實現(xiàn)
3、RpcProxy:遠程接口的代理實現(xiàn)
4跟衅、RpcInvoker:客戶端:負責編碼調用信息和發(fā)送調用請求到服務端并等待調用結果返回孵睬;服務端:負責調用服務端接口的具體實現(xiàn)并返回調用結果
5:、RpcProtocol:負責協(xié)議編/解碼
6伶跷、RpcConnector:負責維持客戶端和服務端的連接通道和發(fā)送數(shù)據(jù)到服務端
7掰读、RpcAcceptor:負責接收客戶端請求并返回請求結果
8、RpcProcessor:負責在服務端控制調用過程叭莫,包括管理調用線程池磷支、超時時間等
9、RpcChannel:數(shù)據(jù)傳輸通道
二食寡、RPC簡易實現(xiàn)
1、項目結構
2廓潜、公共服務接口
public interface HelloService {
String sayHello(String name);
}
3抵皱、服務端實現(xiàn)
(1)實現(xiàn)類
public class HelloServiceImpl implements HelloService {
public String sayHello(String name) {
return "hello " + name;
}
}
(2)服務端Rpc服務類實現(xiàn)
其主要邏輯
1、注冊服務類
2辩蛋、啟動服務socket呻畸,循環(huán)等待客戶端連接
3、獲取客戶端傳送的信息
4悼院、根據(jù)信息解析并調用本地實現(xiàn)伤为,并回傳調用值
public class RpcServer {
private static final HashMap<String, Class<?>> serviceRegistry = new HashMap<String, Class<?>>();
private int port;
public RpcServer(int port) {
this.port = port;
}
public RpcServer register(Class serviceInterface, Class impClass) {
//注冊服務(接口名:實現(xiàn)類名)
serviceRegistry.put(serviceInterface.getName(), impClass);
return this;
}
public void run() throws IOException {
//1、啟動服務
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(port));
System.out.println("start server据途!");
ObjectInputStream input = null;
ObjectOutputStream output = null;
Socket socket = null;
try {
//2绞愚、循環(huán)等待連接
for (; ; ) {
//3、獲取客戶端的請求數(shù)據(jù)
socket = serverSocket.accept();
input = new ObjectInputStream(socket.getInputStream());
String serviceName = input.readUTF();//客戶端傳過來的接口名
String methodName = input.readUTF();//客戶端傳過來的方法名
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();//客戶端傳過來的方法參數(shù)類型
Object[] args = (Object[]) input.readObject();//客戶端傳過來的參數(shù)值
System.out.println("接受到客戶端的請求:serviceName:" + serviceName + ",methodName:" + methodName);
//4颖医、根據(jù)客戶端傳過信息獲取注冊的服務端接口,并調用
Class<?> serviceClass = serviceRegistry.get(serviceName);
if (serviceClass == null) {
throw new ClassNotFoundException(serviceName + " not found!");
}
Method method = serviceClass.getMethod(methodName, parameterTypes);
Object invokeResult = method.invoke(serviceClass.newInstance(), args);
//5位衩、返回調用的方法
output = new ObjectOutputStream(socket.getOutputStream());
output.writeObject(invokeResult);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close(input, output, socket);
}
}
private void close(ObjectInputStream input, ObjectOutputStream output, Socket socket) {
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
(3)服務端啟動類
public class ServerMain {
public static void main(String[] args) throws IOException {
RpcServer rpcServer = new RpcServer(8888);
rpcServer.register(HelloService.class, HelloServiceImpl.class);
rpcServer.run();
}
}
3、客戶端實現(xiàn)
主要邏輯
1熔萧、獲取ip和port糖驴,通過jdk代理遠程調用服務端
2、向服務端傳遞服務接口相關信息佛致,并獲取服務端返回的信息
(1)客戶端代理類實現(xiàn)
public class RpcClientProxy<T> implements InvocationHandler {
/**
* 服務端代理接口
*/
private Class<T> serverInstance;
/**
* 服務端地址
*/
private InetSocketAddress address;
public RpcClientProxy(Class<T> serverInstance, String ip, Integer port) {
this.address = new InetSocketAddress(ip, port);
this.serverInstance = serverInstance;
}
/**
* 獲取客戶端代理對象
*
* @return
*/
public T getClientInstance() {
return (T) Proxy.newProxyInstance(serverInstance.getClassLoader(), new Class<?>[]{serverInstance}, this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectOutputStream outputStream = null;
ObjectInputStream inputStream = null;
try {
// 1.創(chuàng)建Socket客戶端贮缕,根據(jù)指定地址連接遠程服務提供者
socket = new Socket();
socket.connect(address);
//2、將遠程服務調用所需的接口類俺榆、方法名感昼、參數(shù)列表等編碼后發(fā)送給服務提供者
outputStream = new ObjectOutputStream(socket.getOutputStream());
outputStream.writeUTF(serverInstance.getName());//接口名
outputStream.writeUTF(method.getName());//方法名
outputStream.writeObject(method.getParameterTypes());//方法參數(shù)類型,數(shù)組
outputStream.writeObject(args);//具體方法參數(shù)值
//3、同步阻塞等待服務器返回應答罐脊,獲取應答后返回
inputStream = new ObjectInputStream(socket.getInputStream());
return inputStream.readObject();
} finally {
if (socket != null) {
socket.close();
}
if (outputStream != null) {
outputStream.close();
}
if (inputStream != null) {
inputStream.close();
}
}
}
}
(2)客戶端啟動類
public class ClientMain {
public static void main(String[] args) {
RpcClientProxy clientProxy = new RpcClientProxy(HelloService.class, "127.0.0.1", 8888);
HelloService helloService = (HelloService) clientProxy.getClientInstance();
String result = helloService.sayHello("中國");
System.out.println(result);
}
}