WebIM开发之通讯模式介绍

我打算写一个系列文章,介绍webim的方方面面,今天开始第一篇。

我之前发布了一个webim,那个im设计了前端UI、交互,后端程序和通讯只是随便写了一下。作为一个交互很多的web应用,良好的后端设计可以减少数据库访问、减轻服务器负载;良好的通讯模式更是可以较少服务器连接数、节省流量。这篇文章主要讲web即时通讯中常用的技术。

HTTP是无连接的,HTTP通讯过程基本就是:客户端发送请求给服务器,服务器接收请求给出响应信息,客户端接收响应信息显示在用户的显示器上, 客户端断开连接。由此可知,要实现即时聊天中的”即时”,我们有两个办法:服务器抓住连接不断开和客户端不断的向服务器发起请求实现伪即时。当然用 Flash XMLSocket可以实现真正的即时通讯,但这样已经不是使用HTTP协议了,HTTP天生的优势(无需另外开端口、自动穿越防火墙)也就无法体现。

1.短轮询(polling):核心思想是客户端定时去服务器取消息。为了实现即时效果,轮询的间隔必须设计得足够短,另外为了操作的流畅,需要使 用Ajax来发送请求。本人的QGYWebIM就是采用的此方案。这种方案的优点是:后端程序编写比较容易,发送完响应信息马上断开连接,不会占用太多服 务器资源。缺点是一般情况下,频繁的请求中有大半是无用,这些冗余请求无形中浪费了带宽和服务器资源。我们可以通过判断用户的活跃程度来决策请求服务器的 间隔,我在51的一个帖子提到过这种方法,但是间隔一旦长了,消息的传送就有延时,违背了即时聊天的初衷了。

2.长轮询(long-polling):基本原理是客户端向服务器发送请求,服务器接到请求后hold住连接,直到有新消息才返回响应信息并关闭 连接,连接被断开期间用户的新信息会被服务器缓存起来。客户端处理完响应信息后再向服务器发送新的请求。这种做法的优势是如果用户一直没新消息,客户端不 会频繁的轮询去服务器取消息,节省了流量,但是服务器维持长连接是很消耗资源的。具体实现起来,前端这边基本不需要什么改动,依然是用Ajax轮询取信 息,后端需要在没有新消息时处理一下。

3.长连接(streaming):其实很早以前就有人使用这种技术来实现聊天室的通讯。以前在页面中嵌入一个iframe,iframe里放一个 使用长连接页面,服务器有新消息就会及时的在iframe里反映出来,再依靠客户端的脚本解析出来就OK了。这样做一个比较严重的问题是:使用 iframe请求长连接时,无论是IE还是firefox都会认为页面没有加载完而显示进度条,很难看。不过这个问题是可以解决的。firefox支持了Streaming Ajax,在readyState为3的时候就能接受数据,所以问题不大;IE则只能在readyState为4,即连接断开时才能得到返回值。但是伟大的Google工程师使用了一个hack成功的解决了这个问题:使用一个被称为“htmlfile”的ActiveX,把iframe放在这个ActiveX里就OK了。

