私自将这一层命名为会话层有些不厚道,在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