2.4 代码分析

分析过程参照chatserver服务器项目代码,以及demo/chatroom/javascript/chatroom.js 客户端比对进行。相应的项目描述xml存在于demo/chatroom/xmls/ 目录下。

下面的描述中,聊天特指群聊,区别于私聊。


  • 2.4.1 View部分

    为了支持脚本系统,下面这些View,都没有定义明确的Control。而是通过Message方式实现,直接向特定的View发送自定义的字符串命令实现控制功能。

    • TemporaryView

      chatroom.view.xml:


      <view name="ChatRoom" lifecycle="temporary">
      	<variable name="info" type="ViewChatRoomInfo" />
      	<subscribe name="names" ref="UserInfo.name" />
      	<variable name="lastmessage" type="ChatMessage" />
      </view>
      

      • <variable name="lastmessage" type="ChatMessage" />	
        

        TemporaryView ChatRoom 描述了一个聊天室的核心功能,一个临时View对应一个具体的聊天室,聊天参与者作为TemporaryView成员加入,任何对variable的修改自动同步到所有成员客户端,所以,聊天的实质就是用聊天室成员发送的信息更新lastmessage。这里lastmessage的ChatMessage定义为:


        <bean name="ChatMessage">
        	<variable name="msg" type="string" />
        	<variable name="user" type="long" />
        </bean>
        

        其中msg为聊天信息,user为发送者id。

        T用户向当前的ChatRoom发送聊天类Message,这里getMessageTo()返回-1

        chatroom.js:


        function sendMessage() {
        	var sendmsg = document.getElementById('sendmsg');
        	var msg = sendmsg.value.trim();
        	if (msg.length > 0)
        		context.send(currentChatView, "message=" + getMessageTo() + "," + msg);
        	sendmsg.value = "";
        }
        

        服务器ChatRoom收到Message进行处理

        ChatRoom.java:


        protected void onMessage(String message, long sessionid) {
        	int index = message.indexOf('=');
        	if (-1 == index)
        		return;
        	final String cmd = message.substring(0, index).trim();
        	final String params = message.substring(index + 1);
        	switch (cmd) {
        	case "message": {
        		.............................
        		if (-1L == toid) {
        			ChatRoom.this.setLastmessage(new ChatMessage(msg, sessionid));
        			RoomInfo.increment_publicMessages_mapkey(roomid);
        		} else {
        		.............................
        		}
        		return;
        	}
        	.............................
        }
        

        在这里,通过调用setLastmessage将聊天信息msg以及当前发送者sessionid设置到当前ChatRoom。接下来框架将这一改变自动同步到所有成员客户端。


        ctx.register(currentChatView, "lastmessage", function(e) {
        	if (e.type == 1 || e.type == 2)
        		showMessageToAll(e.value.user, e.value.msg);
        });
        

        lastmessage的监听器这里被注册了,一旦服务器lastmessage改变被同步到客户端,聊天消息就通过showMessageToAll方法得以表现。/p>

        这里需要注意一点,type被定义为"var type = [ 'NEW', 'REPLACE', 'TOUCH', 'DELETE' ];" (NEW表示该字段被新创建出来,从无到有;REPLACE表示字段的值与上一次同步的值不同,发生了改变;TOUCH表示字段的值与上一次相同,没有发生改变;DELETE表示服务器端相应的字段不存在了,删除了。)上面的代码中e.type匹配了REPLACE和TOUCH,并不匹配NEW。原因在于,用户加入TemporaryView的时候,当前TemporaryView的全部信息将被同步给用户,也就是说,如果匹配了NEW,之前的成员的最后一条聊天信息将被同步给新加入用户。这种处理,需要解决一个边界条件:

      • ChatRoom.java:


        private ChatRoom(TemporaryView.CreateParameter param) {
        	super(param);
        	ChatRoom.this.setLastmessage(new ChatMessage("", -1));
        }
        

        即,创建完成ChatRoom以后,lastmessage设置一条无用信息,供第一个用户丢弃。



      • <subscribe name="names" ref="UserInfo.name" />
        

        订阅是TemporaryView的一个非常重要的功能,可以订阅成员用户的SessionView上的字段信息,一旦某一成员的被订阅字段发生改变,则改变自动同步给所有成员。

        这里的订阅names作为一个示例实现,订阅了用户name,表现的效果是某一用户修改了自己的name,所有的聊天室成员,均能看到改变。

        用户向自己的SessionView UserInfo发送Message修改nickname

        chatroom.js:


        function doRename() {
        	var v100 = context[100];
        	var name = prompt("input name", v100.chatviews.UserInfo.name);
        	if (name != null && name != v100.chatviews.UserInfo.name)
        		context.send(v100.chatviews.UserInfo, "nickname=" + name);
        }
        

        服务器收到进行处理

        UserInfo.java:


        protected void onMessage(String message, long sessionid) {
        	final int index = message.indexOf('=');
        	if (-1 == index)
        		return;
        	checkUpdateHallInfos();
        	final String cmd = message.substring(0, index).trim();
        	final String param = message.substring(index + 1).trim();
        	switch (cmd) {
        	case "nickname":
        		Procedure.execute(ProcedureHelper.nameProcedure(
        			"onMessage nickname",
        			() -> {
        				xbean.UserInfo info = table.Userinfo.update(sessionid);
        				........................................
        				info.setNickname(param);
        				SysInfo.increment_nickNameChange();
        				return true;
        			}));
        		return;
        	........................................
        	}
        }
        

        这里的setNickname,修改了表userinfo的sessionid对应xbean,xbean 绑定在当前UserInfo的name字段上,而这个字段UserInfo.name又被ChatRoom这个临时View所订阅, 所以ChatRoom的所有成员用户都能看到nickname的改变。绑定的细节接下来SessionView介绍。

        chatroom.js:


        ctx.register(currentChatView, "names", function(e) {
        	updateName(e.sessionid, e.value.nickname);
        });
        

        ChatRoom的所有成员(包括改名用户自己)均会收到改名用户的id与新名字names,调用updateName修改UI表现。



      • <variable name="info" type="ViewChatRoomInfo" />
        

        聊天室本身的信息描述,主要功能是,用户进入聊天室的时候向客户端同步聊天室信息,远比lastmessage的使用简单,可以自己看代码。


上一页 下一页