AJAX 聊天室实现原理终极解析
闲来无事,做了一个AJAX聊天室,以前一直想做一个,因为我和几个朋友是Linux机子,尽管我们的机 子上都有apache服务器,但要发送一个信息却不是很容易,老是要借助客户端,有时候吧Linux下的qq和gtalk之类的聊天软件太麻烦,所以呢, 就写了一个聊天室。
先说一下我实现的这个聊天室的聊天模式:
    1,无须注册,登录之类,打开页面就可以聊天。
    2,为避免过量冗余信息,客户端只获取在一定时间以后发送的信息,比如10秒内。
    3,可以单对单聊天,仅限于一个对一个,如果想一对多同时聊天,那么就必须要注册登录才能解决。
这样就简化了一些聊天的模式了,如果想要实现例如qq,msn,gtalk之类的聊天模式,就必须要用到用户注册登录,这样一来,先有的很多ajax聊天程序都已经设计的很完美了,例如:
http://blog.jwchat.org/jwchat/
http://ajaxbber.sourceforge.net/index.php?page=home    
还有很多,不一一列举。
分析一下原理:
客 户端必须不断的刷新,向服务器获取信息和在线会员列表。我采用session验证,因为session够安全,但是在线会员是存入数据库的,因为如果在线 会员比较多,存入数据库,远比用原来的session直接以文件形式存储的要快。session存入数据库,有专门讲解的,为了简化,这里只用到 session_id 这个session_id其实在服务器端还是以文件形式存储的,先做两个表:
message( id , status, nick, to_uid, to_sid, to_ip, from_uid,from_sid,from_ip , message)
online(uid, sid, ip ,nick, message_id , lastupdate, tip , status , link_uid , link_sid, link_ip )
如果不实现单聊,表就可以简化为:
message( id , nick, from_uid,from_sid , from_ip , message) ,
online(uid, sid, ip ,nick, message_id , lastupdate, tip )
为了简化,分单独几道ajax请求和服务器通信:
1.获取信息,这个请求负责刷新获取信息,在线用户。    chat.php?ac=get
2.发送信息,这个请求负责向服务器发送信息。             chat.php?ac=send   
3.请求连接,这个地方负责实现单聊连接的。            chat.php?ac=linkto
4.处理连接,处理接收或拒绝单聊请求。            chat.php?ac=cut

先来分析第一个发送请求,获取消息和在线会员。
客户端向服务器发出请求后,这里我采用这样的提交形式:chat.php?ac=get
先判断用户是否存在?
session_start();
$sid = session_id();
$ip   = $_SERVER['REMOTE_ADDR'];
$dateline = time();
// 获取用户当前设置的昵称。
$nick=$_POST['nick']; // 如果你的ajax请求是get实现的,那么这里就改动一下。
$sql="SELECT * FROM online WHERE sid='$sid' AND ip='$ip' LIMIT 1 " ;
第一步就是要判断用户是否已经存在数据表中,没有,则表示是第一次登录,需要新建立一个用户。
这 里同时采用了 sid 和 ip 来验证用户是否已经在表中存在,用ip主要是为了防止session被劫持。但如果是内网劫持,因为网关出口ip通常是一个,所以这一招基本没太大用处, 可以不用判断ip,后来发现我这里其实是自找麻烦,因为有的公司的ip是不断变化的,我一个朋友的公司ip就是在两个直接随机切换,所以建议还是只判断 sid就可以了,毕竟劫持session_id的可能性还是比较小的。
如果用户不存在,那么新插入一条数据就可以了。
如果用户存在,先刷新用户的最后更新时间,
$sql="UPDATE online SET nick='$nick',dateline='$dateline' WHERE sid='$sid' AND ip='$ip' LIMIT 1 ";
然后删除已经离线的用户:
$interval = 10 ; // 十秒没用刷新的话,就认为其已经离线了。
$sql="DELETE FROM online WHERE dateline < $dateline - $interval ";
获取在线会员
$sql="SELECT * FROM online WHERE 1 ";
获取用户自己:
$sql="SELECT * FROM online WHERE sid='$sid' ";
存入变量$user中。
获取信息。
$message_id=$user['message_id']  ?  $user['message_id']  : 0;
 // 这个$user变量就是我们前面获取的当前用户。
