WebSockets vs Server-Sent Events vs Long-polling
来源:互联网 发布:linux的touch命令 编辑:程序博客网 时间:2024/06/05 09:39
Apparently social networking is the banner of the nowadays web. Everybody intends bringing some features into his projects. Some of them require immediate notification. That is getting common, if you open a page with upcoming messages (status feed, notification subsystem, friends-list), you expected them being updated as soon as a new message (status, notification, friend-making action) arrives. As you well know, original web design allowed only one-way client-server communication (one requests, another one responds), though now HTML5 working group doing their best to fix it or rather to patch it. However, the web-projects are still using long-polling trick to emulate server-client communication.
Well, now new web browser versions appear every few months. Besides they update automatically. Thus a huge number of users have the latest browser versions, which support HTML 5 communication APIs. Is that the time to put long-polling away? Let’s find out.
Our test task will be something you may likely need if you have on your site any sort of user communication. That is notification of user actions. In the simplest case when the user gets a private message, the number of unread notifications increases in the user panel. We will solve the task using long-polling, Server-Sent Events and WebSockets. Then we compare the results.
First of all let’s examine the common code used in the examples. We will need configuration file, a library to access DB, a model to retrieve unread notification number and to add a new notification.
Usually such communication API examples don’t include any business logic, but execution delays to emulate the model working. I would like to make it close to the real application, what is meant to help when comparing memory/CPU usage on the server for each of the cases.
So, we will need a dump DB table:
CREATE TABLE IF NOT EXISTS `notification` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`recipientUid` int(10) unsigned NOT NULL,
`eventId` int(10) unsigned NOT NULL,
`isNew` tinyint(1) unsigned NOT NULL DEFAULT '1',
PRIMARY KEY (`id`),
KEY `IX_recipientUid` (`recipientUid`),
KEY `IX_isNew` (`isNew`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='User notifications';
Here is the model:
<?php
class
Model_UserNotification
{
private
$_db
;
public
function
__construct(Lib_Db
$db
)
{
$this
->_db =
$db
;
}
/**
*
* @param int $recipientUid
* @return int
*/
public
function
fetchNumberByRecipientUid(
$recipientUid
)
{
return
$this
->_db->fetch(
"SELECT count(*) as count "
.
" FROM notification WHERE recipientUid = %d AND isNew = 1"
,
$recipientUid
)->
count
;
}
/**
*
* @param int $recipientUid
* @param int $eventId
*/
public
function
add(
$recipientUid
,
$eventId
)
{
$this
->_db->update(
"INSERT INTO "
.
" notification (`id`, `recipientUid`, `eventId`, `isNew`) VALUES (NULL, '%d', '%d', '1')"
,
$recipientUid
,
$eventId
);
}
/**
*
* @param int $recipientUid
*/
public
function
removeAll(
$recipientUid
)
{
$this
->_db->update(
"DELETE FROM "
.
" notification WHERE recipientUid = %d"
,
$recipientUid
);
}
}
Long pulling
How it works
Client application (browser) sends a request with event recipient id (here is the user, registered on the page) and current state (the displayed number of unread notification) to the server via HTTP. It creates an Apache process, which repeatedly checks DB until the state is changed in there. When the state eventually changed, the client gets the server response and sends next request to the server.
Implementation
Client side contains simple HTML with two input fields to show response data (updated number of unread notifications and time of the response event). JS module sends recipient user id and current state (unread notification number) to the server as XMLHttpRequest. To make possible cross-domain communication, we use JSONP and that means the handler must of the public scope.
...
<p>Recipient id: <?= $recipientUid ?></p>
<p>Notifications: <input id=
"notificationNum"
size=
"4"
name=
"some"
value=
"<?= $displayedNotificationNum ?>"
/></p>
<p>Last event arrived at: <input id=
"time"
size=
"12"
name=
"some"
value=
"0"
/></p>
<script type=
"text/javascript"
>
(
function
( $ ) {
var
UID = <?= $recipientUid ?>;
$.NotifierLongPolling = (
function
() {
var
_stateNode = $(
'#notificationNum'
), _timeNode = $(
'#time'
);
return
{
onMessage :
function
(data) {
_stateNode.val(data.updatedNotificationNum);
_timeNode.val(data.time);
setTimeout($.NotifierLongPolling.send, 3000);
},
send :
function
() {
$.ajax({
'url'
:
'server.php'
,
'type'
:
'POST'
,
'dataType'
:
'jsonp'
,
'jsonpCallback'
:
'$.NotifierLongPolling.onMessage'
,
'data'
:
'recipientUid='
+ UID +
'&displayedNotificationNum='
+ _stateNode.val()
});
}
}
}());
// Document is ready
$(document).bind(
'ready.app'
,
function
() {
setTimeout($.NotifierLongPolling.send, 40);
});
})( jQuery );
</script>
Server waits 3 second than check if the updated state matches the given one. If the state has changed in the DB, the server responds otherwise it repeats the cycle.
//...
$recipientUid
= (int)
$_REQUEST
[
"recipientUid"
];
$displayedNotificationNum
= (int)
$_REQUEST
[
"displayedNotificationNum"
];
$secCount
= 0;
do
{
sleep(IDLE_TIME);
$updatedNotificationNum
=
$model
->fetchNumberByRecipientUid(
$recipientUid
);
}
while
(
$updatedNotificationNum
==
$displayedNotificationNum
);
header(
"HTTP/1.0 200"
);
printf (
'%s({"time" : "%s", "updatedNotificationNum" : "%d"});'
,
$_REQUEST
[
"callback"
],
date
(
'd/m H:i:s'
),
$updatedNotificationNum
);
Client side receives new state from the server, displays and sends to the server the new state to repeat the workflow.
Server-Sent Events
How it works
Client (browser) sends a request to the server via HTTP. It creates a process, which fetches latest state in the DB and responds back. Client gets server response and in 3 seconds sends next request to the server.
Implementation
HTML on client-size has again two input fields to show response data. JS module opens EventSource and passes though the connection recipient user id.
...
<p>Recipient id: <?= $recipientUid ?></p>
<p>Notifications: <input id=
"notificationNum"
size=
"4"
name=
"some"
value=
"<?= $displayedNotificationNum ?>"
/></p>
<p>Last event arrived at: <input id=
"time"
size=
"12"
name=
"some"
value=
"0"
/></p>
<script type=
"text/javascript"
>
(
function
( $ ) {
var
UID = <?= $recipientUid ?>;
NotifierSSE = (
function
() {
var
_stateNode = $(
'#notificationNum'
),
_timeNode = $(
'#time'
),
_src,
_handler = {
onMessage :
function
(event) {
var
data = JSON.parse(event.data);
_stateNode.val(data.updatedNotificationNum);
_timeNode.val(data.time);
}
};
return
{
init :
function
() {
_src =
new
EventSource(
"server.php?recipientUid="
+ UID);
_src.addEventListener(
'message'
, _handler.onMessage,
false
);
}
}
}());
// Document is ready
$(document).bind(
'ready.app'
,
function
() {
setTimeout(NotifierSSE.init, 40);
});
})( jQuery );
</script>
Server responds into the data stream with the last state (unread notification number) regarding the recipient user id.
//...
header(
'Content-Type: text/event-stream'
);
header(
'Cache-Control: no-cache'
);
// recommended to prevent caching of event data.
$recipientUid
= (int)
$_REQUEST
[
"recipientUid"
];
function
send(
$updatedNotificationNum
)
{
printf (
"id: %s\n\n"
, PROC_ID);
printf (
'data: {"time" : "%s", "updatedNotificationNum" : "%d"}'
.
"\n\n"
,
date
(
'd/m H:i:s'
) ,
$updatedNotificationNum
);
ob_flush();
flush
();
}
while
(true) {
send(
$model
->fetchNumberByRecipientUid(
$recipientUid
));
sleep(IDLE_TIME);
}
//...
Client gets onMessage event handler invoked, the data displayed. Browser will repeat request every 3 second unless you close the connection (close method). You can change delay between requests by passing into the data stream on the server “retry: <time in milliseconds>\n”.
WebSockets
How it works
Client notifies web-socket server (EventMachine) of an event, giving ids of recipients. The server immediately notifies all the active clients (subscribed to that type of event). Clients process event when given recipient Id matches the client’s one.
Implementation
Here we need an Event Machine supporting WebSockets. The easiest way, I see, is to deployWaterSpout Server . You write your own controller (notification_controller.php) based onlocke_controller.php.
Client opens WebSocket connection and subscribes a handler for events on /notification/updatespost. Now let’s add a button in the HTML which sends on /notification/presents recipient user and action ids. That will cause broadcast message on the all open connections. So every active client receives the notification. The client event handler checks if the recipient user id matches client’s logged in user id and if so increment unread notification number.
<p>Recipient id: <?= $recipientUid ?></p>
<p>Notification: <span id=
"display"
></span></p>
<button id=
"test"
>Fire an event</button>
<script>
realtime =
new
realtimeComm(window.location.host +
":20001"
);
realtime.addListener(
'/notification/updates'
,
function
(response) {
$(
'#display'
).html(
'Client #'
+ response.data.recipientUid +
' broadcast an action #'
+ response.data.actionId);
});
$(
'#test'
).bind(
'click'
,
this
,
function
(e){
e.preventDefault();
realtime.send(
'/notification/presence'
, {
'actionId'
: 1,
'recipientUid'
: <?= $recipientUid ?>
},
function
() {});
});
</script>
Working a bit on the controller, you can make the system responds exclusively to the client of the given recipient user id.
You’ve probably noticed the example working perfect on Chrome, but for Firefox 6.0 it switches for long-polling. It announced that Firefox 6 supports WebSockets, but you ain’t going to find WebSocket object defined. Well, let’s change WebSocket availability check in the top of realtime.js with following:
if
(!window.WebSocket) {
window.WebSocket = window.MozWebSocket ? window.MozWebSocket : undefined;
}
Oops, it doesn’t work anyway. Firefox 6 supports hybi-07 specification version, when Chrome and WaterSpout – hybi-00 (hixie-76). So, you will hardly able the connection through reverse-proxies and gateways. Besides, the old specification was disabled in Firefox and Opera due to security issues. The only browsers now supporting the latest WebSocket specification are betas of Firefox 7/8 and Chrome 14. As for EventMachine server implementations, I found none.
Conclusion
- WebSockets VS Server-Sent Events VS Long-polling
- WebSockets vs Server-Sent Events vs Long-polling
- WebSockets VS Server-Sent Events VS Long-polling
- WebSockets vs Server-Sent Events vs Long-polling
- WebSockets VS Server-Sent Events VS Long-polling
- What are Long-Polling, Websockets, Server-Sent Events (SSE) and Comet?
- Long-Polling, Websockets, SSE(Server-Sent Event), WebRTC 之间的区别
- Long-Polling, Websockets, SSE(Server-Sent Event), WebRTC 之间的区别
- Bosh vs Comet vs Long Polling vs Polling
- 从long polling 到 websockets
- BOSH vs WebSockets:
- websocket server-sent polling 对比
- server-sent events使用
- Multiple Events in Server-Sent Events
- Server-sent Events和 websocket
- SSE(Server-sent Events)
- Server-Sent Events &&Web Sockets
- C# events vs. delegates
- 二元函数极值
- 来下载的都来顶一下。
- linux shell 的:= 和 export
- cas 单点登录框架
- PCIe总线的基础知识
- WebSockets vs Server-Sent Events vs Long-polling
- 永久免费的专业二维码生成器和二维码扫描器
- spring-shiro.xml
- Java 数据类型
- Ubuntu下eclipse+maven+svn+tomcat配置
- 豌豆荚宣布与LINE合作 负责在华运营
- android Bitmap学习总结
- 多边形重心问题
- java 内存移到堆外!!! Jvm gcih 淘宝优化JVM实践