php+ajax长轮询实现web即时聊天

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介:

web im的实现方式有很多种:

1.普通轮询,原理通过js定时重复发送ajax请求服务端,获取数据后显示。

2.   长轮询,ajax请求服务端,服务端有数据会立即返回。服务端无数据时会一直等待,直到有数据了才立即返回。

3.socket长连接。



特征分析:

方法1:实现起来最容易,定时重复请求服务端会产生无意义的http连接,消耗服务端资源,实时性较差.

方法2:实现起来较容易,会减少无效的ajax请求产生的http连接,能即时返回数据,但服务端会一直挂着,会消耗一定的资源,处理并发能力不强,比较适合于中小型应用服务.(comet)

方法3:门槛较高,需了解socket通讯协议,是http实现长连接的最佳方式,也是真正意义上的server push技术.


Comet技术简介

  以即时通信为代表的web应用程序对数据的Low Latency(低延时)要求,传统的基于轮询的方式已经无法满足,而且也会带来不好的用户体验。于是一种基于http长连接的“服务器推”技术便被hack出来。这种技术被命名为Comet,这个术语由Dojo Toolkit 的项目主管Alex Russell在博文Comet: Low Latency Data for the Browser首次提出,并沿用下来。

其实,服务器推很早就存在了,在经典的client/server模型中有广泛使用,只是浏览器太懒了,并没有对这种技术提供很好的支持。但是Ajax的出现使这种技术在浏览器上实现成为可能, google的gmail和gtalk的整合首先使用了这种技术。随着一些关键问题的解决(比如 IE 的加载显示问题),很快这种技术得到了认可,目前已经有很多成熟的开源Comet框架。

以下是典型的Ajax和Comet数据传输方式的对比,区别简单明了。典型的Ajax通信方式也是http协议的经典使用方式,要想取得数据,必须首先发送请求。在Low Latency要求比较高的web应用中,只能增加服务器请求的频率。Comet则不同,客户端与服务器端保持一个长连接,只有客户端需要的数据更新时,服务器才主动将数据推送给客户端。


本文介绍第二种实现方法

案例名称:web即时聊天(ajax长轮询方式实现)

项目地址:https://github.com/zhangrenjie/web_im_ajax

功能介绍: 

  1. 对话双方都在线(浏览器没有关闭的情况下),对话即时推送.

  2. 支持离线发送消息.当离线方上线时,会自动接收离线消息.

  3. 采用确认机制确保数据推送成功.

  4. 采用超时退出机制,降低服务器资源浪费.



~~本项目只注重php服务端的实现机制和性能优化,前端界面粗糙请忽略.适合中级php程序员学习借鉴,欢迎各位指教交流~~


预览


wKiom1h1uG-zsJ0JAADVtwsyXBc807.png-wh_50



项目文件结构:

1
2
3
4
5
GetMessage.php
SendMessage.php
client.php
jquery. min .js
sql



准备工作:数据库

1
2
3
4
5
6
7
8
9
10
11
mysql>  desc  message;
+ -------------+------------------+------+-----+---------+----------------+
| Field       | Type             |  Null  Key  Default  | Extra          |
+ -------------+------------------+------+-----+---------+----------------+
| id          |  int (10)          |  NO    | PRI |  NULL     | auto_increment |
| reciver_uid |  int (10) unsigned |  NO    | MUL | 0       |                |
| sender_uid  |  int (10) unsigned |  NO    |     | 0       |                |
| content     |  varchar (1000)    |  NO    |     |         |                |
| create_time |  int (10) unsigned |  NO    |     | 0       |                |
| status      | tinyint(1)       |  NO    |     | 0       |                |
+ -------------+------------------+------+-----+---------+----------------+


客户端Client.php

实现功能:1.发送聊天信息,2即时获取并显示聊天内容


页面基本结构

1
2
3
4
5
6
7
8
9
< div  id = "message-list" >
<!--这里显示对话内容-->
</ div >
 
< div  id = "message-send" >
<!--这里填写对话内容,并发送-->
     < input  type = "textarea"  id = "message-box" />
     < input  type = "button"  id = "submit-message"  value = "发送消息" >
</ div >


