上一篇分析客戶端登陸的過程。當(dāng)用戶登陸成功后,聊天又是個(gè)什么過程呢?下面就來分析聊天時(shí),客戶端與服務(wù)器端的交互過程。
客戶端
我們先來看看下,聊天發(fā)送消息的過程。當(dāng)用戶在文本框內(nèi)輸入文字,并回車就可以發(fā)送消息了
1: $("#entry").keypress(function (e) {
2:var route = "chat.chatHandler.send";
3:var target = $("#usersList").val();
4:if (e.keyCode != 13 /* Return */) return;
5:var msg = $("#entry").attr("value").replace("\n", "");
6:if (!util.isBlank(msg)) {
7: pomelo.request(route, {//route = "chat.chatHandler.send"
8: rid: rid,
9: content: msg,
10: from: username,
11: target: target
12: }, function (data) {
13: $("#entry").attr("value", ""); // clear the entry field.
14:if (target != '*' && target != username) {
15: addMessage(username, target, msg);
16: $("#chatHistory").show();
17: }
18: });
19: }
20: });
#1:entry就是聊天文本框的id了,當(dāng)焦點(diǎn)在entry(就用id來代表控件了),每次按下鍵盤都會(huì)觸發(fā)keypress()方法,方法接受一個(gè)事件e
#2:route,決定客戶端向服務(wù)器端的哪個(gè)方法發(fā)送請(qǐng)求。
#3:target,entry的上方有個(gè)名為users的下拉框,target就是下拉框的值了,決定用戶向誰發(fā)送消息。
#4-#5:對(duì)輸入的字符判斷,如果不是回車就返回,如果是回車就將entry中的換行符替換成空字符串
#6:util是client.js定義的一個(gè)對(duì)象,里面包含了一些對(duì)字符的處理方法,其中isBlank()是判斷字符串是否是空字符串
#7:如果不是空字符串,就將這條消息發(fā)送給服務(wù)器,route就是#2所定義的服務(wù)器的處理方法 chat.chatHandelr.send
#8-#11:客戶端將用戶所在的channel,發(fā)送的消息了,用戶名以及發(fā)送消息的對(duì)象封裝成對(duì)象,發(fā)送給服務(wù)器
#12:定義回調(diào)函數(shù),處理服務(wù)器返回的結(jié)果對(duì)象data
#13:清空entry
#14:根據(jù)發(fā)送的對(duì)象判斷是否將發(fā)送的添加到聊天記錄中
#15:在聊天記錄(id=chatHistory)顯示
服務(wù)器端
接下來,在看服務(wù)器端收到客戶端發(fā)送的請(qǐng)求是怎么處理的。打開chatofpomelo\game-server\app\servers\chat\handler\chatHandler.js
找到handler.send
1: handler.send = function(msg, session, next) {
2:var rid = session.get('rid');
3:var username = session.uid.split('*')[0];
4:var channelService = this.app.get('channelService');
5:var param = {
6: route: 'onChat',
7: msg: msg.content,
8: from: username,
9: target: msg.target
10: };
11: channel = channelService.getChannel(rid, false);
12:
13://the target is all users
14:if(msg.target == '*') {
15: channel.pushMessage(param);
16: }
17://the target is specific user
18:else {
19:var tuid = msg.target + '*' + rid;
20:var tsid = channel.getMember(tuid)['sid'];
21: channelService.pushMessageByUids(param, [{
22: uid: tuid,
23: sid: tsid
24: }]);
25: }
26: next(null, {
27: route: msg.route
28: });
29: };
#1:解釋下參數(shù) msg就是客戶端發(fā)送的對(duì)象,session就是服務(wù)器端與當(dāng)前用戶的會(huì)話,next相當(dāng)于把結(jié)果發(fā)送給客戶端 PS:next的真正功能我也描述不清,還請(qǐng)各位指點(diǎn)。
#2-#3:從session中取得roomID(rid)和用戶名usernmae,chatofpomelo\game-server\app\servers\connector\handler\entryHandler.js的enter()方法有對(duì)于session的設(shè)置
#4:獲取ChannelService(管理Channel)
#5-#10:把發(fā)送的信息,用戶名和發(fā)送信息的對(duì)象以及route。這里的route:onChat由服務(wù)器端定義的,客戶端監(jiān)聽。每個(gè)客戶端都會(huì)監(jiān)聽onXXX事件,監(jiān)聽服務(wù)器發(fā)送的消息。這樣用戶發(fā)送的消息才能由服務(wù)器發(fā)送給其他用戶。
#11:根據(jù)用戶發(fā)送的rid,獲取對(duì)應(yīng)的channel
#14-#25:判斷發(fā)送對(duì)象,是廣播還是發(fā)送給特定用戶。
#26-28:將結(jié)果返回給客戶端
其實(shí),分析這么多代碼,前面我寫的很詳細(xì),后面就寫的簡略了。分析完后可以發(fā)現(xiàn),其實(shí)交互部分情況類似,只要弄懂了其中一部分,其余的也就好懂了。
PS:到此,這個(gè)demo的雛形就完成了,本來到這結(jié)束了……確實(shí),如果你的servers.json里只有一個(gè)chat服務(wù)器,那么一切流程都可正常運(yùn)行。但是,如果不只一個(gè)chat服務(wù)器,那肯定會(huì)遇到問題的,是不是信息發(fā)不出去,在看服務(wù)器端,報(bào)錯(cuò)了!!
是不是channel為undefined?看上面的第11行
1: channel = channelService.getChannel(rid, false);
很抱歉,channelService里并沒有channel,你一定很奇怪,當(dāng)用戶登錄時(shí),不是創(chuàng)建了channel了嗎,怎么會(huì)沒有呢?
為了驗(yàn)證,我把a(bǔ)dd和send時(shí)的app打印出來,對(duì)比

