TA的每日心情 衰 2021-2-2 11:21
签到天数: 36 天
[LV.5]常住居民I
一.WebSocket简单介绍 : L' |) @0 s& {: c, I' t
随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。$ {' t9 e6 i, v
我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。; U S* l) I$ X9 }' m+ w
轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。
' n( m C" v' R" e: h Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。
, L: V9 ?. [' J2 q, N 这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。
+ n& B$ x+ v! k* L8 k 伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过java Script向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
) d/ y9 ?, S" y6 c- @ JavaEE 7中出了JSR-356:Java API for WebSocket规范。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat从7.0.27开始支持 WebSocket,从7.0.47开始支持JSR-356,下面的Demo代码也是需要部署在Tomcat7.0.47以上的版本才能运行。" G& E$ _# N6 {6 t, q, N
7 I, U; p8 z( q' U 二、WebSocket协议介绍 + L9 c. A" x7 R( Y9 T
WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:1.WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;2.WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。简单的建立握手的时序图如下:0 ~$ R* X9 R K7 @1 N
" |# a4 [& J6 g2 x+ ~7 b1 g6 f, Y& _ 握手过程:
6 e Q6 I' E/ _# u( v Browser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。1 `- ]% S6 @2 f% Z9 R- K
在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。" [% \4 z. i8 L1 H
WebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。5 [7 O; E/ K) w0 n' V. o% R8 r. H P4 q T B
Browser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。
6 @0 V: q' q* y1 w6 }+ H 5 a9 I! k( V. k; |* o
三、Tomcat 7中的Websocket架构 # w' W p% r+ v# C @. A" B
3 j" m) T8 j+ T
如图所示,因为Websocket通信分为握手和数据传输两个过程,两个过程中需要用到的处理方式是不一样的,握手过程是基于HTTP 1.1基础上的,而数据传输是直接基于TCP的流传输。
7 Y$ U H9 E5 i 握手过程中,在HttpServletRequest的基础上,封装了WsHttpServletRequest类,添加了对Request的失效操作函数invalidate()。而在数据通信时,接受和处理数据过程中,基于org.apache.coyote.http11.upgrade.UpgradeInbound重新封装了用于处理数据输入流的类StreamInbound,并在StreamInbound的基础上扩展生成了用于消息处理的类MessageInbound。在这两个数据处理类中均留有onData,onTextData/onBinaryData,onOpen,onClose等事件操作函数接口,这些接口将在载入的代码类中实现业务逻辑。在用于数据输出流的类WsOutbound则是封装了UpgradeOutbound对象实例,基于UpgradeOutbound对象的基础上,添加了websocket响应有关的处理逻辑。这里处理函数均为同步调用的函数,保证websocket响应的时序性。- \9 K+ n) |; a) U' c6 {0 `6 r: D
Tomcat中Websocket的处理流程如下:
3 W, \* n9 \' j2 y) ~5 u
接收客户端发来的握手请求,Coyote.http11连接器对socket进行解析,形成HttpServletRequest发送给Container。
5 s, I( P% p0 k+ ?* Q Container中的相应WebsocketServlet处理请求,如不接受连接请求,则返回,如接受连接请求,则对请求作出响应,建立起客户端和服务器的socket连接。
8 S( Y4 }# V1 h# t) m' C5 q 服务器此时可以通过WsOutbound发送数据给客户端,同时通过StreamInbound监听socket。
) X' V) l k* X8 }" B 如果接收到客户端发来的数据,则将socket数据解析成frame,判断frame类型,通过事件分发数据到不同的逻辑处理流程。
# @% O( r% f& |# z3 W 数据返回时调用WsOutbound对返回的数据进行封装处理,发送给客户端。, N' B/ L, W6 P- a( b0 l, g8 D
- E: o6 s- ]1 v" v" Z
四、代码实现以及需求
$ Z u! [3 X+ {; N 2 a! M: ^- j o- m0 s# R
1、项目需要,定时向所有在线用户推送一个广告或是推送一个通知之类的(比如服务器升级,请保存好手头工作之类的)。
/ Y9 L+ U" y) G; J# L5 o: B 8 M1 Q* ?% W2 F$ Q3 m1 {; O0 O
2、相关环境 , Nginx、tomcat7、centos 6.53 I1 D! r0 u+ m' l) l7 H
, }5 D2 R! }) O* ]) R/ Z Y- F
3、项目框架,spring Mvc 4.0.6、layer $ C4 Y+ W# B% ~' P
+ D( s, J) D# V
4、代码实现:
3 z, z( E! Z$ p' L& Z
7 f M0 g7 t; @ WebSocketConfig: import websocket.handler.SystemWebSocketHandler;
9 ^1 U" @0 }4 E# D0 _. ~ ] @Configuration! N4 }$ A2 @5 u4 {6 }% X1 V2 \
@EnableWebMvc
. R1 J: B$ e+ [5 X" z# i$ u" P @EnableWebSocket8 l/ B+ t0 h3 @
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{- e1 a, o* Y1 x" t. `! e4 S
0 y& r* B* C2 ?; U E6 O @Override
9 v7 X" D8 S3 b% ~ public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
. P7 J+ c( F2 Q" W5 g registry.addHandler(systemWebSocketHandler(),"/webSocketServer");" M) V; L. I* X
registry.addHandler(systemWebSocketHandler(),"/sockjs/webSocketServer");
* p; T( [1 w5 K' J }* P( @- f# S3 Q7 L
@Bean5 Y9 Y' p( t8 B# O1 S) J
public WebSocketHandler systemWebSocketHandler(){: W$ Y+ s& g3 @' Y! w# n# z
return new SystemWebSocketHandler();# ~. K0 Q1 E9 M1 p' m, d
}
( k% }, n9 p. E" B } 复制代码 SystemWebSocketHandler: 2 h+ Y7 |! T% q: Q' w
public class SystemWebSocketHandler extends TextWebSocketHandler {/ P# O2 z0 ~- U+ j1 N
8 E; }2 c q3 S8 y: F3 ]
1 m. k. Y$ ~/ B9 _3 Z5 B, p0 T; y private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();;
* R3 O6 {; e$ I* N
% ]2 j& Y0 \& Y public void afterConnectionEstablished(WebSocketSession session) throws Exception {
; \: S/ E* I! l8 o/ ] System.out.println("ConnectionEstablished");& L, i; ]1 _# P2 b( a6 K, b4 b
users.add(session);3 U- n# b a9 z6 G7 ^5 @
System.out.println("当前用户"+users.size());
3 g" f% |$ d [* @, R. S' h7 y }
) M6 H5 A! }. o4 g5 E& Y2 ?% T /**
+ p( Q Y# O, F' k * 在UI在用js调用websocket.send()时候,会调用该方法8 D# m& `0 [# ]3 E5 y" X1 h
* @Author 张志朋, |7 i* Y1 m W5 o6 y
* @param session- U- u. g4 Z0 I( x# T4 X8 O! j
* @param message
6 I' j4 h3 H; N. ?( w) N5 o# O0 h5 H * @throws Exception & C/ A" k9 h: S; ]! W( J
* @Date 2016年3月4日/ ?: }3 k2 t: a( w; P# o
* 更新日志 [$ T7 G# p* m3 {9 Z5 D- l
* 2016年3月4日 张志朋 首次创建" V4 x; V0 Q j& z. U, x y
*5 e# U8 z% i) o: j) U
*/
: a0 q+ C* X1 ] Y @Override
/ O1 u+ W& \1 U* a) _" y protected void handleTextMessage(WebSocketSession session,
/ o" u* U% o* q+ a# P+ _ TextMessage message) throws Exception {
) D- W) [5 d y" s! N super.handleTextMessage(session, message);3 ^, B* N+ |0 H
sendMessageToUsers(session,message);
* ~1 M$ e7 D3 Q6 p, l; g8 S }8 \2 R1 t8 b- ~+ p6 e, t2 x
@Override
/ L3 c0 a) [" l* I+ q! W public void handleTransportError(WebSocketSession session, Throwable exception) throws IOException {
q) s* P4 G) A4 ] |. g4 ?6 X if(session.isOpen()){7 L- E2 q& D' X+ o% B Y& a* z
session.close();
# F( J- ~* l2 O. C$ T: I- q }/ Z- |% M- _% [2 @4 u
users.remove(session);0 ?% o3 v# ?" P8 D
}- g7 U, K, s5 R5 ]
' l6 `& y1 r; y3 x" w! T @Override
7 B: R% k _; O7 t% D1 p public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
) \" C+ U9 f @, Y5 |) _4 L/ x users.remove(session);- z4 m5 W6 {+ V
}# s; }: B5 X4 V% m
1 P8 L$ }3 U \& W; Q" y' }8 m
@Override& E& A) _' G" W" [6 S$ \5 E
public boolean supportsPartialMessages() {
1 W( F+ F: n3 z. T return false;9 F4 K1 f' }# Y) |- l+ E- u. `
}
' N) }& e$ n$ U5 e5 ]% Q4 G /**9 y/ m7 X$ D O- F1 y$ v
* 给所有在线用户发送消息
0 {3 S2 X! ?& C% a6 K7 C4 ] * @Author 张志朋2 j2 ^( D0 ~7 ]6 }6 D6 w- F
* @param message void
( E( n4 x, l2 ], ~* Z; H1 D7 R * @Date 2016年3月4日
/ g+ [5 ~' Z! c6 }, y. Z- X0 @8 f * 更新日志2 h. ^" B, Q1 T1 B$ D' z2 x/ X
* 2016年3月4日 张志朋 首次创建
& b# Y0 v$ ^! T) j5 s& `6 q *5 c, T/ m: |% p2 T0 y; |7 |
*/
8 a. X' U5 D$ n8 `( T3 I( [ public void sendMessageToUsers(WebSocketSession session,TextMessage message) {
9 t" t( x8 e1 _1 x6 { n for (WebSocketSession user : users) {
3 ]( r* \1 D9 s `# G6 T+ ]+ I3 ~ try {
: Y" C" O0 C2 i# j2 D' u8 E if (user.isOpen()) {
3 X" D7 k& C* ^1 W! \2 A7 X& K6 O/ n user.sendMessage(message);( J' x9 a# D* Z& c
}" b% ]2 j( z, u% O
} catch (IOException e) {0 d! k2 \1 Y" k& a
e.printStackTrace();
1 }) e5 w1 Y1 ]7 p# X! y- ` } J- Q9 U0 W7 P4 F6 I- h
}
) I( ], `' j' ~% K' K: T }
# q. y& X: y1 D }
% N3 Q- ^) o+ A& T& K 复制代码 信息输入 index.html:& {% z% m$ q% C% z& \3 r
<html xmlns="http://www.w3.org/1999/xhtml">8 b2 v) k& a% s" R, c8 ~& k
<head>3 b1 B# ~4 D; l# b$ k
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
" z/ R1 {" V* B7 H <title>请输入任意消息</title>
- l8 r/ F' Z0 b$ i v% \ <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
6 ?. {$ Q$ [3 o0 h <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
4 j7 }. r, V4 W8 w3 Z2 i0 ?5 b% k8 P <script type="text/javascript">8 a1 j% i7 b; @, T& n: {
var ws = null;" @& y8 Q/ K2 K
$(function () {
" A% F. ]. G/ a; z if ('WebSocket' in window) {' A3 ^6 ^- ~ ~: o7 C
ws = new WebSocket('ws://127.0.0.1:8080/webSocketServer'); 9 C; u( E$ c" N& }4 ^
} ! F* C; R% g) o+ I. v6 k/ w
else if ('MozWebSocket' in window) {- B3 q! a& Y% |! H4 \* ~7 _
ws = new MozWebSocket("ws://127.0.0.1:8080/webSocketServer");
- l* @# R# `$ K! s( E }
, e. _$ F1 y* O# F else {
% j7 U- B) Z. S ws = new SockJS("ws://127.0.0.1:8080/webSocketServer");
1 N# Q/ v8 [& _- {6 h7 }$ X }
: z$ a. | p6 r0 y5 D$ x9 p3 l; ` ws.onopen = function () {
' |2 ] F* g# a7 Y: }; |" {
% k& i+ @( V* I0 L* o };
( O6 S+ C5 m2 o( s& B) P1 P6 _ ws.onmessage = function (event) {) E6 O7 F' O n9 `3 ]7 f) N& d
5 L- O! a9 w+ z' H };
0 z( a; k; {/ H: @9 { ws.onclose = function (event) {' i" @* @- Z8 h- ?5 h6 c( Y
; e& H% R5 {, h# ]5 P* |& m3 Q+ z };: y# ^( A* c* O, R; D6 a4 e
});
7 S/ T1 \; Q% b8 C: B function stop(){% N7 g5 J$ x1 n/ Y4 t# j
var message = $("#message").val();2 R) X( k" F2 u; Z( k; ?! v
ws.send(message);) J5 h' N& r9 t
}$ z6 Z+ F- _2 E! A, p
</script>
$ Y, r0 G: O: Z. l+ e0 T0 P! J </head>& ^' c$ n/ j" C' \! q* I. z8 s
<body class="keBody">
5 @) ^- n. ^3 m$ X; P2 B 请输入提示信息: <textarea id="message"></textarea><br />
' r: k" }; h& S' `; r <input type="button" value="开始" />+ C }4 y. m0 O3 e( f8 H0 ?9 z
</body>
. |: I- P# P1 {+ G6 `' R; Q& Z1 B </html> 复制代码 3 M+ `/ V$ ]" W
webSocket.js 用于导入项目。document.write("<script language=javascript src='http://127.0.0.1:8080/js/jquery-1.10.2.min.js'></script>");- K. p+ ?5 |2 p
document.write("<script language=javascript src='http://127.0.0.1:8080/layer/layer.js'></script>");
' r$ q2 J( H6 K/ b$ S document.write("<script language=javascript src='http://cdn.sockjs.org/sockjs-0.3.min.js'></script>");
! x8 d3 f3 B% k d! J var ws = null;2 D+ C( ^9 ~! S% e" k
var basePath = "ws://127.0.0.1:8080/";
- A2 v2 {% v- R- u4 y8 c U ^ if ('WebSocket' in window) {) D5 w$ w1 q3 K) Q7 ] n
ws = new WebSocket(basePath+'webSocketServer'); & n+ I8 F3 _+ _7 O8 l& V9 ~
}
! {# r F% ]/ ~) t$ z N else if ('MozWebSocket' in window) {
7 g( k! G/ V+ V ws = new MozWebSocket(basePath+"webSocketServer");
0 u% r' F$ f" z& _: ]: y, j }
1 G* r8 r) t+ d( R" z& c4 X8 J else {
/ [% d3 D& r% n: W* l+ \6 K8 x ws = new SockJS(basePath+"sockjs/webSocketServer");( F9 z! N3 @7 Y: l' n
}
6 V3 \9 b2 a' t ws.onopen = function () {1 j: ~4 t4 j/ F# A5 K2 Z
( }7 d* o: R2 l
};0 ^3 L, N3 ]8 D' }
ws.onmessage = function (event) {) e' F$ n. {% ^/ j
pop(event.data);
2 Z' ~" Z. D7 s1 O M' b };
8 }/ y' a4 u T: z" q* d ws.onclose = function (event) {
* D1 ~. T( Y' @( b! h: a2 b/ } ws.close();6 c/ N3 e& w% d; |5 \" y& o
}; [3 z9 }' Q2 z: u" W3 D
//提示信息
/ n2 v* Y# s! [ function pop(message){
7 L7 \' z: m1 T9 q* {7 v9 P layer.alert(message);
8 H* c$ e- p! m& W+ G } 复制代码 * I" J% F; l. R( }$ a; Z* L
5、在项目头部引入
9 l7 T: @& V$ s: h <script language=javascript src='http://127.0.0.1:8080/webSocket.js '></script> 3 f& b, P8 G% N9 V7 L4 v
. m j+ c1 h# c4 `/ F' _ ) @, d1 D. J. R' s& J M- r6 c
这时查看后台 会有以下信息 说明 引入成功。
* d0 n" P( N) S( W0 g2 w
4 T" m5 N, a: j8 [
6 H/ ?& Y2 E, j3 M5 b# F3 O 然后在打开页面 index.html 输入以下内容 点击开始即可。 2 E4 { c8 ^: ~, R
. n; F/ j1 ?: j0 u. N- p3 B
- O" M1 L$ D% q1 L% Q5 n7 Y 如果在网站出现一下提示说明配置成功,这时候所有网站登录用户都可以收到此信息。 ) E1 A) K# f2 L- \$ U0 [
# s& V" f* v0 ~0 F+ `- ~ N
# h0 v+ D6 U& a
- r& Y( K1 Q5 i' Q, X# X6 W+ }
项目下载地址:
! p2 m9 v' ]7 I+ g9 a6 a6 v 0 I* w( I6 G0 g _$ b9 d4 _( Q
& F" c. @) w+ [' a* t
& y9 R1 [% h! D5 v
- ~0 R3 p) }) j: V ?. w7 q
科帮网 1、本主题所有言论和图片纯属会员个人意见,与本社区立场无关2、本站所有主题由该帖子作者发表,该帖子作者与科帮网 享有帖子相关版权3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和科帮网 的同意4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意7、科帮网 管理员和版主有权不事先通知发贴者而删除本文
JAVA爱好者①群:
JAVA爱好者②群:
JAVA爱好者③ :