最近看到一个远程调用的简单实现,于是加上自己的理解分享给大家。
远程调用是典型的CS模型,Server端提供服务,客户端调用得到结果
先看服务端提供服务的方法
1 /**
2 * 提供服务
3 *
4 * @param service 服务实现
5 * @param port 端口(可以双发约定)
6 * @throws Exception
7 */
8 public static void provide(final Object service, final int port) throws Exception {
9 //参数检查
10 if (service == null) {
11 throw new IllegalArgumentException("The service can't be null!");
12 }
13 if (port > 65535) {
14 throw new IllegalArgumentException("The host can't greater than 65535!");
15 }
16 //开启一个ServerSocket接收请求
17 ServerSocket serverSocket = new ServerSocket(port);
18 //死循环等待请求
19 while (true) {
20 //接受到请求,获取socket
21 final Socket socket = serverSocket.accept();
22 try {
23 //开启一个线程处理
24 new Thread(new Runnable() {
25 @Override
26 public void run() {
27 try {
28 try {
29 //重socket中获取输入流
30 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
31 try {
32 //获取方法名
33 String methodName = ois.readUTF();
34 //获取方法参数数组
35 Class[] methodParameterTypes = (Class[]) ois.readObject();
36 //获取参数值数组
37 Object[] arguments = (Object[]) ois.readObject();
38 //根据方法名和方法参数获取方法(根据方法名和方法参数可以唯一定位到一个方法)
39 Method method = service.getClass().getMethod(methodName, methodParameterTypes);
40 if (method == null) {
41 throw new NoSuchMethodException();
42 }
43 //执行方法
44 Object result = method.invoke(service, arguments);
45 System.out.println("Method:" + methodName + ";Arguments:" + arguments + " invoke!");
46 //获取socket输出流
47 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
48 try {
49 //输出结果
50 oos.writeObject(result);
51 } finally {
52 oos.close();
53 }
54 } finally {
55 ois.close();
56 }
57 } finally {
58 socket.close();
59 }
60 } catch (Exception e) {
61 //记个日志啥的
62 e.printStackTrace();
63 }
64 }
65 }).start();
66 } catch (Exception e) {
67 //记个日志啥的
68 e.printStackTrace();
69 }
70 }
71 }
然后是消费的方法
**
* 消费服务
*
* @param clazz 接口类
* @param host 发布服务机器的host
* @param port 发布服务机器的port
* @return
*/
public static Object consume(final Class clazz, final String host, final int port) {
//参数检查
if (clazz == null) {
throw new IllegalArgumentException("The clazz can't be null!");
}
if (host == null || host.isEmpty()) {
throw new IllegalArgumentException("The host can't be null or empty!");
}
if (port > 65535) {
throw new IllegalArgumentException("The host can't greater than 65535!");
}
//生成代理,每次调用方法其实是调用远程的服务
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
//建立socket链接
Socket socket = new Socket(host, port);
try {
//获取输出流
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
try {
String methodName = method.getName();
Class[] methodParameterTypes = method.getParameterTypes();
//输出要调用的方法名
oos.writeUTF(methodName);
//输出要调用的方法参数列表
oos.writeObject(methodParameterTypes);
//输出要调用的方法参数
oos.writeObject(arguments);
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
try {
//获取结果
Object result = ois.readObject();
//可能返回的对象是异常
if (result instanceof Throwable) {
throw (Throwable) result;
}
return result;
} finally {
ois.close();
}
} finally {
oos.close();
}
} finally {
socket.close();
}
}
});
return proxy;
}
一般端口可以双方约定,而host可以采用configServer的方法解决,也就是开启一个服务,当启动一个服务的时候到configServer注册一下(服务名+host),如果是多台服务器提供服务,host就是一个list,调用方发起调用的时候首先到configServer根据服务名获取host列表,然后选一个host发起调用!configServer的优点是可以做到很多控制,比如流量控制,权重控制,调用host列表维护(死掉就剔除,重试机制)等等,这样调用方不用关心我调用的是哪台机器,只用关心我调用哪个方法。但也有坏处,一旦configServer挂掉了.......(其实也可以通过MS或调用方本地缓存调用列表解决)。
一般由调用方提供一个接口包(算是一个双方的约定),接口类中定义了提供发提供的方法
如我们发布一个简单的服务
1 public interface Girl {
2 //提供服务
3 String server(String name);
4 }
实现
1 public class GirlImpl implements Girl{
2 @Override
3 public String server(String name) {
4 return name+"亚美爹";
5 }
6 }
发布服务
这里你可以写在一个main方法中,也可以配置一个Spring的bean,并配置init方法,然后在init方法中开启
1 Girl beautifulGirl=new GirlImpl();
2 try {
//在本机的1111端口上开启Girl的服务
3 Utils.provide(beautifulGirl, 1111);
4 } catch (Exception e) {
5 e.printStackTrace();
6 }
消费
1 try {
2 //从此你就获得了一个漂亮妹子,她可以给你提供各种服务
3 Girl beautifulGirl= (Girl)Utils.consume(Girl.
class, "127.0.0.1", 3333);
4 //你可以来一个循环,或者来一个死循环,一直哈哈
5 beautifulGirl.server("yourName");
6 }
catch (Exception e) {
7 e.printStackTrace();
8 }
总结:
其实远程调用也就是获取服务的一个代理,每当你调用服务的方法事,他都会想服务方传去方法,方法参数列表,参数,前两个用于唯一确定一个方法,后一个用于方法调用。
这里实现的很简答,当然还有很复杂的,比如Spring的实现,淘宝的HSF等等
以上为个人理解,如果有错的地方,欢迎指正。