$sql="SELECT * FROM message WHERE dateline > $dateline-$interval AND id > $message_id AND from_sid != '$sid' AND status=1 ORDER BY dateline ASC";
解释一下,这里只获取当前时间-10以后的信息,那么以前的信息就不会获取了,也就是说不会像qq那样,即使你离线了,别人发给你的信息,在你下次登录后还能获取到。因为我们没用会员注册,所有的信息都是无定向的。
这里用 status=1表示信息是群聊的, status=0表示信息是单聊的。所以如果只想实现群聊,不想实现单聊的,就不用判断状态了。
在 获取信息的同时需要更新用户的message_id,这个标志用来表示当前用户已经获取到哪条信息的位置了,如果没用这个标志,那么,每次刷新,用户上次 获取过的信息,如果在10秒内,这次还会获取到,假设有人连续发送信息,那么客户端会获取到大量重复信息,当然,客户端可以用一个隐藏的文本框来存储当前 用户已经获取到哪条信息了,这个方法既笨拙,又不实用,而且我好像见到有人就是这么做的。

while($row=mysql_fetch_array(mysql_query($sql)) ){
    $message_id = $row['id'] > $message_id  ? $row['id'] : $message_id  ;
    // 不论您是否采用这样的循环方式获取消息,您都应该把message表的取到的最大id记录下来。
}
下面获取单聊的信息。因为单聊的窗口通常会和群聊分开的。
$sql="SELECT * FROM message WHERE dateline > $dateline-$interval AND id > $message_id AND from_sid != '$sid' AND status=0 AND to_sid='$sid' order by dateline asc";
好下面在获取完单聊消息后,仍然需要执行:
$message_id = $row['id'] > $message_id  ? $row['id'] : $message_id  ; 
以获取最大的消息id。

当然了,如果您觉得麻烦,完全可以这样:
$maxId_sql="SELECT max(id) FROM message";
因为我们每次取完消息后,必然会把当前所有的可用信息取完,只是这样做增加了一次数据库查询。
下面,更新用户表的 message_id;
$sql="UPDATE online SET message_id='$message_id'  WHERE sid='$sid' AND ip='$ip' LIMIT 1";
好了,这样下次再取信息的时候,就会由这个message_id向后开始取,只取 id 比message_id大的信息。

