废话不多说,开始今天的主题。纵观这个程序,感觉它的最可贵之处,在于展示了,如何用nodejs实现长链接模式的刷新技术。 (这个程序不详细介绍,重点讲解这个功能) Client.js 首先看一段核心代码: 复制代码 代码如下: function longPoll (data) { //....此处省略**行 $.ajax({ cache: false , type: "GET" , url: "/recv" , dataType: "json" , data: { since: CONFIG.last_message_time, id: CONFIG.id } , error: function () { addMessage("", "long poll error. trying again...", new Date(), "error"); transmission_errors += 1; //don"t flood the servers on error, wait 10 seconds before retrying setTimeout(longPoll, 10*1000); } , success: function (data) { transmission_errors = 0; //if everything went well, begin another request immediately //the server will take a long time to respond //how long? well, it will wait until there is another message //and then it will return it to us and close the connection. //since the connection is closed when we get data, we longPoll again longPoll(data); } }); }
这是client.js中的一段代码,一看这段代码,大家应该立马想到两个字——“递归”。在longPoll方法中,再次调用longPoll方法,典型的递归调用。 根据这段代码的语义,可以看出,第一次加载时,会调用longPoll方法,异步向"/resv"获取值,如果成功了, 执行success的方法,立即再次调用longPoll方法。如果失败了,执行error函数,隔10秒中再次调用longPoll方法。当然,执行error方法有一定的次数限制,由变量transmission_errorsx控制。 大家可能会有一个疑问,这样一直递归循环获取数据,服务器会不会有很大的负担?在没有数据可获取的时候,也会一直这样循环吗?当然,答案时否定的!并且,nodejs利用自身的特点,很好的处理了这个问题。接着往下看: Server.js 现看server中如何回应上面client的调用,核心代码: 复制代码 代码如下: fu.get("/recv", function (req, res) { //对session的验证和更新...... channel.query(since, function (messages) { if (session) session.poke(); res.simpleJSON(200, { messages: messages, rss: mem.rss }); }); });
先不要管这个fu.get()是什么意思,它和本次教程无关。总之知道它能回应client的调用就行了。上面的代码,除了对session的一些操作之外,只是调用了channel的query方法。注意传递的参数: since,它纪录了一个时间; 匿名方法,它接受一个messages参数,两个动作:1 更新session时间,2 返回一个json,即把messages返回给客户端。 有人可能会有疑问:在这里直接返回messages不行吗,干嘛还得在一个channel中定义一个方法才操作?答案:如果是那样,就成了一个死循环,server和client每时每刻都进行着数据交互,即使没有信息可返回。 还是接着往下看吧! 看channel是怎么定义的: 复制代码 代码如下: var MESSAGE_BACKLOG = 200, SESSION_TIMEOUT = 60 * 1000; var channel = new function () { var messages = [], callbacks = []; this.appendMessage = function (nick, type, text) { var m = { nick: nick , type: type // "msg", "join", "part" , text: text , timestamp: (new Date()).getTime() }; switch (type) { case "msg": sys.puts("<" + nick + "> " + text); break; case "join": sys.puts(nick + " join"); break; case "part": sys.puts(nick + " part"); break; } messages.push( m ); while (callbacks.length > 0) { //shift() 方法用于把数组的第一个元素从其中删除,并返回第一个元素的值 callbacks.shift().callback([m]); } while (messages.length > MESSAGE_BACKLOG) messages.shift(); }; this.query = function (since, callback) { var matching = []; for (var i = 0; i < messages.length; i++) { var message = messages[i]; if (message.timestamp > since) matching.push(message) } if (matching.length != 0) { callback(matching); } else { callbacks.push({ timestamp: new Date(), callback: callback }); } }; // clear old callbacks // they can hang around for at most 30 seconds. setInterval(function () { var now = new Date(); while (callbacks.length > 0 && now - callbacks[0].timestamp > 30*1000) { callbacks.shift().callback([]); } }, 3000); };