// we were served from child.example.com but
// have already set document.domain to example.com
var currentDomain = "http://exmaple.com/";
var dataStreamUrl = currentDomain+"path/to/server.cgi";
var transferDoc = new ActiveXObject("htmlfile"); // !?!
// make sure it’s really scriptable
transferDoc.open();
transferDoc.write("");
transferDoc.write("<script type="text/javascript"><!–
document.domain=’
"+currentDomain+"‘;
// –></script>
");
transferDoc.write("");
transferDoc.close();
// set the iframe up to call the server for data
var ifrDiv = transferDoc.createElement("div");
transferDoc.appendChild(ifrDiv);
// start communicating
ifrDiv.innerHTML = "";

无疑,使用长连接对于用户来说是最好的方案,用户体验最好(消息能及时的到达)、占用用户带宽最少(不会发送无用的请求),但是会增加服务器的开销;长轮询是折中方案,Facebook IM 就是采用这种方案,不过做了一点改动:客户端发起的每个连接服务器都hold10S,这10S中新消息会源源不断的返回给客户端,10s后连接关闭,客户 端发起下一个连接。这样做是因为Facebook的用户会不断的打开、关闭新页面,如果每个页面都建立一个永久的长连接,会阻塞浏览器其他请求,服务器也 会吃不消的;短轮询因为实现起来简单,适用于小型应用。

 

转自: http://www.qgy18.com/2008/08/webim-design-transport/

Firefox下回退时form表单重新提交的问题

   曾经写过一个javascript自动常见form,并提交到自动创建的iframe的一个应用。由于提交之后跳转到另外一个页面,然后回退,会返现form的提交请求又自动重发了一次,因为是提交到一个隐形的iframe中的,我就在提交之后把iframe给删除了,但是回退后还会重新发送提交请求,我没有办法,知道有一天我在走路的时候,突然想到问题可能出现在为删除的form上,于是,我测试了一个,提交form之后就删除form,然后回退就没有该问题了,困扰了我几个月的问题终于真相大白、水落石出了。

关于浏览器的访问限制

1.  跨域访问是浏览器非常明显的访问限制
2. 另外除了根据域名的访问限制外,还有协议间的限制,如:http 和 https即使是同域也不能相互访问;

我原来以为跨域限制应该不限制http和https之间的访问,知道做的时候才发现是有这方面的限制的。做浏览器这方面开发是需要更多地了解浏览器的特性的。

PHP 中的内存限制


   今天在做图片上传部分的工作,上传后的图片需要做处理,a机器上总报PHP内存耗尽的错误,检查了一下php.ini里面memory_limit 设置为16M,应该够用了吧,才处理2M的图片;后来检查了一下处理图片需要很大内存的,如果图片尺寸很大的话,即使图片占用空间不大,处理起来也很费内存的;2M的图片需要50多M的内存。
   但是奇怪的是,在b机器上处理图片就没有问题,而b机器上php.ini 的设置为:
   memory_limit = 8M ;
   这就奇怪了,然后使用 php -r "echo ini_get(‘memory_limit’);"  , 在 a机器上显示结果为: 16M ,在b机器上没有显示;
   于是,怀疑b机器上的
memory_limit没有生效;问了一下百度,才知道要是memory_limit生效,则PHP在编译的时候需要 –enable-memory-limit ; 于是查了一下a机器和b机器上的编译选项,都没有明确指定 –enable-memory-limit ;
   在用php -v 查看一下,
   a机器版本php 5.2.5
   b机器版本php 5.1.5
   大概是默认的编译选项不同吧! 看了一下PHP 5.2.5 的NEWS ,果然PHP 5.2.5默认是开启的,而PHP 5.1.5 默认是不开启的。

启发
   我们在给PHP添加内存限制是只是在php.ini中添加了 memory_limit 的设置,却没有验证是否达到了效果;
   这就教育我们做事要“善始善终”, 不能只是做完了就算完了,一定要想办法证明自己做的没有问题,否则事情就没有做完。

mc_watch

本文是一个查看mc的PHP脚本文件,用法简单。

 

javascript 中的类属性与实例属性

下面的代码实现了javascript中类属性与实例属性:

function myObject() {
    var 
instance_data 100;
    
this.getInstanceData = function() {
        return 
instance_data;
    }
     
this.setInstanceData = function(v) {
        
instance_data v;
     }
}

// 使用一个匿名函数去修改构造器的原型 myObject.prototype, 以访问该匿名函数中的upvalue
void function() {
    var 
class_data 10;
    
this.getClassData = function() {
        return 
class_data;
    }
    
this.setClassData = function(v) {
        
class_data v;
    }
}.
call(myObject.prototype);

// 创建对象
//
var obj1 = new myObject();
var 
obj2 = new myObject();

obj1.setInstanceData(10);
alert(obj2.getInstanceData()); // 输出100

obj1.setClassData(200);
alert(obj2.getClassData()); // 输出200

Javascript 中构造单实例

下面几段代码,比较耐人寻味,里面体现了一些javascript的运行本质的东西,需要慢慢理解:

function aFunc() {
    function 
myfunc() {
        
//...
    
}
    return 
myfunc;
}
var 
f1 aFunc();
var 
f2 aFunc();
alert(f1 === f2); //false

function aFunc2() {
    var 
myfunc = function() {
        
//...
    
}
    return 
myfunc;
}
var 
f1 aFunc2();
var 
f2 aFunc2();
alert(f1 === f2); // false

function aFunc3() {
    function 
myfunc() {
        
//...
    
}
    return function () {
        return 
myfunc;
    }
}

var f1 aFunc3();
var 
f2 aFunc3();
alert(f1 === f2); // false

var aFunc4 = function () {
    function 
myfunc() {
        
//...
    
}
    return function () {
        return 
myfunc;
    }
}();
var 
f1 aFunc4();
var 
f2 aFunc4();
alert(f1 === f2); // true