此文章比较长,阅读需要花点时间!毕竟Pushlet 团队 花了 6年,自己也花了几个月,所以、、、、献给有心人,好东西拿出来给大家分享,可能此文章中存在些文字上的失误和技术上的盲区,欢迎大家批评指正,希望能给点意见和建议,如果有心人想看我具体的实现,甚至是IM源码请给我来信索取:[email protected] ,本人道行太浅导致有地方没解释清楚,还请谅解。谢谢!!
新写了一个IM具体实现:详细参照 http://pijunliang.iteye.com/blog/107447 源码和DEMO都附上
IM ( Instant messaging ),顾名思义 就是 即时消息 ,这个理念在当今互联网时代并不陌生,常用的QQ 和MSN就是最典型代表。传统QQ和MSN 都是采用C/S 结构的实现方式,现在讨论的话题是: 如何利用 B/S 结构实现一套轻量级的 IM聊天工具并协同应用系统工作,给应用系统提供接口,点对点收发消息(包括对脱机用户留言)、一对多收发消息、群聊功能、即时显示在线用户和脱机用户,可以拓展短信接口!支持多个数据库,并可以跟应用系统一起部署。
想在B/S结构上实现 即时 这个理念的确是有点困难。首先是性能;其次是技术实现方式;最后是稳定性。比较传统的做法:页面定时刷新 和 基于APPLET的SOCKET。这样做的局限性太大。
通过各种搜索工具寻找答案。例如:www.google.com www.open-open.com http://sourceforge.net/ 输入关键字:chat 。 出来的结果让人眼花缭乱,各种实现方式都有。但是最常见的有:JabberHTTPBind-1.0 和 wildfire 。这些都是按照特定的协议,后台有着庞大的实现,甚至有的后台另外起着一个服务。 用着各种AJAX 的实现去连接后台这个服务,例如:jwchat 和OpenCHAT-0.43 。如果说是单纯的聊天功能,直接用就可以。但是因为现在的需要 是 将IM的提醒功能当作一个接口提供给应用系统。让IM给 应用系统 提供接口并一起协同工作。采用上面的做法,显得有点画蛇添足了,而且要改造后台实现,这样的做法似乎代价太高。
偶然得知,PUSHLET 可以将 服务器端 的信息通过IO 的方式推送到 客户端,利用传说中的AJAX的 XmlHttpRequest 方式实现。推送时间接近500毫秒,而且很轻量。这个不正是我们所找的吗?<o:p></o:p>
都知道开源的技术文档大部分是需要收费的,所以现在对Pushlet的学习都需要去摸索。当深入到 开源Pushlet 实现的时候。发现Pushlet 团队开发了6年。幸运的是这次能站在了巨人的肩上,可是给我的时间才1个月。
在强大的压力和强大的挑战下,必须静下心来将Pushlet全是英文的源码 一行一行的跟踪调试,将其自带的例子进行原理分析。收获很大。Pushlet代码风格和代码质量 ,让人看着就舒服。(有点类似底层JDK的编码)
IM完全仿照QQ的功能实现。开发过程中,有一个问题比较郁闷,现象:类似QQ主界面,点击人员聊天,然后迅速关闭这个聊天的窗口,再点击其它窗口 或是 再选择人聊天 的时候屏幕就出现白屏,他们是在同一浏览器中,因为是用window.open()实现的,在同一个会话中怎么也不可以打开其它页面,浏览器也不往外面请求,也不Response,重新打开一个浏览器也是一样,第一次可以,第2次就不可以了。郁闷的是我换个浏览器FIREFOX 就没这个问题,只是IE存在这个现象。
最后解决方案:因为这里是用XmlHttpRequest 的异步的请求方式,Pushlet后台有个线程 在那里等待了5秒, 在请求等待中,客户端的浏览器已经关闭了,所以就不请求了。后来把这个时间调到500毫秒,问题就解决了, 说是程序的问题,那为什么其它浏览器支持呢?说不是程序的问题 ,那为什么IE浏览器不支持呢?
IM的功能介绍:
页面: 采用window.open()的方式,将菜单栏 隐藏 和 大小固定。
Javascript : 采用 Window.Event 的事件处理和分析一些请求!利用 XmlHttpRequest (AJAX)异步 的方式跟后台的Action 请求。
Pushlet : 用到一个JS(700多行代码) 和 一个开发包(20多个类)。实现机制是利用时间差,只是感觉不到它的PULL。(详见下面)
spring 2.0 : 利用 AOP 、 IOC、 hibernateTemplet 跟Hibernate 交互 和 用com.ecomm.util.struts.DelegatingRequestProcessor 跟Struts 交互。
hibernate <st1:chsdate isrocdate="False" w:st="on" year="1899" day="30" islunardate="False" month="12">3.2.5</st1:chsdate> 处理 DAO 和 持久层。
Struts <st1:chsdate isrocdate="False" w:st="on" year="1899" day="30" islunardate="False" month="12">1.2.9</st1:chsdate>: 处理所有请求,然后转交给Manager
Dtree: 构建用户树。按照人员的部门分类
2.1 弹出提醒的方式:弹出所有未查收消息列表
2.2 声音提醒的方式:有新消息就发出“你有新消息请查收”的声音
2.3 图层提醒的方式:在首页隐藏一个DIV,如果有新消息就显示有多少条消息
2.4 数字提醒的方式:有新消息就会在首页显示有多少条,没有就为0 ,是系统默认的提醒方式。显示在消息主界面
在聊天主界面中,默认就是单聊状态,选择部门下面人员,进行聊天实例,
加载顺序 :加入、监听、发送消息。
页面布局:分为2 FRAME ,左边是 文字输入框和显示框。右边是 接收人和发送人的个性签名,并且中间有个按扭可以追加接收人(群组右边不同:上面显示建群人,群描述,下面是群内的人员和个性签名)
发送消息支持快捷键:Ctrl+Enter
接口中有一个Send(T t)方法。只要用户提供发送人、接收人、类型和内容就能触发IM实例,达到提醒的能。
Pushlet 源码 有一个Session 和 一个 Subscriber ,Session 是用来管理监听的,Subscriber 是控制 监听下的多少人的。例如:首先进入页面有个onload() 加载 p_jion() 和 p_listen(id) 到 ajax-pushlet-client.js 调度,这个JS 会将请求映射到 Pushlet 的一个 Servlet 中。告诉Session 有个 id在请求获得监听,这时候Pushlet 容器会把处理这个请求 并 返回一个 listen_ack.xml 到 response..getOutputStream() 中,这个时候就可以发送消息。如果对方在线,pushlet 容器 就会通过Response 推送到对方,并保存记录,如果对方不在线就将提醒表状态设置为0。准备提醒对方。
原理:利用 ajax-pushlet-client.js 中 SetTimeOut(); 定时向Pushlet 后台Selvlet请求一次,Pushlet容器会根据这次刷新请求,分析此人在当前监听中是否有消息,如果有就将消息的XML通过 response..getOutputStream()方式 发送给这个人一份,并返回一个refresh-ack.xml,告诉ajax-pushlet-client.js 将在多长的时间后再次发送刷新的请求。
6.1 chat_group
聊天群组的信息,建群人,群内描述,群内人员,当前有多少人加入群内聊天实例,
群ID就是群监听
6.2 chat_listen
群监听,默认是单聊的方式
接收人和发送人 产生一个监听,并保存谁加入当前聊天实例中,并可以追加聊天接收人
6.3 chat_message
消息记录。跟提醒表 是一对多的关系
消息发送人,消息内容,发送时间,消息类型,消息类型下的子类型,URL,外键ID(可以是组ID,也可以是 单聊ID)
6.4 chat_remind
提醒表,跟消息记录表 是多对一的关系
接收人,消息记录的ID,和标记(是未读,以读,删除)
6.5 chat_userinfo
扩展用户信息表
个性签名,用户头像路径,四种提醒方式,状态。
<o:p> </o:p>
心得:利用开源产品最好利用其所有功能,要是需要改造开源实现,那将是一件很痛苦 和 很具有挑战性的工作
<o:p> 附带几个聊天界面,看看聊天时时效果:</o:p>
<o:p>首页,类似QQ/MSN 分为:部门人员 / 我的群组 / 系统设置</o:p>
<o:p>聊天的主窗口:这里用了Pushlet的时时功能,发送了对方立即能收到。</o:p>
<o:p>支持鼠标右击,修改和删除群组 </o:p>
<o:p>群组聊天的主界面:这里用了Pushlet的时时功能,发送了对方立即能收到。</o:p>
<o:p>消息提醒的配置功能:</o:p>
<o:p>历史记录查询的配置:</o:p>
<o:p></o:p>
消息历史记录列表,点击右边的人员 会出来 自己和他的聊天记录。
提供技术链接:
Pushlet :
www.matrix.org.cn/resource/article/<st1:chsdate isrocdate="False" w:st="on" year="2007" day="16" islunardate="False" month="1">2007-01-16</st1:chsdate>/bcc<st1:chmetcnv w:st="on" tcsc="0" unitname="C" sourcevalue="2" negative="False" numbertype="1" hasspace="False">2c</st1:chmetcnv>490-a502-11db-8440<st1:chmetcnv w:st="on" tcsc="0" unitname="C" sourcevalue="755941" negative="True" numbertype="1" hasspace="False">-755941c</st1:chmetcnv>7293d.html