构建实时Web的JAVA选择组合:socket.io client + socketio-netty server

前言

现在一说到实时web,可能大家不由自主的就想到了node.js,确实,在语言级别node.js实现了异步的、基于事件机制的IO特性,使用简单。在JAVA语言层面,提供了NIO作为非阻塞IO的替代品。无论node.js还是JAVA,都没有从真正意义上实现AIO(这个需要操作系统支持,依赖内置库/运行时模拟支持)。

 

Websocket/Flashsocket

要说到实时Web,就不能不说到Websocket,目前存在的几个草稿协议,若是要自己实现websocket协议支持,需要注意其握手协议等。

目前对Websocket的支持,caniuse.com 给出了一张图表,详细说明了各个浏览器的支持情况:

构建实时Web的JAVA选择组合:socket.io client + socketio-netty server_第1张图片

粉红色区域表示不支持Websocket。

至于IE浏览器,以及部分陈旧的桌面浏览器,可以选择Flashsocket作为替代品。

客户端如何把Websocket和Flashsocket结合在一起使用,可借鉴开源项目:web-socket-js (客户端的Websocket实现方案)

 

其思路和socket.io大致一致,仅仅提供对websocket的客户端的简单包装,若是Android 上原生浏览器,没有安装Flash Lite情况下,就无能为力了。

因此,仅仅凭借Websocket + Flashsocket,是不能够完成跨浏览器、统一客户端API的重任。

 

socket.io通信传输协议

socket.io 支持以下通信信道传输协议:

  • WebSocket
  • Adobe® Flash® Socket
  • AJAX long polling
  • AJAX multipart streaming
  • Forever Iframe
  • JSONP Polling

socket.io客户端和服务器端双方约定适合当前浏览器的最佳通信信道,然后正常通信。毫无疑问,基于全双工通信信道的Websocket/Flashsocket传输协议,是数据传输最为快速的选择,仅仅需要一个长连接。

基于上面图表,我们在指定socket.io transport参数时,可以做到心里有数。

在socketio-netty服务器端配置:

transports = websocket,flashsocket,htmlfile,xhr-polling,jsonp-polling

在客户端,简单定义地址:

var socket = io.connect('http://localhost:9000');

在不远的将来,桌面版浏览器可能升级了最新版本的websocket草案,导致客户端原生的websocket协议无法被识别时,可使用Flashsocket作为替代品。但总会有一种通信协议垫底,可以保证正常的运转。        

 

在线画板示范

一个聊天的示范实时的数据量不大,那么一个在线画板应用呢,大家所绘制的线和点能够立刻的同步到所有人的屏幕上去。嗯,它还是跨越目前所见的浏览器(IE6+,Firefox,Opera,Safari,Chrome等),支持Phonegap构建,支持iphone,android,ipad等。

因为没有部署的在线服务器,只能看一下简单视频了:

服务器端实现也是很简单,核心代码不到100行(不包括main函数、日志输出等):

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112

/**
* 在线画报socket.io服务器端示范
*
* @author yongboy
* @time 2012-3-27
* @version 1.0
*/
public class WhiteboardServer {
public static void main ( String [] args ) {
MainServer chatServer = new MainServer ( new WhiteboardHandler (), 80 );
chatServer . start ();
}
}
 
