2012年12月26日

简单的远程调用实现

最近看到一个远程调用的简单实现,于是加上自己的理解分享给大家。
远程调用是典型的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等等


以上为个人理解,如果有错的地方,欢迎指正。

posted @ 2012-12-26 19:25 Evan.lee 阅读(1961) | 评论 (0)编辑 收藏

<2012年12月>
2526272829301
2345678
9101112131415
16171819202122
23242526272829
303112345

导航

统计

常用链接

留言簿

随笔档案

搜索

最新评论