簡單RPC之Socket實現

最近在學習RPC,看了些文章和代碼,RPC的底層是通過SOCKET通信來實現的,這篇文章就是關於RPC的Socket簡單實現。

rpc的相關概念可以參考這篇文章:http://blog.csdn.net/yinwenjie/article/details/49453303

一、技術方案

使用比較原始的方案實現RPC框架,採用Socket通信、動態代理與反射與Java原生的序列化。

二、RPC框架架構

1)服務提供者,運行在服務器端,提供服務接口定義與服務實現類。

2)服務中心,運行在服務器端,負責將本地服務發佈成遠程服務,管理遠程服務,提供給服務消費者使用。

3)服務消費者,運行在客戶端,通過遠程代理對象調用遠程服務。

三、RPC架構圖


四、具體實現

1、client端代碼

package org.weir.rpc.BioRpc.client;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.Socket;

public class BioClientInvocationHandler implements InvocationHandler{
	Class serviceinterface = null;
	int port;
	public BioClientInvocationHandler(Class rpcInterface,int port){
		this.serviceinterface = rpcInterface;
		this.port = port;
	}
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// TODO Auto-generated method stub
		Socket socket = null;
		ObjectOutputStream output = null;
		ObjectInputStream input = null;
		try {
			// 2.創建Socket客戶端,根據指定地址連接遠程服務提供者
			socket = new Socket();
			socket.connect(new InetSocketAddress(port));

			// 3.將遠程服務調用所需的接口類、方法名、參數列表等編碼後發送給服務提供者
			output = new ObjectOutputStream(socket.getOutputStream());
			output.writeUTF(serviceinterface.getName());
			output.writeUTF(method.getName());
			output.writeObject(method.getParameterTypes());
			output.writeObject(args);

			// 4.同步阻塞等待服務器返回應答,獲取應答後返回
			input = new ObjectInputStream(socket.getInputStream());
			return input.readObject();
		} finally {
			if (socket != null)
				socket.close();
			if (output != null)
				output.close();
			if (input != null)
				input.close();
		}
	}

}

package org.weir.rpc.BioRpc.client;

import java.lang.reflect.Proxy;

public class BIOClient<T> {
	public int port;

	public <T> T refer(Class<T> rpcInterface) {
		return getProxy(rpcInterface);
	}

	public BIOClient(int port) {
		this.port = port;
	}

	@SuppressWarnings("unchecked")
	public <T> T getProxy(Class<T> rpcInterface) {
		return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class<?>[] { rpcInterface },
				new BioClientInvocationHandler(rpcInterface,port));
	}

	
}
2、server端代碼

server端接口類,爲了以後擴展,所以寫出接口形式

package org.weir.rpc.BioRpc.server;

import java.io.IOException;

public interface BIOServer {
	public void stop();

	public void start() throws IOException;

	public void register(Class serviceInterface, Class impl);

	public boolean isRunning();

	public int getPort();
}

server端實現類

package org.weir.rpc.BioRpc.server;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;

public class BIOServiceCenter implements BIOServer {
	private static int port;
	private static final HashMap<String, Class> serviceRegistry = new HashMap<String, Class>();
	private static boolean isRunning = false;
	
	public BIOServiceCenter(int port) {
		this.port = port;
	}

	@Override
	public void stop() {
		// TODO Auto-generated method stub
		isRunning = false;
	}

	@Override
	public void start() throws IOException {
		// TODO Auto-generated method stub
		ServerSocket serverSocket = new ServerSocket();
		serverSocket.bind(new InetSocketAddress(port));
		System.out.println("[weir-rpc]->start server!!!");
		ObjectOutputStream output = null;
		try {
			while (true) {
				Socket socket = serverSocket.accept();
				ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
				String serviceName = ois.readUTF();
				String methodName = ois.readUTF();
				Class<?>[] parameterTypes = (Class<?>[]) ois.readObject();
                Object[] arguments = (Object[]) ois.readObject();
				Class<?> serviceClass = serviceRegistry.get(serviceName);
				if (serviceClass == null) {
					throw new ClassNotFoundException("[weir-rpc]" + serviceName + "not found");
				}	
				Method method = serviceClass.getMethod(methodName, parameterTypes);
				Object result = method.invoke(serviceClass.newInstance(), arguments);
				 // 3.將執行結果反序列化,通過socket發送給客戶端
                output = new ObjectOutputStream(socket.getOutputStream());
                output.writeObject(result);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	@Override
	public void register(Class serviceInterface, Class impl) {
		// TODO Auto-generated method stub
		serviceRegistry.put(serviceInterface.getName(), impl);
	}

	@Override
	public boolean isRunning() {
		// TODO Auto-generated method stub
		return isRunning;
	}

	@Override
	public int getPort() {
		// TODO Auto-generated method stub
		return port;
	}

}
3、test類

package org.weir.rpc;

import java.io.IOException;

import org.weir.rpc.BioRpc.client.BIOClient;
import org.weir.rpc.BioRpc.server.BIOServer;
import org.weir.rpc.BioRpc.server.BIOServiceCenter;
import org.weir.rpc.api.RpcService;
import org.weir.rpc.api.impl.RpcServiceImpl;

public class TestBIORpc {
	public static void main(String[] args) throws IOException {
		new Thread(new Runnable() {
			public void run() {
				try {
					BIOServer serviceServer = new BIOServiceCenter(8088);
					serviceServer.register(RpcService.class, RpcServiceImpl.class);
					serviceServer.start();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}).start();
		BIOClient bioClient = new BIOClient(8089);
		
		RpcService service = (RpcService) bioClient.getProxy(RpcService.class);
		System.out.println(service.hello("hello"));
	}
}
4、遠程service類接口

package org.weir.rpc.api;

public interface RpcService {
	public String hello(String hi);
}
service實現類

package org.weir.rpc.api.impl;

import org.weir.rpc.api.RpcService;

public class RpcServiceImpl implements RpcService{

	@Override
	public String hello(String hi) {
		// TODO Auto-generated method stub
		System.out.println("[weir-Rpc]->hello"+hi);
		return "[weir-Rpc]->hello"+hi;
	}

}
運行test類,結果如下


五、總結

    RPC調用框架就是屏蔽了底層調用的具體細節,使得調用遠程服務可以像調用本地服務一樣方便。

     這裏實現的簡單RPC框架是使用Java語言開發,與Java語言高度耦合,並且通信方式採用的Socket是基於BIO實現的,IO效率不高,還有Java原生的序列化機制佔內存太多,運行效率也不高。可以考慮從下面幾種方法改進。

  1. 可以使用NIO或直接使用Netty替代BIO實現;
  2. 使用開源的序列化機制,如JSON、kryo、hessian等序列化機制,提高效率。
  3. 這篇文件的服務註冊直接使用HashMap來註冊,如果server服務宕機會失去服務的註冊信息,所以服務註冊可以使用Zookeeper進行管理,能夠讓應用更加穩定。
  4. 同步等待機制對於複雜計算服務不太合適,可以考慮使用異步的通信方式。
  5. 可以專門封裝一個消息對象,用來通信,更加面向對象。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章