Java简易RPC框架学习(三)---会话层

更多博客请关注

私自将这一层命名为会话层有些不厚道,在dubbo的rpc架构里面,这一层叫做信息交换层。这一层以Request、Response、为核心,封装请求响应模式,同步转异步。而且还随时监控着传输层Client端的连接情况。在我看来,这种管理请求响应的层也可以称为会话层。为了尊重dubbo中的定义,本层的门面类叫做Exchanger,即交换的意思。下面介绍到的代码都可以在我的GitHub上找到,会话层在exchanger包里面。

0、结构

如下图所示为信息会话层以及其下的结构,信息交互的主题是Future、Request和Response,只有在建立连接的时候会使用到门面类Exchanger的connect和bind方法。一旦建立连接就可以通过Client的request方法来发送请求,该请求实际是交给了ExhangerChannel来完成的,而请求响应的模式的实现是由Request、Future和Response三个类来完成的。

1、Future

Future在java中是一种异步消息的处理机制,Future一一对应一个Request与一个Response,Future内部提供了异步转同步,同步转异步的方法,查询Response完成状态的方法,以及设置Response完成状态的方法。根据上述描述,可以实现一个简易的Future。

public class MyFuture {
    private static final Map<Long, MyFuture> FUTURES   = new ConcurrentHashMap<Long, MyFuture>();
    private static final Map<Long, EndPoint>       CHANNELS   = new ConcurrentHashMap<Long, EndPoint>();
     TODO: 2016/11/24 这里的锁难道只是用来等待response的么
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();
    private final long id;
    private final EndPoint client;
    private final Request request;
    private ResponseCallback callback;
     TODO: 2016/11/24 这里使用同步变量
    private volatile Response response;
    public MyFuture(EndPoint client, Request request){
        this.client = client;
        this.request = request;
        this.id = request.getId();
        FUTURES.put(id, this);
        CHANNELS.put(id, client);
    }
     TODO: 2016/11/24 同步获取
    public Object get(){
        if(!isDone()){
            try{
                lock.lock();
                while(!isDone()){
                    done.await(1000, TimeUnit.MILLISECONDS);//等待
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
        return returnFromResponse();
    }
    public void setCallback(ResponseCallback callback){
        if(isDone()){
            invokeCallback(callback);
        }else{
            boolean isdone=false;
            lock.lock();
            try{
                if(!isDone()){
                    this.callback=callback;
                }else {
                    isdone=true;
                }
            }finally {
                lock.unlock();
            }
            if(isdone){
                //todo
                // 这句话貌似根本调用不到啊
                invokeCallback(callback);
            }
        }
    }
    public static void received(Response response){
        try{
            MyFuture future =FUTURES.remove(response.getId());
            if(future!=null){
                future.doReceived(response);
            }
        }finally {
            //ChannelHandler handler=CHANNELS.get(response.getId());
            //handler.close();
            CHANNELS.remove(response.getId());
        }
    }
    private void doReceived(Response res){
        lock.lock();
        try{
            response=res;
            if(done!=null){
                 TODO: 2016/11/24 结束等待
                done.signal();
            }
        }finally {
            lock.unlock();
        }
         TODO: 2016/11/24 回调在这里执行
        if(callback!=null){
            invokeCallback(callback);
        }
    }
    private void invokeCallback(ResponseCallback c){
         TODO: 2016/11/24 为什么这里要拷贝
        ResponseCallback callbackCopy = c;
        Response res=response;
        callbackCopy.done(res.getResult());
    }
    public boolean isDone(){
        return response!=null;
    }
    private Object returnFromResponse(){
        Response res=response;
        if(res==null){
            throw new IllegalStateException("response cannot be null");
        }
        return res.getResult();
    }
}

代码很长,但是每个函数都比较重要,所以下面会详细介绍。

构造方法需要将Request和与之对应的Client对象传入,以保证之后的操作能够正确对应到正确的Request上。

get方法是一个异步转同步的方法,该方法中会一直调用isDone方法来查看Response的状态,如果Response完成就会返回结果,否则会一直阻塞。这个方法中能够看到并发编程的一些技巧,例如使用了ReetrantLock(可重入锁)以及Condition(条件变量)来保证多个线程不会出错。还有一个小细节就是Response变量是violatile修饰的,于是Response的变动会及时地被通知到。

setCallBack方法用于设置回调,是异步回调初始化的时候设置回调函数用的,这个方法和invokeCallback共同使用,来实现同步转异步。

received和doReceived方法是用来更改Response变量状态的,当客户端收到服务器端的消息之后,调用received方法来更改Response的状态。

isDone方法返回Response是否完成。

2、Request和Response

本文章实现了简单的Request和Response类,复杂的Response类可能包括了各种状态,例如404,500之类的,但是本文中的Request和Response只包含了id以及消息体。其中Id的生成方法很有趣,使用了原子变量的递增保证了其唯一性。Request和Response都必须继承Serializable接口,因为它需要通过网络传输,于是序列化是必须的,如果消息体内部是用户自定义的类,那么该类也应该继承可序列化接口。如下所示为Request类的实现,暂且不列举Response类了。

public class Request implements Serializable {
    private final long mId;
    private Object  mData;
    private static final AtomicLong INVOKE_ID = new AtomicLong(0);
    public Request(long id){
        mId = id;
    }
    public Request() {
        mId = newId();
    }

    public Object getmData() {
        return mData;
    }

    public void setmData(Object mData) {
        this.mData = mData;
    }

    private static long newId() {
        // getAndIncrement()增长到MAX_VALUE时,再增长会变为MIN_VALUE,负数也可以做为ID
        return INVOKE_ID.getAndIncrement();
    }
    public long getId(){
        return this.mId;
    }
    private void writeObject(ObjectOutputStream o) throws IOException {
        o.writeObject(this.mData);
        o.writeObject(this.mId);
    }

    private void readObject(ObjectInputStream o) throws IOException, ClassNotFoundException {
        this.mData = (Object) o.readObject();
    }
}


3、会话层服务端

服务端的作用并不大,只是封装了Transport层的Server接口,如下所示为ExchangeServer的实现:

public class ExchangerServer {
    private final EndPoint server;
    public ExchangerServer(EndPoint server){
        if(server==null){
            throw new IllegalArgumentException("server==null");
        }
        this.server=server;
    }
}

4、会话层客户端

客户端的作用跟服务端差不多,也是封装了Transport层的Client的接口,但是不同的是Client会维护一个名为ExhangeChannel的实例,这个ExchangeChannel可以认为是Client与Server的一个通道,客户端的请求通过这个客户端来发送给服务端,ExchangeChannel在发送请求之后会返回一个Future实例,之后的事情就交给Future了。因此,Client、Server、ExchangeChannel是一对一的关系,而ExchangeChannel与Request、Response和Future是一对多的关系,这也符合一个连接会发送多条信息的原则。下面的代码为ExchangeClient的实现,可以看到request请求是交给ExchangeChannel来发送的,而且返回一个Future实例。

public class ExchangerClient{
    private EndPoint client;
    private final ExchangeChannel channel;
    public ExchangerClient(EndPoint client){
        this.client=client;
        this.channel=new ExchangeChannel(client);
    }
    public boolean isConnected(){
        return client.isConnected();
    }
    public MyFuture request(Object request){
        return channel.request(request);
    }
    public void close(){
        this.client.close();
    }
}

5、门面类

门面类Exchangers的作用同样是为了方便拓展,也许以后再技术迭代的时候希望使用其他的消息交换方式,而不是request、Response方式。如下图所示为Exchangers的实现:

// 工厂方法?
public class Exchangers {
    public static MyExchanger myExchanger;
    public static ExchangerServer bind(URL url, Replier<?>replier){
        return bind(url,new ExchangeHandlerDispatcher(replier));
    }
    public static ExchangerClient connect(URL url){
        return getExchanger().connect(url);
    }
    public static ExchangerServer bind(URL url,ExhangeHandler handler){
        return getExchanger().bind(url,handler);
    }
    public static MyExchanger getExchanger(){
        if(myExchanger==null) {
            synchronized (Exchangers.class) {
                if(myExchanger==null){
                    myExchanger=new MyExchanger();
                }
            }
        }
        return myExchanger;
    }
}

该门面类同样使用了单利工厂模式,对外提供了bind和connect方法。大家注意到这里的bind方法中又包涵了一个ExchangeHandlerDispatcher实例,这里的ExchangeHandlerDispatcher方法是干嘛的?先进行测试,大家自然明白了。

6、测试

6.1服务端启动

如下所示为测试类的实现,其中startServer方法传入端口号,绑定的地址写死为localhost。在调用门面类的bind方法之前,有一个初始化的步骤,可以看到定义了一个名为ReplierDispatcher的实例,ReplierDispatcher如其名字所示是一个分发器,其作用是针对不同的消息类型来进行不同的处理。在测试中我们给分发器新增了一个处理函数,即Object.class的处理函数(当然所有的类都是Object的子类),该处理函数的作用是等待5秒并返回一个字符串,字符串包含了request的数据。初始化分发器之后,将该分发器传递给Exchanger门面类,实际上,该分发器会一直被传递给Transporter层的Server实例,让Server实例在收到消息(received)之后调用该分发器的处理方法,于是就实现了用户自定义处理消息。

public class TestMain {
    public static void main(String[] args){
        startServer(8889);
    }
    private static void startServer(int port){
        ReplierDispatcher dispatcher=new ReplierDispatcher();
        dispatcher.addReplier(Object.class, new Replier<Object>() {

            @Override
            public Object reply(Object request) {
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return new String("测试通信"+request);
            }
        });
        Exchangers.bind(URL.valueOf("dubbo://localhost:"+port),dispatcher);
    }
}

6.2客户端启动并发送消息

如下所示为客户端的测试代码,客户端的使用比较简单,只要通过门面类进行连接操作并得到一个ExchangerClient的实例,然后通过ExchangerClient实例进行request方法,传入消息,ExchangerClient的request方法会返回一个Future实例,紧接着调用Future的get()方法,该方法会一直阻塞到结果返回。然后在控制台中打印出来。

public class TestMain2 {
    public static void main(String[] args){
        test(8889);
    }
    private static void test(int port){
        ExchangerClient client= Exchangers.connect(URL.valueOf("dubbo://localhost:"+port));
        System.out.println((String)client.request("first").get());
        client.close();
    }
}

6.3结果

在客户端发送消息5秒左右之后,打印如下信息,可以看到该信息即为用户自定义的处理结果,说明服务器、客户端、Future的整个请求流程正常执行。

测试通信first


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值