一、 背景:
现在实时web消息推送一般会用到websocket,但是由于此技术并没有推广开来,所以各浏览器对其支持也不同,例如下图显示了各类浏览器的支持情况。
粉红色区域表示不支持Websocket。
至于IE浏览器,以及部分陈旧的桌面浏览器,可以选择Flashsocket作为替代品。
客户端如何把Websocket和Flashsocket结合在一起使用,可借鉴开源项目:web-socket-js (客户端的Websocket实现方案)
其思路和socket.io大致一致,仅仅提供对websocket的客户端的简单包装,若是Android 上原生浏览器,没有安装Flash Lite情况下,就无能为力了。
因此,仅仅凭借Websocket + Flashsocket,是不能够完成跨浏览器、统一客户端API的重任。
在这种情况下,socket.io就应运而生了。
二、socket.io的介绍以及优点
socket.io 支持以下通信信道传输协议:
socket.io并不是简单的封装了websocket,可以说websocket只是socket.io的其中一部分。使用socket.io客户端和服务器端双方约定适合当前浏览器的最佳通信信道,然后正常通信。并且还可以手动指定使用某种方式进行通信。只要我们指定socket.io transport的参数,就可以做到心里有数。
在socketio-netty服务器端配置:
transports = websocket,flashsocket,htmlfile,xhr-polling,jsonp-polling
在客户端,简单定义地址:
var socket = io.connect('http://localhost:9000');
在不远的将来,桌面版浏览器可能升级了最新版本的websocket草案,导致客户端原生的websocket协议无法被识别时,可使用Flashsocket作为替代品。但总会有一种通信协议垫底,可以保证正常的运转。
socket.io即提供了node.js服务器端又提供了客户端的整体解决方案,而socketio-netty则是基于JAVA服务器端,支持最新socket.io client最新版规范。对JAVA编程人员来讲,可以不用学习node.js,从而多了一个选择。
注:附1中简单的介绍了集中不同通信协议的优缺点。有兴趣的可以查看。
三、socket.io事件和方法简单介绍
Socket.IO内置了一些默认事件,我们在设计事件的时候应该避开默认的事件名称,并灵活运用这些默认事件。
服务器端事件:
server.addEventListener("con", Object.class, new DataListener
添加监听的事件以及该时间中传输消息的实体对象,服务端可以通过此方法来捕捉到client传输过来的Message请求;
server.addConnectListener(new ConnectListener();
添加连接监听时间,当client连接时会首先触发此事件,server可以在这里进行一些初始化操作。
server.addDisconnectListener( new DisconnectListener();
添加断开连接监听时间,当client断开连接时会首先触发此事件,server可以在这里进行一些断线操作。(包括关闭浏览器,主动断开,掉线等任何断开连接的情况)。客户端事件:
connect:连接成功disconnect:断开连接message:同服务器端message事件 在这里要提下客户端socket发起连接时的顺序。当第一次连接时,事件触发顺序为:connecting->connect;当失去连接时,事件触发顺序为:disconnect->reconnecting(可能进行多次)->connecting->reconnect->connect。
注意:刷新浏览器时,相当与客户端首先disconnect然后重新建立一次connect。并且此时socket.io会默认重连之前断开的连接。
客户端常用方法:
socket.emit()和socket.on();这两种都可以用来发送消息,只是在写法上有稍微不同。
socket.emit('action');表示发送了一个action命令,命令是字符串的,也可以这么写: socket.on('action',function(){...});
socket.emit('action',data);表示发送了一个action命令,还有data数据,也可以这么写: socket.on('action',function(data){...});
socket.emit(action,arg1,arg2); 表示发送了一个action命令,还有两个数据,也可以这么写: socket.on('action',function(arg1,arg2){...});
在emit方法中包含回调函数,例如:
socket.emit('action',data, function(arg1,arg2){...} );那么这里面有一个回调函数可以在另一端调用,也可以这么写:socket.on('action',function(data,fn){ fn('a','b') ; });
上面的data数据可以有0个或者多个,相应的在另一端改变function中参数的个数即可,function中的参数个数和顺序应该和发送时一致
上面的fn表示另一个端传递过来的参数,是个函数,写fn('a','b') ;会回调函数执行。一次发送不应该写多个回调,否则只有最后一个起效,回调应作为最后一个参数。
四、简单的代码应用(聊天室)
服务端:
package com.corundumstudio.socketio.demo;
import java.util.HashMap;
import java.util.Map;
import com.corundumstudio.socketio.listener.*;
import com.corundumstudio.socketio.*;
public class ChatLauncher {
private static Map
public static void main(String[] args) throws InterruptedException {
Configuration config = new Configuration();
//设置主机名称UR了
config.setHostname("localhost");
//设置端口,此处必须设置,不设置启动时会报错
config.setPort(80);
final SocketIOServer server = new SocketIOServer(config);
server.addEventListener("con", ChatObject.class, new DataListener
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
data.setMessage("用户处于在线状态");
System.out.println("uuid"+client.getSessionId());
server.getBroadcastOperations().sendEvent("chatevent", data);
//回调当前clicent的函数
//client.sendEvent("chatevent", data);
}
});
server.addEventListener("discon", ChatObject.class, new DataListener
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
data.setMessage("用户离线了");
server.getBroadcastOperations().sendEvent("chatevent", data);
System.out.println("离线了!!!");
client.disconnect();
//回调当前clicent的函数
//client.sendEvent("chatevent", data);
}
});
//当client连接时触发此事件
server.addConnectListener(new ConnectListener(){
@Override
public void onConnect(SocketIOClient client) {
System.out.println(client.getSessionId()+"在线了!!!");
}
});
//当client离线时触发此事件
server.addDisconnectListener( new DisconnectListener(){
@Override
public void onDisconnect(SocketIOClient client) {
System.out.println(client.getSessionId()+"离线了!!!");
}
});
//监听端口上的chatevent事件
server.addEventListener("chatevent", ChatObject.class, new DataListener
@Override
public void onData(SocketIOClient client, ChatObject data, AckRequest ackRequest) {
// broadcast messages to all clients
server.getBroadcastOperations().sendEvent("chatevent", data);
//回调当前clicent的函数
//client.sendEvent("chatevent", data);
}
});
server.start();
Thread.sleep(Integer.MAX_VALUE);
server.stop();
}
}
客户端:
body {
padding:20px;
}
#console {
height: 400px;
overflow: auto;
}
.username-msg {color:orange;}
.connect-msg {color:green;}
.disconnect-msg {color:red;}
.send-msg {color:#888}
var userName = 'user' + Math.floor((Math.random()*1000)+1);
//页面打开时,进行连接,url后为端口信息,若不设置默认为连接80端口
var socket = io.connect('http://localhost:80');
//监听连接事件
socket.on('connect', function() {
//var jsonObject = {userName: userName,
// message: '1'};
//socket.emit("con",jsonObject);
output('Client has connected to the server!');
});
//监听chatevent事件
socket.on('chatevent', function(data) {
output('' + data.userName + ': ' + data.message);
});
//监听离线事件
socket.on('disconnect', function() {
output('The client has disconnected!');
});
function sendDisconnect() {
socket.disconnect();
// var jsonObject = {userName: userName,
// message: '2'};
//socket.emit("discon",jsonObject);
}
function sendReconnect(){
socket.connect();
}
function sendMessage() {
var message = $('#msg').val();
$('#msg').val('');
var jsonObject = {userName: userName,
message: message};
//1、通过send方法发送数据
//chatSocket.json.send(jsonObject);
//2、原生emit方法
socket.emit('chatevent', jsonObject);
}
function output(message) {
var currentTime = "" + moment().format('HH:mm:ss.SSS') + "";
var element = $("
$('#console').prepend(element);
}
//回车事件
$(document).keydown(function(e){
if(e.keyCode == 13) {
$('#send').click();
}
});
运行服务端.java文件后,打开客户端页面即可进行聊天。如下图:
图 1
图 2
注意:完整代码在方的参考区域,有兴趣的可以下载下来一起学习。
五、学习心得
在html5之前,因为http协议是无状态的,要实现 浏览器与服务器的实时通讯,如果不使用 flash、applet 等浏览器插件的话,就需要定期轮询服务器来获取信息。这造成了一定的延迟和大量的网络通讯。随着HTML5 的出现,这一情况有望彻底改观,这与需要实现与服务器实时通信的应用来说,是一种极大的进步,而且随和用户对网络实时通信的要求越来越高,学习sockey.io这门技术也是很有发挥空间的。
附1:
不断地轮询(俗称“拉”,polling)是获取实时消息的一个手段:Ajax 隔一段时间(通常使用 JavaScript 的 setTimeout 函数)就去服务器查询是否有改变,从而进行增量式的更新。但是间隔多长时间去查询成了问题,因为性能和即时性造成了严重的反比关系。间隔太短,连续不断的请求会冲垮服务器,间隔太长,务器上的新数据就需要越多的时间才能到达客户机。
综上,考虑到浏览器兼容性和性能问题,采用长轮询(long-polling)是一种比较好的方式。
参考代码:
https://github.com/mrniko/netty-socketio
https://github.com/mrniko/netty-socketio-demo
参考文章:
http://tech.qq.com/a/20120521/000296.htm
http://www.cnblogs.com/luxiaoxun/p/4279997.html
http://blog.csdn.net/mengxianhua/article/details/44778733
http://blog.csdn.net/kelong_xhu/article/details/50846483 (socketio分布式)