1.各类web推送技术优缺点
不断地轮询(俗称“拉”,polling)是获取实时消息的一个手段
Ajax 隔一段时间(通常使用 JavaScript 的 setTimeout 函数)就去服务器查询是否有改变,从而进行增量式的更新。但是间隔多长时间去查询成了问题,因为性能和即时性造成了严重的反比关系。间隔太短,连续不断的请求会冲垮服务器,间隔太长,服务器上的新数据就需要越多的时间才能到达客户机。
优点:服务端逻辑简单;
缺点:其中大多数请求可能是无效请求,在大量用户轮询很频繁的情况下对服务器的压力很大;
应用:并发用户量少,而且要求消息的实时性不高,一般很少采用;
长轮询技术(long-polling)
客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息或超时(设置)才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。
优点:实时性高,无消息的情况下不会进行频繁的请求;
缺点:服务器维持着连接期间会消耗资源;
基于Iframe及htmlfile的流(streaming)方式
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
优点:消息能够实时到达;
缺点:服务器维持着长连接期会消耗资源;
插件提供socket方式
比如利用Flash XMLSocket,Java Applet套接口,Activex包装的socket。
优点:原生socket的支持,和PC端和移动端的实现方式相似;
缺点:浏览器端需要装相应的插件;
WebSocket
是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。
优点:更好的节省服务器资源和带宽并达到实时通讯;
缺点:目前还未普及,浏览器支持不好;
综上,考虑到浏览器兼容性和性能问题,采用长轮询(long-polling)是一种比较好的方式。
netty-socketio是一个开源的Socket服务器端的一个java的实现, 它基于Netty框架。
2.使用场景
- web版聊天室应用
- 股票交易走势
- 车辆实时监控
示例:模拟车联网应用中车辆在地图上动态移动,移动数据从后台获取,然后通过socket通信推送到前端页面, 使用百度地图API实现前端覆盖物的动态移动。
3.Server端实现
引入jar包
<!-- netty-socket.io -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
server服务实现
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(9092);
final SocketIOServer server = new SocketIOServer(config);
//客户端断开连接监听
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
System.out.println("**************onDisconnect****************");
//监听断开的连接存放在set中,用于后续判断结束对应的后台线程
disconnectSet.add(client.getSessionId());
}
});
//根据不同的命名空间,单个客户端触发
server.addNamespace("/position").addEventListener("msgevent", MsgObject.class, new DataListener<MsgObject>() {
@Override
public void onData(SocketIOClient client, MsgObject data, AckRequest ackRequest) {
//每个客户端连接启动一个后台线程
MsgThread msgThread = new MsgThread(client, data);
msgThread.start();
}
});
//广播式消息
server.addNamespace("/notice").addEventListener("msgevent", MsgObject.class, new DataListener<MsgObject>() {
@Override
public void onData(SocketIOClient client, MsgObject data, AckRequest ackRequest) {
server.getRoomOperations("/notice").sendEvent("msgevent", data);
}
});
//启动服务
server.start();
定义一个内部类,用于处理消息发送的线程
class MsgThread extends Thread {
private SocketIOClient client;
private MsgObject data;
//初始化方法
public MsgThread(SocketIOClient client, MsgObject data){
this.client = client;
this.data = data;
}
public void run() {
String temp = data.getMessage();
PositionObject posArray[] = new PositionObject[Integer.parseInt(data.getMessage())];
//此处模拟后端服务不断获取数据向客户端发送消息
while(true){
if(MsgServer.disconnectSet.contains(client.getSessionId())){
MsgServer.disconnectSet.remove(client.getSessionId());
//如果客户端断开则后台停止对应的线程
client.disconnect();
Thread.interrupted();
break;
}
data.setMessage(temp+":"+String.valueOf(Math.random()));
for(int i=0; i<posArray.length; i++){
//模拟多车GPS经纬度变化
PositionObject position = new PositionObject();
position.setX(116.380967+Math.random()*0.01);
position.setY(39.913285+Math.random()*0.01);
posArray[i] = position;
}
data.setPosArray(posArray);
//先客户端发送消息
client.sendEvent("msgevent", data);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4.Client端(Web实现)
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
body, html,#allmap {width: 100%;height: 80%;overflow: hidden;margin:0;font-family:"微软雅黑";}
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=百度地图AK"></script>
<script src="js/socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<title>模拟车辆根据GPS数据实时移动</title>
</head>
<body>
<div id="allmap"></div>
<div id="status"></div>
<div id="console"></div>
<button type="button" onClick="connect('http://localhost:9092/position','msgevent')" id="send">connect</button>
<button type="button" onClick="disconnect()">disconnect</button>
<input id="msg" class="input-xlarge" type="text" placeholder="Type something..." />
<button type="button" onClick="sendMsg()" id="send">Send</button>
</body>
</html>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");
map.centerAndZoom(new BMap.Point(116.404, 39.915), 15);
var myIcon = new BMap.Icon("http://developer.baidu.com/map/jsdemo/img/Mario.png", new BMap.Size(32, 70), { //小车图片
//offset: new BMap.Size(0, -5), //相当于CSS精灵
imageOffset: new BMap.Size(0, 0) //图片的偏移量。为了是图片底部中心对准坐标点。
});
var socket;
var eventN;
var carMkes=new Array();
var userName = 'user' + Math.floor((Math.random() * 1000) + 1);
var message;
function connect(url, eventName){
socket = io.connect(url, {
'force new connection': true,
reconnect: true,
'connect timeout': 5000,
'reconnection delay': 200
});
eventN = eventName;
socket.on('connect', function() {
$('#status').html('Client has connected to the server!');
});
socket.on(eventName, function(data) {
$('#console').html(data.userName + '=' + data.message);
doPosition(data);
});
socket.on('disconnect',function() {
$('#status').html('The client has disconnected!');
clearMap();
});
socket.on('reconnect', function() {
$('#status').html('Client has reconnected to the server!');
});
if(message!=null){
sendMsg();
}
}
//断开socket连接
function disconnect() {
socket.disconnect();
}
//向服务端发送数据
function emitMsg(param) {
socket.emit(eventN, param);
}
function sendMsg(){
message = $('#msg').val();
var param = {userName : userName,message : message};
emitMsg(param);
//模拟多辆车子
for(var i=0; i<message; i++){
var point = new BMap.Point(116.380967,39.913285);
var carMk = new BMap.Marker(point,{icon:myIcon});
carMkes[i] = carMk;
map.addOverlay(carMk);
}
}
//地图覆盖物定位显示,相当于车辆位置移动
function doPosition(data){
var posArray = data.posArray;
for(var j=0; j<posArray.length; j++){
var point = new BMap.Point(posArray[j].x,posArray[j].y);
carMkes[j].setPosition(point);
}
}
//清空地图覆盖物
function clearMap(){
map.clearOverlays();
carMkes = [];
}
</script>