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
功能介绍:
对话双方都在线(浏览器没有关闭的情况下),对话即时推送.
支持离线发送消息.当离线方上线时,会自动接收离线消息.
采用确认机制确保数据推送成功.
采用超时退出机制,降低服务器资源浪费.
~~本项目只注重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);
}
|