IM即时聊天功能的实现方式(理论)

发布于 2023-06-10  210 次阅读


一、实现IM常见的两种技术方案

1>基于Ajax实现Comet推送技术

此方案同样有两种实现方式:polling和long polling,下面我们简单了解一下:

1.1>polling轮询

前端JS用定时器周期性向服务器发送http请求,模拟实时刷新。比如每秒请求一次来检测是否有新消息到达。

过程很简单,收到客户端请求后,服务端马上执行脚本查询,并且立即响应客户端。

客户端等待的时间很短,客户端唯一要做的事情就是定时向服务端发出查询请求。

1.2>long polling长轮询

原理和polling一样,只不过收到客户端请求后,服务端不一定立即响应客户端。

如果查询到没有新的消息,服务器不响应而是hold链接,定时查询消息状态。

客户端会持续等待服务响应,在链接持续的过程中,就变相实现了服务器的主动消息推送。

客户端也不再需要定时向服务发送请求了,而是直到服务端响应或者连接丢失之后,客户端接着马上重新请求。

Long Polling相较传统的Polling而言,最大的实惠在于:减少了请求次数。举个例子,假定一个用户每2小时内,有可能收到2条新消息。如果采用传通的Polling方式,每30秒发向服务端发送一次查询请求的话。则在这2小时内,服务器需要处理240(60*60*2/30)次请求,其中至少有238次请求是没有实际意义的。试想,如果是10000的并发量的话,这种浪费是很惊人的。相较而方,Long Polling没有那么浪费服务器资源来处理这些没有实际意义的请求。

2>基于WebSocket实现

WebSocket是类似Socket的TCP长连接的通讯模式, 连接建立后,后续数据都以帧序列传输,连接断开前不需要重新发起连接请求。

客户端发送和接受消息是在同一个持久连接上发起,实时性优势明显,在性能上处于完全领先位置。

但是WebSocket技术开发相对复杂,且长期维持链接对带宽资源的消耗巨大,开发成本较高。

二、选择适合自己的技术

就个人云服务器而言,使用WebSocket显然不是一个明智的做法,大量长链接会消耗巨量带宽。

我的2M带宽小水管显得心有余而力不足,或许在多人在线聊天室的场景中WebSocket会有更好的表现。

因为只需要长期维持一个固定的socket链接池,且用户的链接对象是明确的,统一指向聊天室即可。

假设有10个人在聊天,那么成本就是10链接---->1聊天室;而一对一单聊场景下,用户的随意性会难以控制链接数。

比如10链接---->10链接,即翻倍为20个链接,这样的不可控的风险和成本并不适合我们。

所以我们选择使用long polling的Comet方案深入研究。

三、Comet技术分析(long polling)

不得不说Ajax确实是一个好东西,由它的出现使得web端新技术不断产生,Comet就属于这么一个技术,有时叫做反向Ajax,有时叫做服务器"推"技术。先看一下维基的解释:

"Comet是一种用于web的推送技术,能使服务器实时地将更新的信息传送到客户端,而无须客户端发出请求,目前有两种实现方式,Ajax和iframe流。"

那我们用人话来解释一下:

凡是由服务端(即服务器,再具体点就是你目前访问的那个文件)实时的,不间断的往客户端(浏览器)发送数据的技术,都可以称之为Comet技术。

通俗点讲就是服务器不是一收到请求就直接吐数据,而是在那憋啊憋,一直憋到憋不住了,才告诉你执行结果。

至于憋多长时间,就看具体应用了,如果憋太久的话,服务器资源的占用也会是个问题。

现在我们就要通过这种方法来实现即时通信(其实是准实时)

先说一下原理:

1. 客户端发起一个ajax长链接查询,然后服务端就开始执行代码,主要是检查某个文件是否被更新,如果没有,睡一会(sleep),醒来接着检查

2.如果客户端又发起了一个查询链接(正常请求),服务端收到后,处理请求,处理完毕后更新某个特定文件的modify time

3.这时第一次ajax查询的后台代码还在执行,发现某个文件被更新,说明来了新请求,输出对应的结果

4.第一次ajax查询的callback被触发,更新页面,然后再发起一个新的ajax长链接

四、进阶知识:基于iframe的Comet技术

经常有人会遇到如下报错:

“Warning: Cannot modify header information - headers already sent by…..”

这是因为通过HTTP协议通信,数据包会包含俩个部分,一个是Header,一个是data。

一般来说,都是先Header部分,在Heaer部分指明了 Data部分的长度,然后使用\r\n\r\n来表示header部分结束,接下来是Data部分。

当我们有任何输出的时候,Header部分就发送了,这个时候,你再想header函数来改变一些Header部分的域信息,就会得到上面的提示信息。

所以通常会使用ob缓存(output_buffering)。让它来缓存服务器的输出,不要太早将Header部分发给客户端。

那么,如果不使用ob缓存,是不是就可以实现,每当服务器有输出,就立即发送给客户端呢?

//设置php.ini中output_buffering=0 或者使用ob_end_flush()关闭缓存
set_time_limit(0);
ob_end_flush();//关闭缓存
//还需要调用flush(), 来强制使得PHP将所有的程序输出发送给客户端
flush();

在代码中使用ob_start(), 就相当于在php.ini中使用output_buffering=on一样,使用服务器缓存。

在代码中使用ob_end_flush() 就相当于在php.ini中使用output_buffering = false一样,关闭服务器缓存.

我们就有可能使用Ticks来实现,一个无刷新,无ajax的聊天室:

页面中包含俩个iframe,一个是不断获取聊天室的聊天内容,一个包含用户发表聊天内容的form。

这样就实现了一个简单的基于iframe的Comet技术聊天室的框架。

五、其他

关于实时输出,还有一些其他的限制,比如在PHP5手册中讲到的:

个别web服务器程序,特别是Win32下的web服务器程序,在发送结果到浏览器之前,仍然会缓存脚本的输出,直到程序结束为止。

有些Apache的模块,比如mod_gzip,可能自己进行输出缓存,这将导致flush()函数产生的结果不会立即被发送到客户端浏览器。

甚至浏览器也会在显示之前,缓存接收到的内容。例如 Netscape 浏览器会在接受到换行或 html 标记的开头之前缓存内容,并且在接受到标记之前,不会显示出整个表格。

一些版本的 Microsoft Internet Explorer 只有当接受到的256(甚至更多)个字节以后才开始显示该页面,所以必须发送一些额外的空格来让这些浏览器显示页面内容。


君子慎独,不欺暗室。卑以自牧,含章可贞。