發(fā)現(xiàn)確實(shí)創(chuàng)建的channel沒有了,到底是怎么回事?
再比較,就會(huì)發(fā)現(xiàn)還有一個(gè)不同之處。

你會(huì)發(fā)現(xiàn),登陸和聊天時(shí)所在的服務(wù)器不一樣,難怪channel消失。
其實(shí),聊天和登陸一樣,用戶在發(fā)送消息時(shí),看上面客戶端代碼的第8行和第10行,會(huì)發(fā)現(xiàn)客戶端不只會(huì)發(fā)送消息,還會(huì)把用戶名username和rid同時(shí)發(fā)送給服務(wù)器端。服務(wù)器端會(huì)根據(jù)username和rid,實(shí)際上只有rid,判斷該用戶是位于哪臺(tái)chat-server上,這就是為什么game-server/app.js會(huì)有這幾行代碼
1: app.configure('production|development', function () {
2:// route configures
3: app.route('chat', routeUtil.chat);//routes的chat屬性對(duì)應(yīng)routeUtil.chat()方法
4: app.filter(pomelo.timeout());
5: });
#2:當(dāng)服務(wù)器類型是chat,就會(huì)把路由到routeUtil.chat方法。
然后在進(jìn)入該方法,看看是怎么判斷用戶屬于哪個(gè)路由器的。
1: exp.chat = function(session, msg, app, cb) {
2:
3: console.log("uid = " + session.uid + " rid = " + session.get("rid"));
4:var chatServers = app.getServersByType('chat');//根據(jù)類型 獲取服務(wù)器列表
5:
6:if(!chatServers || chatServers.length === 0) {//如果服務(wù)器列表不存在或?yàn)榭眨瑒t調(diào)用回調(diào)函數(shù)cb,將錯(cuò)誤傳給該回調(diào)
7: cb(new Error('can not find chat servers.'));
8:return;
9: }
10:
11:var res = dispatcher.dispatch(session.get('rid'), chatServers);//通過rid獲得具體的chat服務(wù)器
12: console.log("chat服務(wù)器:" + res.id);
13: cb(null, res.id);
14: };
#11:之前的代碼,相信大家都明白了。我們直接看11行代碼,是不是很眼熟?dispatcher好像見過!還記得之前客戶端連接gate服務(wù)器,然后由gate服務(wù)器分配connector服務(wù)器,再返回其host和clientPort嗎?不錯(cuò),這里同樣是調(diào)用該方法,只不過connectors改成了chatServers。這樣根據(jù)傳入的rid,就可判斷用戶當(dāng)初登陸的那個(gè)chat-server,這樣也就能找到對(duì)應(yīng)的channel
疑問:雖然解決了這一問題,但是還是不明白如果不添加對(duì)chat的路由,即沒有這行代碼:
1: app.route('chat', routeUtil.chat);
是怎么分配chat-server,是隨機(jī)分配,還是自增分配?
還有就是這句代碼是何時(shí)被調(diào)用的。
希望各位能夠指點(diǎn)一下。