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.htmlexample1.htmlexample1.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提供了远比协议模式强大的网络应用编程能力,而又完全不用关心任何网络开发细节。


上一页 下一页