功能1:发送内容操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<script type= "text/javascript" >
     //-------------发送消息---------
     $( function  () {
         var  reciver_uid = <?php echo $reciverUid;?>;
         var  sender_uid = <?php echo $senderUid;?>;
         $( '#submit-message' ).on( 'click' function  () {
             var  message_content = $( '#message-box' ).val();
             if  (message_content !=  '' ) {
                 $( this ).attr( 'disabled' 'disabled' );
                 var  send_url =  './SendMessage.php' ;
                 var  send_data = {
                     'message' : message_content,
                     'reciver_uid' : reciver_uid,
                     'sender_uid' : sender_uid,
                 };
                 $.post(send_url, send_data,  function  (response) {
                     if  (response.status == 1) {
                         $( '#message-box' ).val( '' );
                         $( '#submit-message' ).removeAttr( 'disabled' );
                         var  send_message_str =  '<li style="text-align: right;padding-right: 10px;">' ;
                         send_message_str +=  '您对'  + send_data.reciver_uid +  '说:'  + send_data.message;
                         send_message_str +=  '</li>' ;
                         $( '#message-list' ).append(send_message_str);
                     else  {
                         console.log( '发送失败!!' );
                     }
                 },  'json' );
 
             }
         });
     });
</script>



处理发生消息SendMessage.php

实现功能:保存发送信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
$link  = mysqli_connect(    
     '127.0.0.1' ,   /* The host to connect to 连接MySQL地址 */    
     'root' ,       /* The user to connect as 连接MySQL用户名 */    
     '' ,          /* The password to use 连接MySQL密码 */    
     'web_im' );     /* The default database to query 连接数据库名称*/    
if  (! $link ) {    
     printf( "Can't connect to MySQL Server. Errorcode: %s " , mysqli_connect_error());    
     exit ;    
}    
//只能用函数来判断是否连接成功    
if  (mysqli_connect_errno()) {    
     echo  mysqli_connect_error();    
}  
   
$senderUid  = (int) $_POST [ 'sender_uid' ];    
$reciverUid  = (int) $_POST [ 'reciver_uid' ];    
$message  str_replace ([ ' ' ',' ],  '' $_POST [ 'message' ]);    
$time  = time();
     
$sql  "insert into message values(NULL ,'{$reciverUid}','{$senderUid}','{$message}','{$time}','1')" ;    
$result  = mysqli_query( $link $sql );    
$insertId  = mysqli_insert_id( $link );    
if  ( $insertId ) {    
     $returnArr  = [ 'status'  => 1, 'info'  =>  $insertId ,];    
else  {    
     $returnArr  = [ 'status'  => 0, 'info'  =>  '' ,];    
}    
echo  json_encode( $returnArr );    
mysqli_close( $link );    
exit ();


再回到客户端Client.php的功能2

功能2:即时获取并显示聊天内容(注意:客户端使用了递归跟服务端自动应答)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<script type= "text/javascript" >
     var  reciver_uid = <?php echo $senderUid;?>;
     var  sender_uid = <?php echo $reciverUid;?>;
     var  url =  './GetMessage.php' ;
     $( function  () {
         get_message_reply(url, reciver_uid, sender_uid,  'get_message' '' );
     });
 
 
     //获取消息并应答
     //get_get_message_reply()
     //param request_type  请求类型 详解:
     //      get_message   获取信息
     //      comfrim_read  确认已经读取了信息
     function  get_message_reply(url, reciver_uid, sender_uid, request_type, send_data) {
         var  setting = {
             url: url,
             data: {
                 'request_type' : request_type,
                 'reciver_uid' : reciver_uid,
                 'sender_uid' : sender_uid,
                 'send_data' : send_data,
             },
             type:  'post' ,
             dataType:  'json' ,
             success:  function  (response) {
                 if  (response.status == 1) {
                     if  (response.response_type ==  'is_read' ) {
                         //将消息写入到消息盒子
                         var  messages = response.info;
                         var  message_str =  '' ;
                         var  id_arr =  new  Array();
                         for  ( var  in  messages) {
                             id_arr.push(messages[i][ 'id' ]);
                             message_str +=  '<li>'  + messages[i][ 'sender_uid' ] +  '在'  + messages[i][ 'send_time' ] +  '的时候对您说:'  + messages[i][ 'content' ] +  '</li>' ;
                         }
                         $( '#message-list' ).append(message_str);
                         //确认收到消息
                         get_message_reply(url, reciver_uid, sender_uid,  'comfrim_read' , id_arr);
 
                     else  if  (response.response_type ==  'is_connecting' ) {
                         //继续获取消息
                         get_message_reply(url, reciver_uid, sender_uid,  'get_message' '' );
                     }
                 }
             }
         };
         $.ajax(setting);
     }
</script>


NOTICE:下面是核心中的核心


服务端推送消息GetMessage.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
set_time_limit(0);    
$maxInvalidCount  = 30;    
$link  = mysqli_connect(    
     '127.0.0.1' ,   /* The host to connect to 连接MySQL地址 */    
     'root' ,       /* The user to connect as 连接MySQL用户名 */    
     '' ,          /* The password to use 连接MySQL密码 */    
     'web_im' );     /* The default database to query 连接数据库名称*/    
if  (! $link ) {    
     printf( "Can't connect to MySQL Server. Errorcode: %s " , mysqli_connect_error());    
     exit ;    
}    
//只能用函数来判断是否连接成功    
if  (mysqli_connect_errno()) {    
     echo  mysqli_connect_error();    
}
 
     
$requestType  $_POST [ 'request_type' ];    
switch  ( $requestType ) {    
     case  'get_message' : //客户端请求读取消息    
         break ;    
     case  'comfrim_read' : //客户端确认已经读取了信息,服务端需要更新读取状态    
         $idsArr  $_POST [ 'send_data' ];    
         $ids  = implode( ',' $idsArr );    
         $sql  "update message set status = 2 where id in ({$ids})" ;    
         mysqli_query( $link $sql );    
         mysqli_close( $link );    
         break ;    
     default :    
         break ;    
 
    
$sql  "select * from message where reciver_uid='{$_POST['reciver_uid']}' and sender_uid='{$_POST['sender_uid']}' and status='1'" ;    
$i  = 0;    
while  (true) {    
     //读取数据    
     $result  = mysqli_query( $link $sql );    
     if  ( $result ) {    
         $returnArr  = [];    
         while  ( $row  = mysqli_fetch_assoc( $result )) {    
             $row [ 'send_time' ] =  date ( 'Y-m-d H:i:s' $row [ 'create_time' ]);    
             $returnArr [] =  $row ;    
         }    
         if  (! empty ( $returnArr )) {    
             //返回结果    
             $data  = [    
                 'status'  => 1,    
                 'response_type'  =>  'is_read' ,    
                 'info'  =>  $returnArr ,    
             ];    
             echo  json_encode( $data );    
             mysqli_free_result( $result );    
             mysqli_close( $link );    
             exit ();    
         }    
     }