吐出的最终数据最好是JSON格式的,这样也好减少流量,方便处理文本。
您总不希望别人发送给您信息的时候,发给你这样一个:<script> location.href="g.cn" ;</script>
这样你的页面啪的一声就转到google的主页了!
所以在前台处理的时候需要把将要提交的信息进行转义:
str.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/'/g,''').replace(/"/g,'&quot;');
在php端处理的时候就比较轻松了:htmlspecialchars( $_POST['msg'] );
好了,信息算是处理完成了,然后只要吐出就可以了:
header('Content-Type:text/html ; charset=UTF-8'); // 设定你的编码。
按照某些w3c mime标准来说,json吐出的格式应该是:application/json;
当然不推荐这么做,只需吐出 text,或html就可以了,这样做是方便调试。
我们在后台把所有取出的信息组合成一个大的数组:
$echo_json=array();
$echo_json['msg']=$msg ; // 取出的消息
$echo_json['online']=$online; // 取出的用户
echo json_encode( $echo_json );
当然,为了节约流量,最好把吐出的字母变量缩短,我就是这么干的。一个字母足够了。

2。发送信息。
    $msg_status =   1;  // 信息状态,1表示群,0表示私聊
    $toip        =   $user['link_ip'];      // 发送的目标对象信息直接由用户的表里取,如果没有单聊,省去。
    $touid         =   $user['link_uid'];
    $tosid          =   $user['link_sid'];
    $nick           =   htmlspecialchars($_POST['nick']);    // 这个是用户发送消息时的昵称。
    $message        =   htmlspecialchars($_POST['message']); // 消息体。
    $sql="INSERT INTO message(status,from_uid,from_sid,from_ip,to_uid,to_sid,to_ip,nick,dateline,message) VALUES('$msg_status','$uid','$sid','$ip','$touid','$tosid','$toip','$nick','$dateline','$message')";
    插入消息体,如果有单聊的话,需要加上判断:
    $sql="SELECT * FROM online WHERE sid='$link_sid' AND ip='$link_ip' LIMIT 1";
    而如果不存在    $user2,单聊的对象,那么,返回失败消息,提示用户发送消息已失败,对象已断开。
    好,吐出消息:
    这里可用简化一下处理结果,如果发送消息成功了,那么什么也不返回,如果失败了返回0.
    这样做为了省流量。因为毕竟发送消息成功的时候比较多,失败比较少。在客户端用js取出返回的数据
    判断是否为空,为空,则,发送成功,不为空,则发送失败。
    echo $result;

3。单聊请求,如果不提供单聊,3,4两条可用略过了。
这里采用的是,向服务器发送连接请求,然后由对方选择是否接纳。
当然如果不想让用户自己选择连接某个对象,而是由服务器自动配对,这就是当今很流行的路过聊天方式。
其实原理非常简单,例如 luguode.com ,等等,这类聊天非常的多。
简要说明一下由服务器自动配对的做法:
第一步:获取哪些用户仍是单身:
先说明一下状态代码表示:
online表中的status 字段:为0表示用户单身,为3表示用户已经配对。1,2留着有其他用处。
$sql="SELECT * FROM online WHERE sid !='$sid' AND status=0 ";
假如我们取出一个数组:$single_onlines;
下面我们取出一个随机的用户:
srand((double)microtime()*1000000);
// 初始化随机数种子,php 4以后版本据说已不在需要,
// 但很多时候,我还是需要这句才能得到正确的随机数,shit!
$target_index    =    rand( 0 , count($single_onlines)-1) ; //随机下标
$target_sid    =     $single_onlines[$target_index]['sid'] ;
$target_ip        =      $single_onlines[$target_index]['ip'] ;
// 获取到目标的sid 了,下面同时更新用户和我!
$sql="UPDATE online SET status='3', link_sid='$target_sid', link_ip='$target_ip' WHERE sid='$sid' AND ip='$ip' LIMIT 1 ";// 更新我的状态

$sql="UPDATE online SET status='3', link_sid='$sid', link_ip='$ip' WHERE sid='$target_sid' AND ip='$target_ip' LIMIT 1 "; // 更新目标状态

当然了,在更新前,需要做一些简要的判断,例如,我自己是不是已经是3了阿,是表示我已经是和别人建立单聊了,那么就返回一个错误了。

说到这类不知道您发现问题了没?就是,当我没有请求和别人单聊的时候,也可能会被别人啪的连上了。
简单解释一下:例如现在有三个人 A,B,C,我是A,这三个人都是单身,当我向服务器发出请求连接的时候,
这个时候,服务器随机找出了B,而B这个时候并没有向服务器发出连接请求,也会被啪连上了。
所以我们就可以加一个状态判断。例如:status=1表示用户正在向服务器发出连接请求。
0,表示用户什么也没有做,也不想和别人单聊。
而这个时候,就从那些状态为1的用户里面找出一些,随机连接。
如果没有单身了,那么先把用户状态更新为1,返回没有找到的提示。

感觉很罗嗦。所以我就自己设计了一个连接--服务器处理--客户端处理--的这样一个模式。
请求地址:chat.php?ac=linkto
post来的变量:
$linkto=$_POST['linkto'];// 表示要和哪个uid的用户建立连接
如果用户是单身状态,那么连接请求可用,用户的在线列表左边将会出现一个--连接的按钮
这个时候,uid就派上用场了,为什么不用session_id?因为你不可能把用户的session_id都发送给
客户端吧?只有发送uid了,
这个uid可用是随机字母,也可用是随机数字,我这里采用的是6为随机数字。
这里要稍微改动一下,用户第一次访问的情况。
    srand((double)microtime()*1000000);
    $uid=rand(100000,999999);
    $sql="SELECT uid FROM online WHERE uid='$uid'";
    while( mysql_fetch_array( mysql_query($sql) )){
        $uid=rand(100000,999999);
    }// 直到产生一个唯一的uid为止.
    循环来验证当前产生的uid是否已经在表中存在了?如果存在,那么继续产生,如果不存在,那就用这个了。
    定义一下状态:
    0:用户单身
    1:用户正在发出连接请求。
    2:用户正在被连接请求。
    3:用户已经配对。
当连接请求发出的时候,需要判断这样几种情况?
    自己状态是:
    status=1,
       我已经在请求了,这个时候可用根据需要是否允许用户进行多次请求。
       一般是允许的。因为如果不允许的话,你发送过去,对方没有即使处理,那么就要一直挂着等了。这样显然不好。当然也可以这么做了。
    status=2:
       我在被请求,提示错误
    status=3:已经成对,提示错误。
   
    第二步,查询对象是否存在,或已经掉线?
        $sql=SELECT * FROM online WHERE uid='$linkto' LIMIT 1;
        $user2=mysql_fetch_array(mysql_query($sql));
        如果不存在$user2,证明已经掉线,返回错误信息。
        否则进入下一步:
    目标状态:
    status=0或1
        更新目标用户,返回发送请求成功。
        $sql="UPDATE online SET link_uid='$uid2' AND link_sid='$sid2' AND link_ip='$ip2' ";
    status=2,
        目标已经有人请求,返回请求错误信息。
    status=3,目标已经成对,返回错误信息
   
4.处理连接:
    每次刷新信息时候,返回用户自己的信息:
    在客户端,用js检测用户状态信息是否为2?
    如果为2,则弹出对话框,提示用户是否接收或拒绝连接请求。
    接收的时候,仍需要注意几种情况:
    自己的状态判断,和目标的状态判断。

最后需要增加一个刷新用户状态,
if($user['status']==3){ // 刷新单点连接
    $linksid=$user['linksid'];
    $linkip=$user['linkip'];
    $sql="SELECT uid FROM online WHERE sid='$linksid' AND ip='$linkip' LIMIT 1";
    if(!mysql_fetch_array( mysql_query($sql) ) ){
       $sql="UPDATE online SET status='0',info='$info',linkuid='',linksid='',linkip='' WHERE sid='$sid' AND ip='$ip' LIMIT 1";
       //更新用户状态,使其重新变为0。
    }
}

然后还需要提供一个手动断开连接的处理,这就比较简单啦,直接把自己的状态,和对方状态更新为0,同时清楚掉link_uid....之类的

下面说说前台,基于JQuery的!
前台比较简单。
Chat={};
Chat.get=function(){
    $.post('chat.php?ac=get',{'nick':$('#nick').val()},function(data){
          // 这里把取到数据 data对象里面的消息追加到聊天窗口。
          $.each(data.msg,function(i,msg){
              //比较简单就不多写了。
          });
          //scroll('im'); 这里加一个滚动效果,可用把窗口自动滚动到最底部。
      }
        setTimeout('Chat.get()',3000);
    },'json');
}

function scroll(id){
         var scrollTop=document.getElementById(id).scrollHeight - document.getElementById(id).clientHeight >= 0 ? document.getElementById(id).scrollHeight - document.getElementById(id).clientHeight : 0;
         document.getElementById(id).scrollTop=scrollTop;
 }

其他的请求就略过了,因为比较简单。原理都差不多了。
好了,到这里就基本讲解完了一个ajax聊天室的原理,如果是注册聊天,实行起来会比这个容易些!

AJAX聊天室实现原理 JQuery+PHP 【转】相关推荐

  1. 基于jquery的ajax聊天室系统,基于jQuery的Ajax聊天室应用毕业设计(含外文翻译)...

    基于jQuery的Ajax聊天室应用毕业设计(含外文翻译) 毕业设计(论文) I 基于基于 jQuery 的的 Ajax 聊天室应用聊天室应用 摘摘 要要 随着网络的逐渐普及,以及网络技术的不断发展, ...

  2. ajax聊天室_Ajax聊天

    ajax聊天室 当出现Web 2.0术语时,开发人员会谈论很多社区. 而且,无论您认为是炒作还是不炒作,吸引客户或读者就当前话题或所售产品进行即时对话的想法都非常引人注目. 但是你怎么到达那里? 您能 ...

  3. asp.net ajax聊天室,ASP.NET MVC4异步聊天室的示例代码

    本文介绍了ASP.NET MVC4异步聊天室的示例代码,分享给大家,具体如下: 类图: Domain层 IChatRoom.cs using System; using System.Collecti ...

  4. AJAX聊天室无刷新技术方案

    聊天室在网上很常见,它给人们提供了在线聊天的机会.聊天室在功能上类似论坛,但是论坛上的信息可以保存较长的时间,而聊天室主要用于在线聊天,所以对存储信息方面的要求不高,另外还要经常删除一些信息,避免数据 ...

  5. 基于socket的聊天室实现原理

    基于socket的聊天室,目前还比较少见,国内比较知名的有网易和碧海银沙聊天室.这种聊天室的特点很明显,不象CGI聊天室那样不管有没有人发言,都会定时刷新.而是当有人发言时,屏幕上才会出现新聊天内容, ...

  6. php聊天室的设计实现,基于PHP的Ajax聊天室系统的设计与实现

    第7卷 第20期 2007年10月167121819(2007)2025396204 科 学 技 术 与 工 程 Science Technol ogy and Engineering Vol .7 ...

  7. jsp ajax聊天室,jsp+servlet实战酷炫博客+聊天系统

    项目介绍本项目使用jsp+servlet+mysql架构搭建可聊天的酷炫博客系统.界面非常好看,除了登录注册个人中心修改外,博客还添加背景音乐,可在线交友聊天,发表动态,相互评论等,喜欢的博文还能添加 ...

  8. 张晓飞ajax聊天室,张晓飞

    研究成果 期刊论文 1.Xiaofei Zhang, Xitong Guo, Kee-hung Lai, Yi Wu, 2019. How does Interactional Unfairness ...

  9. 张晓飞ajax聊天室,张晓飞-华中师范大学数学学院中文站

    论文涉及的代码及软件可以从以下网址获取:https://github.com/Zhangxf-ccnu 选择的代表作(#共同第一作者,*通讯作者) [10] Le Ou-Yang, Dehan Cai ...

  10. 张晓飞ajax聊天室,张晓飞欧阳乐做客信息逸站作专题报告

    (文|黄锋  图|汤桂烽)4月16日上午,华中师范大学张晓飞博士和深圳大学欧阳乐博士应学院章文教授邀请,在逸夫楼C603会议室分别作了题为"EnImpute: imputing dropou ...

