AJAX聊天室实现原理 JQuery+PHP 【转】

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,'&#039;').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聊天室的原理,如果是注册聊天,实行起来会比这个容易些!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值