ITKeyword,专注技术干货聚合推荐

注册 | 登录

自己实现一个RPC框架

three_man 分享于 2014-10-20

推荐:RPC框架

作者:用心阁 链接:https://www.zhihu.com/question/25536695/answer/36197244 来源:知乎 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出

RPC简介


远程调用协议。 有多有用,多普遍就不摆了。 大概过程: 1. 调用客户端句柄,传输参数。 2. 封装参数等消息为网络传输格式传给远程主机 3. 服务器句柄得到消息并解析参数 4. 在服务器段执行要调用的代码,并把结果返回给服务器句柄 5. 服务器句柄封装返回结果并用网络传输给客户端 6. 客户端解析并进行其他处理 可见之问题主要有,通信方式、句柄实现、以及消息封装和解析(序列化及反序列化)

RPC 之 通信

直接用Socket走TCP/IP 或者是UDP协议, 例如RMI

走HTTP协议,  Hessian。 好处不用防火墙再开端口了

在下面的例子中,使用java socket。这样可以看到更基础的内容。


RPC 之动态代理

句柄部分我们可以使用动态代理,在调用客户端方法的时候使用动态代理来调用远程方法


RPC 之序列化与反序列化

这个也有很多种,比如Java自带的序列化机制,实现了Serializable接口的类,都能够进行序列化

对于Java序列化的原理可以看下下面的博文:

http://www.java3z.com/cwbwebhome/article/article8/862.html

是Java处理二进制的一贯风格,定义了标志位表示后面有多长的内容表示了一个什么,然后是具体的内容。


Java的反序列化。

最好的办法当然是看ObjectInputStream的源代码了。

大概的流程是:

1. readObject0  入口

2. 然后就是一段一段读取然后解析了。一般最开始读取Class readClass,读取这个类的Class接口

3. readOrdinaryObject 使用newInstance来创建实例

4. 然后用defaultReadFields 中的desc.setObjFieldValues内容来个这个实例的字段赋值。

5. 这是一个循环的过程,知道把对象中所有的内容都还原。


还可以有其他的方式,比如WebService技术是使用了Soap协议来进行传输的,使用的是类似于XML格式的方式描述消息内容的(因此消息内容会比较臃肿)


Hessian是使用了二进制的方式进行了序列化,它使用了自己的规则,该规则可以用其他语言实现,是一个简单而且高效的协议。

细节链接如下:http://hessian.caucho.com/doc/hessian-serialization.html

推荐:一个高性能RPC框架原理剖析

业务与底层网络通信分离 Server大部分主要分为两层: 网络接收层:负责监听端口,负责收包,编码,解码工作,负责将响应包回传给客户端。 业务处理层:负责接收


在下面的例子中使用ObjectInputStream来进行序列化操作。


自己的RPC框架

服务器端

启动Socket并且给每个请求创建一个处理线程:

public class StartUp {
	public static final int port = 8080;
	
	public static void main(String[] args) {
			exportRpc();
	}
	
