现在一说到实时web,可能大家不由自主的就想到了node.js,确实,在语言级别node.js实现了异步的、基于事件机制的IO特性,使用简单。在JAVA语言层面,提供了NIO作为非阻塞IO的替代品。无论node.js还是JAVA,都没有从真正意义上实现AIO(这个需要操作系统支持,依赖内置库/运行时模拟支持)。
要说到实时Web,就不能不说到Websocket,目前存在的几个草稿协议,若是要自己实现websocket协议支持,需要注意其握手协议等。
目前对Websocket的支持,caniuse.com 给出了一张图表,详细说明了各个浏览器的支持情况:
粉红色区域表示不支持Websocket。
至于IE浏览器,以及部分陈旧的桌面浏览器,可以选择Flashsocket作为替代品。
客户端如何把Websocket和Flashsocket结合在一起使用,可借鉴开源项目:web-socket-js (客户端的Websocket实现方案)
其思路和socket.io大致一致,仅仅提供对websocket的客户端的简单包装,若是Android 上原生浏览器,没有安装Flash Lite情况下,就无能为力了。
因此,仅仅凭借Websocket + Flashsocket,是不能够完成跨浏览器、统一客户端API的重任。
socket.io 支持以下通信信道传输协议:
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 ~~~"
);
}
}
|
如何让打包的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的服务器端: