5.4 服务器开发
-
5.4.5 服务器简单验证
这里先不介绍客户端实现,而用HTML5方法,通过chrome,直观体验服务器开发的效果。
-
实验5
private MySessionView(SessionView.CreateParameter param) { super(param); // bind here long sessionid = param.getSessionId(); bindMytable(sessionid); Procedure.execute(() -> { xbean.MyXbean xb = table.Mytable.insert(sessionid); if (xb != null) xb.setVar0(100); return true; }); MyGlobalView gview = MyGlobalView.getInstance(); gview.setVar0(new xbean.MyCbean(123)); }
运行服务器,刷新chrome页面,结果与实验3的输出完全一样。
函数最后加入一行gview.syncToClient(sessionid);
第一行可以看到,MyGlobalView的var0字段设置的123被发送到客户端了。使用GlobalView需要手工同步。
函数最后再加入两行gview.syncToClient(sessionid);
注意到第二行和第三行,状态都是REPLACE,明明数据没有改变,怎么不是TOUCH?
原因很简单: GlobalView维护数据的最新版本,通过手动刷新将最新版本的数据同步到一个或者多个客户端,并不会为个别客户端记录发送历史,所以GlobalView上永远不会通告TOUCH状态。
同样的,如果删除了字段数据,字段数据的状态就是不存在,既然不存在,syncToClient时也就不会同步到客户端,这就是说,GlobalView上永远不会通告DELETE状态。
为了客户端实现方便, 避免使用GlobalView字段的删除语义。
-
实验6
private MySessionView(SessionView.CreateParameter param) { super(param); // bind here long sessionid = param.getSessionId(); bindMytable(sessionid); Procedure.execute(() -> { xbean.MyXbean xb = table.Mytable.insert(sessionid); if (xb != null) xb.setVar0(100); return true; }); MyTemporaryView tview = MyTemporaryView.createInstance(); tview.getMembership().add(sessionid); }
MyTemporaryView.java
@Override protected void onAttached(long sessionid) { MySessionView.getInstance(sessionid).setVar0("onAttached"); }
运行服务器,刷新chrome页面。
第二行,TemporaryView的onopen被调用,于是:
第三行,SessionView上的var0被创建为"onAttached"。
注意到xml的描述。
<subscribe name="_var0" ref="MySessionView.var0" />
在这里,临时view订阅了MySessionView.var0,命名为_var0,于是有了:
第四行,TemporaryView的_var0同样设置为"onAttached"。
在这里把View对象的内容展开看,其实那个_var0,挂在了57344这个节点下,这个57344实际上就是当前的sessionid。TemporaryView的订阅效果在客户端表现就是:每个成员用户的被订阅信息挂在以成员用户sessionid为key节点下。
修改example.html,在v100.share.MyTemporaryView.onchange的方法之后添加一行。
ctx.send(v100.share.MyGlobalView, e.view.__i__);
在MyGlobalView.java中实现onMessage方法:
MyGlobalView.java
@Override protected void onMessage(String message, long sessionid) { MyTemporaryView.getInstance(sessionid, Integer.parseInt(message)).getMembership().remove(sessionid, (byte) 33); }
重新运行服务器,刷新chrome页面
在这里多出了第5行。应该这样解释:
第4行输出以后,TemporaryView的instanceid,被作为消息发送给GlobalView
GlobalView实现了onMessage方法,解析出instanceid获得该TemporaryView,将用户从Membership中移除,导致客户端结束了该TemporaryView。
这里可以看出,Control,Message具有全局意义,这就是本手册前面叙述的:
"Control尽管定义在View名字空间下,也不意味着这个Control的实现只能改变当前这个View。"
-
实验7
该实验详细解释TemporaryView的行为,行为比较复杂。
清理掉前面所有修改。
拷贝一份example.html到example1.html,example1.html中更换一个username
example1.html
private static Object lock = new Object(); private static MyTemporaryView tview; private MySessionView(SessionView.CreateParameter param) { super(param); // bind here long sessionid = param.getSessionId(); setVar0("hello " + sessionid); synchronized (lock) { if (tview == null) tview = MyTemporaryView.createInstance(); tview.getMembership().add(sessionid); } }
MyTemporaryView.java
@Override protected void onAttached(long sessionid) { MySessionView.getInstance(sessionid).setVar0("onAttached " + sessionid); }
运行服务器,刷新chrome页面。
另外启动一个chrome,F12开启调试,拖入example1.html。
图1, example.html调试窗口
图2, example1.html调试窗口
图1,前5行按前一个解释即可。
图2,
第一行,当前用户加入了前一个用户创建的TemporaryView,onopen后面[57344, 53248]可以看到现在有2个成员。
第二行,"_var0 onAttached 57344",这是57344成员被订阅的var0的最新信息
第三行,"_var0 hello 53428",这是当前用户被订阅的var0最新信息
第四行,当前用户的MySessionView.var0被设置为"hello 53428"
这里,出现一个疑问,第三行,第四行搞反了吧?其实,这就是本手册前面叙述的:
"客户端可以重现服务器设置同一View上的字段的顺序;不同View之间的时序不作保证。"
回到图1,
第六行,onattach 53428,这表示第二个成员53428加入进来了
第七行,53428成员被订阅的var0信息"hello 53428"被送过来了
回到图2,
第五行,MySessionView.var0在onAttached时被设置为"onAttached 53428"
第六行,被订阅的信息也送给自己
回到图1,
第八行,53428成员被订阅的信息"onAttached 53428"被送过来了。
比较,图1第七行,图2第八行展开的信息,完全一样,这里可以看见两个客户端的TemporaryView内容上完全同步的。
这个比较复杂,简单总结一下行为。用户加入Membership时:
1. 收集新用户的所有被订阅信息,作为attach消息广播给其它用户。
2. View信息(variable, bind),连同其它用户的所有被订阅信息被发送给新用户。
3. 框架以新用户sessionid为参数调用服务器端View实例的onAttached方法。
离开view的实验建议自己做了,否则截图太多,这个相对简单,有两种情况。
Membership.remove移除用户:
1. 发送close消息给离开用户。
2. 发送detach消息(连同Membership.remove的reason参数)给其它用户。
3. 框架以离开用户sessionid与reason为参数调用服务器端View实例的onDetached方法。
用户离线:
1. 发送detach消息(以-1为reason)给其它用户。
2. 框架以断线用户sessionid以及reason = -1为参数调用服务器端View实例的onDetached方法。
最后,临时View关闭时
1. 发送close消息给所有用户。
2. 框架遍历所有用户sessionid,连同reason = -1为参数逐一调用服务器端View实例的onDetached方法。
-
总结
上面几个实验,简单示例了View的使用。类比协议模式可以看出,协议模式的设计可以映射到View系统里面来:站在服务器端的角度看,相当于创建一个唯一SessionView,只使用variable节点,用协议名命名节点,类型为按照协议字段组织的Bean。所以,View提供了远比协议模式强大的网络应用编程能力,而又完全不用关心任何网络开发细节。
-