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,之前的成员的最后一条聊天信息将被同步给新加入用户。这种处理,需要解决一个边界条件:
-
<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的使用简单,可以自己看代码。
ChatRoom.java:
private ChatRoom(TemporaryView.CreateParameter param) { super(param); ChatRoom.this.setLastmessage(new ChatMessage("", -1)); }
即,创建完成ChatRoom以后,lastmessage设置一条无用信息,供第一个用户丢弃。
-
-