class WhiteboardHandler extends IOHandlerAbs {
private Logger log = Logger . getLogger ( this . getClass ());
// 房间的一对多<房间号,List<客户端>>
private ConcurrentMap < String , Set < IOClient >> roomClients = new ConcurrentHashMap < String , Set < IOClient >>();
 
@Override
public void OnConnect ( IOClient client ) {
log . debug ( "A user connected :: " + client . getSessionID ());
 
String content = String . format ( "5:::{\"name\":\"%s\",\"args\":[%s]}" ,
"clientId" ,
String . format ( "{\"id\":\"%s\"}" , client . getSessionID ()));
client . send ( content );
}
 
@Override
public void OnDisconnect ( IOClient client ) {
log . info ( "A user disconnected :: " + client . getSessionID ()
+ " :: hope it was fun" );
 
GenericIO genericIO = ( GenericIO ) client ;
Object roomObj = genericIO . attr . get ( "room" );
 
if ( roomObj == null ) {
log . info ( "the roomObj is null!" );
return ;
}
 
String roomId = roomObj . toString ();
 
Set < IOClient > clients = roomClients . get ( roomId );
log . info ( "clients size is " + clients . size ());
clients . remove ( client );
log . info ( "removed clients's size is " + clients . size ());
 
// 通知其它客户端,有人离线
broadcastRoom ( roomId , client , "roomCount" , String . format (
"{\"room\":\"%s\",\"num\":%s}" , roomId , clients . size ()));
}
 
@Override
public void OnMessage ( IOClient client , String oriMessage ) {
log . debug ( "Got a message :: " + oriMessage
+ " :: echoing it back to :: " + client . getSessionID ());
String jsonString = oriMessage . substring ( oriMessage . indexOf ( '{' ));
JSONObject jsonObject = JSON . parseObject ( jsonString );
String eventName = jsonObject . get ( "name" ). toString ();
JSONArray argsArray = jsonObject . getJSONArray ( "args" );
JSONObject obj = argsArray . getJSONObject ( 0 );
String roomId = obj . getString ( "room" );
 
if ( eventName . equals ( "roomNotice" )) {
if (! roomClients . containsKey ( roomId )) {
roomClients . put ( roomId , new HashSet < IOClient >());
}
 
GenericIO genericIO = ( GenericIO ) client ;
genericIO . attr . put ( "room" , roomId );
roomClients . get ( roomId ). add ( client );
 
int clientNums = roomClients . get ( roomId ). size ();
broadcastRoom ( roomId , client , "roomCount" , String . format (
"{\"room\":\"%s\",\"num\":%s}" , roomId , clientNums ));
 
String content = String . format (
"5:::{\"name\":\"%s\",\"args\":[%s]}" , "roomCount" , String
. format ( "{\"room\":\"%s\",\"num\":%s}" , roomId ,
clientNums ));
client . send ( content );
 
return ;
}
 
broadcastRoom ( roomId , client , eventName , obj . toJSONString ());
}
 
private void broadcastRoom ( String roomId , IOClient client ,
String eventName , String jsonString ) {
Set < IOClient > clients = roomClients . get ( roomId );
if ( clients == null || clients . isEmpty ())
return ;
 
String content = String . format ( "5:::{\"name\":\"%s\",\"args\":[%s]}" ,
eventName , jsonString );
for ( IOClient rc : clients ) {
if ( rc == null || rc . getSessionID (). equals ( client . getSessionID ())) {
continue ;
}
 
rc . send ( content );
}
}
 
@Override
public void OnShutdown () {
log . debug ( "shutdown now ~~~" );
}
}
view raw WhiteboardServer.java This Gist brought to you by  GitHub.

如何让打包的android支持websocket协议,可参考:为Phonegap Android平台增加websocket支持,使默认成为socket.io首选通道选择

本人暂无JAVA主机,无法部署到互联网环境下。只能截个图,看看先。

项目演示代码下载地址

 

小结

很显然,实时Web,是一种技术趋势,将成为一种人们的默认技术选择,用以拉近和桌面应用的距离。
socket.io是一种数据实时推送、事件驱动模型的框架,支持事件订阅,简单易用。其价值目前看来,还未被完整的挖掘出来。

socket.io即提供了node.js服务器端(地址)又提供了客户端(地址)的整体解决方案,而socketio-netty则是基于JAVA服务器端,支持最新socket.io client最新版规范。对JAVA编程人员来讲,可以不用学习node.js,从而多了一个选择。

这里再解释一下,为何构建app而不是webapp的服务器端:

  1. Tomcat 7 虽然支持了Websocket协议,仅采用最新websocket草案版本
  2. Jetty 8 已经支持Websocket,但才支持servlet 3.0规范
  3. 被绑定在某个单独的Servlet容器上面,不为通用
  4. Servlet容器协议栈可能过于庞杂

参考资料

  1. 实时Web时代即将到来:不只谷歌Twitter玩得起 
    http://tech.qq.com/a/20120521/000296.htm
  2. 文章转载自http://www.blogjava.net/yongboy/archive/2012/05/24/378839.html

你可能感兴趣的:(websocket)