	/**
	 * 导出RPC接口
	 */
	private static void exportRpc() {
		try {
			ServerSocket ss = new ServerSocket(9001);
			while(true){
				Socket s = ss.accept();
				new RpcThread(s).start();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

然后定义传递的消息内容,要包含了客户端要告诉服务器端的内容,最少要包含,调用的接口名,调用的方法名,以及方法参数。该对象如下:

public class RpcObject implements Serializable{
	private static final long serialVersionUID = 1L;
	private Class c;
	private String methodName;
	private Object[] args;
	
	public RpcObject() {
	}
	
	public RpcObject(Class c, String methodName, Object[] args) {
		this.c = c;
		this.methodName = methodName;
		this.args = args;
	}
	public Class getC() {
		return c;
	}
	public void setC(Class c) {
		this.c = c;
	}
	public String getMethodName() {
		return methodName;
	}
	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}
	public Object[] getArgs() {
		return args;
	}
	public void setArgs(Object[] args) {
		this.args = args;
	}
}

服务器端处理线程,将会出去参数对象,然后通过反射来得到具体的实例对象以及调用需要调用的方法。

public class RpcThread extends Thread {
	private Socket s;
	public RpcThread(Socket s) {
		this.s = s;
	}
	
	@Override
	public void run() {
		ObjectInputStream is = null;
		ObjectOutputStream os = null;
		try {
			is = new ObjectInputStream(s.getInputStream());
			// 得到远程调用参数,包含了接口名,调用方法,方法参数
			RpcObject rpcObject = (RpcObject) is.readObject();
			// 构建接口的实现类,然后通过反射调用方法
			Object o = getObject(rpcObject.getC());
			Object reO = executeMethod(o, rpcObject.getMethodName(), rpcObject.getArgs());
			
			// 输出返回值
			os = new ObjectOutputStream(s.getOutputStream());
			os.writeObject(reO);
			os.flush();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} finally {
			try {
				is.close();
				os.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 通过反射技术执行方法,并返回返回值
	 * @param o
	 * @param methodName
	 * @param args
	 * @return
	 */
	private Object executeMethod(Object o, String methodName, Object[] args) {
		Object objR = null;
		Class[] cs = new Class[args.length];
		for (int i = 0; i < args.length; i++) {
			Object arg = args[i];
			cs[i] = arg.getClass();
		}
		try {
			Method m = o.getClass().getMethod(methodName, cs);
			objR = m.invoke(o, args);
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
		return objR;
	}
	
	/**
	 * 根据接口名得到实例
	 * @param c
	 * @return
	 */
	private Object getObject(Class c) {
		Object o = null;
		try {
<span style="white-space:pre">			</span>o = ConfMonitor.conf.get(c.getName()).newInstance();
<span style="white-space:pre">		</span>} catch (InstantiationException e) {
<span style="white-space:pre">			</span>e.printStackTrace();
<span style="white-space:pre">		</span>} catch (IllegalAccessException e) {
<span style="white-space:pre">			</span>e.printStackTrace();
<span style="white-space:pre">		</span>}
		return o;
	}
}
服务器端配置文件,主要是配置接口对应了哪个实现类:

/**
 * 模拟配置,实际的框架中大部分都是使用xml进行配置的,比如Hessian是配置在web.xml的servlet属性里的
 * @author cdwangzijian
 *
 */
public class ConfMonitor {
	public static Map<String, Class> conf = new HashMap<String, Class>();
	
	static {
		conf.put("com.prince.rpc.service.IHello", HelloImpl.class);
	}
}

客户端

使用动态代理,给每一个方法调用都变为远程调用。

public class ProxyFactory {

	public static <T> T create(Class<T> c, String ip, int port) {
		 InvocationHandler handler = new RpcProxy(ip, port, c);
		
		return (T) Proxy.newProxyInstance(c.getClassLoader(),
                new Class[] {c },
                handler);
	}
}

代理将会封装远程参数,把调用的接口,方法名,参数传给远程,并且获得返回值

/**
 * 客户端接口代理
 * 当客户端接口方法被调用的时候,把方法名,方法参数作为参数。
 * 传送给远程服务执行,然后获取返回值
 * @author cdwangzijian
 *
 */
public class RpcProxy implements InvocationHandler, Serializable{
	private String ip;
	private int port;
	private Class c;
	
	private static final long serialVersionUID = 1L;

	public RpcProxy(String ip, int port, Class c) {
		this.ip = ip;
		this.port = port;
		this.c = c;
	}
	
	/**
	 * 动态代理类,当调用接口方法的时候转为调用此方法
	 */
	@Override
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object o = null;		// 用作返回值
		// 通过socket调用远程服务
		Socket s = new Socket(ip, port);
		// 组装为一个保留了要调用的类,方法名及参数的对象,然后序列化之后传给远程
		RpcObject rpcObject = new RpcObject(c, method.getName(), args);
		ObjectOutputStream os = null;
		ObjectInputStream is = null;
		try{
			os = new ObjectOutputStream(s.getOutputStream());
			os.writeObject(rpcObject);
			os.flush();
			// 从远程得到返回结果
			is = new ObjectInputStream(s.getInputStream());
			o = is.readObject();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			os.close();
			is.close();
		}
		
		return o;
	}
	
}
最后是调用部分,获得代理类。

public class RpcClient {
	public static void main(String[] args) {
		String ip = "localhost";
		int port = 9001;
		IHello hello = ProxyFactory.create(IHello.class, ip, port);
		System.out.println(hello.sayHello("wzj"));
	}
}

下面是用作测试的接口及实现类的代码:

public interface IHello {
	String sayHello(String name);
}

public class HelloImpl implements IHello {
	@Override
	public String sayHello(String name) {
		return "hello:" + name;
	}
}

最后就是源代码的下载路径了:  

http://download.csdn.net/detail/three_man/8059871


推荐:一个简单RPC框架是如何炼成的(IV)——实现RPC消息的编解码

之前我们制定了一个很简单的RPC消息 的格式,但是还遗留了两个问题,上一篇解决掉了一个,还留下一个 我们并没有实现相应的encode和decode方法,没有基于可以跨

RPC简介 远程调用协议。 有多有用,多普遍就不摆了。 大概过程: 1. 调用客户端句柄,传输参数。 2. 封装参数等消息为网络传输格式传给远程主机 3. 服务器句柄得到消息并解析参数 4. 在服务器段

相关阅读排行


用户评论

游客

相关内容推荐

最新文章

×

×

请激活账号

为了能正常使用评论、编辑功能及以后陆续为用户提供的其他产品,请激活账号。

您的注册邮箱: 修改

重新发送激活邮件 进入我的邮箱

如果您没有收到激活邮件,请注意检查垃圾箱。