定義:RPC(Remote Procedure Call Protocol)——遠程過程調用協議 ,RPC協議假定某些傳輸協議的存在,如TCP或UDP,爲通訊程序之間攜帶信息數據。在OSI網絡通訊模型中,RPC跨越了傳輸層和應用層 ,RPC使得開發包括網絡分佈式多程序在內的應用程序更加容易。面試
個人理解:與其說把RPC 看做是一種協議,倒不如把 它看做是一種 客戶機/服務器交互的模式,可是 RPC必定是基於 TCP 或者 其餘 通訊協議的緩存
下面咱們來看一下一個RPC調用的流程涉及哪些通訊細節:bash
RPC的目標就是要2~8這些步驟都封裝起來,讓用戶對這些細節透明。服務器
public interface IRpcService extends Serializable{
}
複製代碼
public interface IHelloService extends IRpcService{
String sayHi(String name,String message);
}
複製代碼
public class HelloServiceImpl implements IHelloService{
private static final long serialVersionUID = 146468468464364698L;
@Override
public String sayHi(String name, String message) {
return new StringBuilder().append("hi~!").append(",").append(message).toString();
}
}
複製代碼
注:這個地方 我沒有采用dom4j 解析配置文件的形式 進行接口註冊 有時間的朋友能夠多加一層網絡
public interface Server {
//Socket端口
int PORT = 8080;
//啓動服務端
void start() throws IOException;
//中止服務端
void stop();
/**
* 服務註冊
* -- serviceInterface 對外暴露接口
* -- 內部實現類
*/
void regist(Class<? extends IRpcService> serviceInterface,Class<? extends IRpcService> impl);
}
複製代碼
public class ServerCenter implements Server{
/**線程池 接收客戶端調用**/
private static ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 20, 200, TimeUnit.MILLISECONDS,new ArrayBlockingQueue<Runnable>(10));
/**服務註冊緩存**/
public static final Map<String,Class<?>> serviceRegistry = new HashMap<>();
/**
* 啓動服務
*/
@Override
public void start() throws IOException {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(PORT));
try {
while(true){
executor.execute(new ServiceTask(server.accept()));
}
} finally {
server.close();
}
}
/**
* 中止服務
*/
@Override
public void stop() {
executor.shutdown();
}
/**
* 註冊服務
*/
@Override
public void regist(Class<? extends IRpcService> serviceInterface, Class<? extends IRpcService> impl) {
serviceRegistry.put(serviceInterface.getName(), impl);
}
private static class ServiceTask implements Runnable{
Socket client = null;
public ServiceTask(Socket client) {
this.client = client;
}
@Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
input = new ObjectInputStream(client.getInputStream());
String serviceName = input.readUTF();
String methodName = input.readUTF();
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
Object[] arguments = (Object[]) input.readObject();
Class<?> serviceClass = serviceRegistry.get(serviceName);
if(serviceClass == null){
throw new ClassNotFoundException(serviceName + "not found");
}
Method method = serviceClass.getMethod(methodName, parameterTypes);
Object result = method.invoke(serviceClass.newInstance(), arguments);
//將執行結果反序列化 經過socket返給客戶端
output = new ObjectOutputStream(client.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
if(input != null){
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(output != null){
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(client != null){
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) throws Exception {
ServerCenter center = new ServerCenter();
center.regist(IHelloService.class,new HelloServiceImpl().getClass());
center.start();
}
}
複製代碼
public class Client {
@SuppressWarnings("unchecked")
public static <T extends IRpcService>T getRemoteProxyObj(final Class<? extends IRpcService> serviceInterface,final InetSocketAddress addr){
return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = null;
ObjectOutputStream output = null;
ObjectInputStream input = null;
try {
//1.建立Socket客戶端,根據指定地址鏈接遠程服務提供者
socket = new Socket();
socket.connect(addr);
//2.將遠程服務調用所需的接口類、方法名、參數列表等編碼後發送給服務提供者
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
//3.同步阻塞等待服務器返回應答 獲取應答後返回
input = new ObjectInputStream(socket.getInputStream());
return input.readObject();
} finally{
if(socket != null){
socket.close();
}
if(output != null){
output.close();
}
if(input != null){
input.close();
}
}
}
});
}
}
複製代碼
注:測試以前 須要開啓服務端併發
public class RpcTest {
public static void main(String[] args) throws IOException {
IHelloService service = Client.getRemoteProxyObj(IHelloService.class, new InetSocketAddress(8080));
System.out.println(service.sayHi("張三", "新年快樂!"));
}
}
複製代碼
就這樣咱們實現了一個簡陋的RPCapp
本文意在經過實現簡單的RPC,去真正意義上對RPC框架的實現原理有初步的瞭解,而不是人云亦云。框架
此RPC實現有諸多缺點,可是 咱們只要明白RPC的基座 其餘的RPC框架只是完善基座以及擴展而已 。dom
Java學習、面試;文檔、視頻資源免費獲取socket