最新文章

  1. Jmeter连接Oracle数据库
  2. c++,不能声明为虚函数的函数
  3. python中常见的流程结构-Python学习笔记5程序的控制结构
  4. Linux系统中的uptime命令
  5. 后台开发经典书籍--Redis深度历险:核心原理和应用实践
  6. MyBatis-Plus 乐观锁 防止超卖、逻辑删除、自动填充、Id自增
  7. ​Golang 并发编程指南
  8. 如何解决JAVA环境变量配好后,重启电脑又失效的问题
  9. 前端学习(2526):Vuex成果和展示
  10. 2020年数字营销与商业增长白皮书
  11. 如何在CentOS 7上使用HAproxy Loadbalancer设置Percona XtraDB集群(负载均衡)
  12. ref转发到DOM元素
  13. qt 读取gif一帧_译:Unreal渲染一帧详解(Unreal Frame Breakdown)
  14. python爬考研_用Python爬取了考研吧1000条帖子,原来他们都在讨论这些!
  15. ●BZOJ 2393 Cirno的完美算数教室
  16. ACCESS实例1——简易文档管理器
  17. 群晖-VideoStation-(TMDB刮削器)api申请
  18. 编程小白的第一本Python入门书学习笔记
  19. python中re.group()
  20. Dataframe基础操作巩固——美国大选献金项目

热门文章

  1. 技术:车牌识别摄像机的应用,无人值守洗车房解决方案
  2. 【读书笔记】两个天才 发挥孩子的创造力 4岁以上 蒲蒲兰绘本
  3. Windows批量装机平台搭建(二)之WDS配置
  4. 计算机技能鉴定高级,职业技能鉴定计算机(高级)试题.pdf
  5. 2022最新ES面试题整理(Elasticsearch面试指南系列)
  6. C语言中时间相关函数总结
  7. 专家看台:盛大架构师周爱民回顾职业历程,分享十项建议
  8. 不能设置一个python sdk_【君奉天|开发日记】Python SDK换唤醒词及设置服务配置
  9. 归纳法证明汉诺塔解析式思路
  10. 关于5G数据中心:数据中心在5G无线网络系统中的作用