本文共 7742 字,大约阅读时间需要 25 分钟。
web im的实现方式有很多种:
1.普通轮询,原理通过js定时重复发送ajax请求服务端,获取数据后显示。
2. 长轮询,ajax请求服务端,服务端有数据会立即返回。服务端无数据时会一直等待,直到有数据了才立即返回。
3.socket长连接。
特征分析:
方法1:实现起来最容易,定时重复请求服务端会产生无意义的http连接,消耗服务端资源,实时性较差.
方法2:实现起来较容易,会减少无效的ajax请求产生的http连接,能即时返回数据,但服务端会一直挂着,会消耗一定的资源,处理并发能力不强,比较适合于中小型应用服务.(comet)
方法3:门槛较高,需了解socket通讯协议,是http实现长连接的最佳方式,也是真正意义上的server push技术.
以即时通信为代表的web应用程序对数据的Low Latency(低延时)要求,传统的基于轮询的方式已经无法满足,而且也会带来不好的用户体验。于是一种基于http长连接的“服务器推”技术便被hack出来。这种技术被命名为Comet,这个术语由Dojo Toolkit 的项目主管Alex Russell在博文首次提出,并沿用下来。 其实,服务器推很早就存在了,在经典的client/server模型中有广泛使用,只是浏览器太懒了,并没有对这种技术提供很好的支持。但是Ajax的出现使这种技术在浏览器上实现成为可能, google的gmail和gtalk的整合首先使用了这种技术。随着一些关键问题的解决(比如 IE 的加载显示问题),很快这种技术得到了认可,目前已经有很多成熟的开源Comet框架。 以下是典型的Ajax和Comet数据传输方式的对比,区别简单明了。典型的Ajax通信方式也是http协议的经典使用方式,要想取得数据,必须首先发送请求。在Low Latency要求比较高的web应用中,只能增加服务器请求的频率。Comet则不同,客户端与服务器端保持一个长连接,只有客户端需要的数据更新时,服务器才主动将数据推送给客户端。
本文介绍第二种实现方法
案例名称:web即时聊天(ajax长轮询方式实现)
项目地址:
功能介绍:
对话双方都在线(浏览器没有关闭的情况下),对话即时推送.
支持离线发送消息.当离线方上线时,会自动接收离线消息.
采用确认机制确保数据推送成功.
采用超时退出机制,降低服务器资源浪费.
~~本项目只注重php服务端的实现机制和性能优化,前端界面粗糙请忽略.适合中级php程序员学习借鉴,欢迎各位指教交流~~
预览
项目文件结构:
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 i 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 (); } } $i ++; //需要给客户端发送确认信息是否还在连接服务器,客户端无回应则整个过程结束 if ( $i == $maxInvalidCount ) { $data = [ 'status' => 1, 'response_type' => 'is_connecting' , 'info' => '' , ]; echo json_encode( $data ); mysqli_close( $link ); exit (); } //file_put_contents('./test.log', date('Y-m-d H:i:s') . "已经重试{$i}次没有获取到信息" . "\r\n", FILE_APPEND); sleep(1); } |