TA的每日心情 | 衰 2021-2-2 11:21 |
---|
签到天数: 36 天 [LV.5]常住居民I
|
一.WebSocket简单介绍
: w3 b. s! ^: ]$ ` 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了。近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展了浏览器与服务端的通信功能,使服务端也能主动向客户端发送数据。
( Q0 g! P/ d5 o# U( o) B' m+ B 我们知道,传统的HTTP协议是无状态的,每次请求(request)都要由客户端(如 浏览器)主动发起,服务端进行处理后返回response结果,而服务端很难主动向客户端发送数据;这种客户端是主动方,服务端是被动方的传统Web模式 对于信息变化不频繁的Web应用来说造成的麻烦较小,而对于涉及实时信息的Web应用却带来了很大的不便,如带有即时通信、实时数据、订阅推送等功能的应 用。在WebSocket规范提出之前,开发人员若要实现这些实时性较强的功能,经常会使用折衷的解决方法:轮询(polling)和Comet技术。其实后者本质上也是一种轮询,只不过有所改进。4 ~6 B6 o9 K+ z' _( p3 e+ Q5 w
轮询是最原始的实现实时Web应用的解决方案。轮询技术要求客户端以设定的时间间隔周期性地向服务端发送请求,频繁地查询是否有新的数据改动。明显地,这种方法会导致过多不必要的请求,浪费流量和服务器资源。6 O+ l1 }+ p2 ]( h* T( e8 i$ `( s- I
Comet技术又可以分为长轮询和流技术。长轮询改进了上述的轮询技术,减小了无用的请求。它会为某些数据设定过期时间,当数据过期后才会向服务端发送请求;这种机制适合数据的改动不是特别频繁的情况。流技术通常是指客户端使用一个隐藏的窗口与服务端建立一个HTTP长连接,服务端会不断更新连接状态以保持HTTP长连接存活;这样的话,服务端就可以通过这条长连接主动将数据发送给客户端;流技术在大并发环境下,可能会考验到服务端的性能。* S3 Q1 s8 T+ T, [0 A: H4 i i
这两种技术都是基于请求-应答模式,都不算是真正意义上的实时技术;它们的每一次请求、应答,都浪费了一定流量在相同的头部信息上,并且开发复杂度也较大。: \0 ^# _5 o8 Q6 U/ f$ {# c8 c; N
伴随着HTML5推出的WebSocket,真正实现了Web的实时通信,使B/S模式具备了C/S模式的实时通信能力。WebSocket的工作流程是这 样的:浏览器通过javaScript向服务端发出建立WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务端就可以通过 TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet技术小 了很多。本文不详细地介绍WebSocket规范,主要介绍下WebSocket在Java Web中的实现。
9 M' y& i4 P; @: e& i: n- o$ `! r; n, @+ i 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以上的版本才能运行。
& @* O2 I: o/ `6 L% e. T; s
' n& Z# E2 G) E; p3 V9 J L( K二、WebSocket协议介绍% E1 W6 E; n8 ]0 A" o- |5 \
WebSocket协议是一种双向通信协议,它建立在TCP之上,同http一样通过TCP来传输数据,但是它和http最大的不同有两点:1.WebSocket是一种双向通信协议,在建立连接后,WebSocket服务器和Browser/UA都能主动的向对方发送或接收数据,就像Socket一样,不同的是WebSocket是一种建立在Web基础上的一种简单模拟Socket的协议;2.WebSocket需要通过握手连接,类似于TCP它也需要客户端和服务器端进行握手连接,连接成功后才能相互通信。简单的建立握手的时序图如下:
- \! U* U, Q- v5 W7 w4 V) U
8 R, ?2 F2 H% o, v! ^ O
握手过程:
7 c$ {* O% t: L. Y5 F, p+ CBrowser与WebSocket服务器通过TCP三次握手建立连接,如果这个建立连接失败,那么后面的过程就不会执行,Web应用程序将收到错误消息通知。
! l& h0 N; |! D7 H" r* C. E在TCP建立连接成功后,Browser/UA通过http协议传送WebSocket支持的版本号,协议的字版本号,原始地址,主机地址等等一些列字段给服务器端。
& g' d. ~; {- h* bWebSocket服务器收到Browser/UA发送来的握手请求后,如果数据包数据和格式正确,客户端和服务器端的协议版本号匹配等等,就接受本次握手连接,并给出相应的数据回复,同样回复的数据包也是采用http协议传输。
' v6 P9 [! R' B7 ZBrowser收到服务器回复的数据包后,如果数据包内容、格式都没有问题的话,就表示本次连接成功,触发onopen消息,此时Web开发者就可以在此时通过send接口想服务器发送数据。否则,握手连接失败,Web应用程序会收到onerror消息,并且能知道连接失败的原因。
5 p* k+ i7 @# l& K ?# |3 A' u/ g( j; M" y$ r! j
三、Tomcat 7中的Websocket架构9 O* t B# h' t, e/ Y2 o& G* D! j
7 q; ?7 C& D% {
如图所示,因为Websocket通信分为握手和数据传输两个过程,两个过程中需要用到的处理方式是不一样的,握手过程是基于HTTP 1.1基础上的,而数据传输是直接基于TCP的流传输。 ' S; H! ~. Q( m, x$ [1 |9 A. Z
握手过程中,在HttpServletRequest的基础上,封装了WsHttpServletRequest类,添加了对Request的失效操作函数invalidate()。而在数据通信时,接受和处理数据过程中,基于org.apache.coyote.http11.upgrade.UpgradeInbound重新封装了用于处理数据输入流的类StreamInbound,并在StreamInbound的基础上扩展生成了用于消息处理的类MessageInbound。在这两个数据处理类中均留有onData,onTextData/onBinaryData,onOpen,onClose等事件操作函数接口,这些接口将在载入的代码类中实现业务逻辑。在用于数据输出流的类WsOutbound则是封装了UpgradeOutbound对象实例,基于UpgradeOutbound对象的基础上,添加了websocket响应有关的处理逻辑。这里处理函数均为同步调用的函数,保证websocket响应的时序性。7 w, h! |* y5 i0 d/ y' O0 Z
Tomcat中Websocket的处理流程如下:
* A, f& I# E- }! @+ |, ~7 I8 P" l
接收客户端发来的握手请求,Coyote.http11连接器对socket进行解析,形成HttpServletRequest发送给Container。& |! v. @# I( B3 X1 t1 r
Container中的相应WebsocketServlet处理请求,如不接受连接请求,则返回,如接受连接请求,则对请求作出响应,建立起客户端和服务器的socket连接。
D( D! O9 S- U* r7 z& U服务器此时可以通过WsOutbound发送数据给客户端,同时通过StreamInbound监听socket。) u: m; I7 S% n: C' ~1 W
如果接收到客户端发来的数据,则将socket数据解析成frame,判断frame类型,通过事件分发数据到不同的逻辑处理流程。$ c$ e! T/ z k8 d
数据返回时调用WsOutbound对返回的数据进行封装处理,发送给客户端。) N6 R8 M* U; I2 c/ c$ H
: W, G" b u( ? X, C四、代码实现以及需求5 i0 r! G* O( L( L# y" Z
1 Z0 n5 ^, c" ~: N# B q6 `1、项目需要,定时向所有在线用户推送一个广告或是推送一个通知之类的(比如服务器升级,请保存好手头工作之类的)。, c# Y0 H& o$ A% ~7 q) m! r
8 s0 }4 z5 ]! C! U( ^# e) d2、相关环境 , Nginx、tomcat7、centos 6.5
5 Z5 p9 @+ }% d9 p# x4 h. r
- m. I: u5 [4 R% V; Z- Z3、项目框架,springMvc 4.0.6、layer4 J: \2 g E0 w( e; ~- E
: d/ n* G4 J9 |
4、代码实现:9 l7 _0 J6 n. u/ Z) W; k
/ R( S ~/ G h. C, g3 IWebSocketConfig:- import websocket.handler.SystemWebSocketHandler;* v# D0 R: w% i# B1 @
- @Configuration! r, U; Q, l. g: ^% x3 T
- @EnableWebMvc$ u9 N8 K. p" {* t1 H
- @EnableWebSocket
7 P3 f4 D5 S0 G1 P# ]; J& L" D" p, l - public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{
. O! A0 j; x* V; C - 3 N ]7 A) A3 j& Q$ v- d/ C0 \2 _
- @Override5 _0 M) M0 D! C# w( v( h5 F& h
- public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {* S% ?+ ~- `0 Q# {2 z6 i
- registry.addHandler(systemWebSocketHandler(),"/webSocketServer");
# y' V, J$ |7 G9 s8 j- l. o5 Z* p - registry.addHandler(systemWebSocketHandler(),"/sockjs/webSocketServer");: n0 _" R4 h; _; v; \- z
- }+ v& x5 `0 L7 I$ F$ s7 @
- @Bean, S# K# W1 r1 j! ^1 K5 V" S) P$ c( z
- public WebSocketHandler systemWebSocketHandler(){7 a6 e8 L, K2 l F: G7 i% s6 d
- return new SystemWebSocketHandler();
$ U! W* M6 }' w1 X - }7 |: k. O; M# z3 }, G/ ]0 e: \5 ~
- }
复制代码 SystemWebSocketHandler:! I# Z# w. C F2 M& c) \
- public class SystemWebSocketHandler extends TextWebSocketHandler {
( e$ {/ U8 v9 q# x. ^
( O9 Z) X" R$ M& t: ^
( p* ], k: {" a; M- a- private static final ArrayList<WebSocketSession> users = new ArrayList<WebSocketSession>();;
9 T$ t% a% `7 n; q9 O
; ^9 o+ k. F: N- x+ p! [8 D- public void afterConnectionEstablished(WebSocketSession session) throws Exception {
" n; k9 A* c" A0 ^2 @ - System.out.println("ConnectionEstablished");. i/ N9 H6 P+ q
- users.add(session);2 B: g( D+ `7 }" v+ p
- System.out.println("当前用户"+users.size());- y$ Q* {6 h' z8 `7 I
- }5 @! Q1 d1 T" W% J5 [" N" X
- /**
5 b5 e4 h* F) T% J2 h - * 在UI在用js调用websocket.send()时候,会调用该方法
* @( q6 b1 r7 S* y% q- g - * @Author 张志朋- A, ~+ Z. y7 z" a
- * @param session& m' P0 ]9 \3 M- T) u3 J+ |
- * @param message. [6 C, D, l' w( b! t& r
- * @throws Exception
- e. X: x, D' m2 v9 G, h - * @Date 2016年3月4日
! Q# ]4 t0 a4 I% R+ c. E8 N - * 更新日志; u. g/ B, _, D h5 B
- * 2016年3月4日 张志朋 首次创建- p; X2 }0 ?' n+ ]4 @9 p
- *
) L; i, o& f" X9 B# ~6 O - */
# P" W) p( b' u* j$ Q - @Override' c/ e9 n2 P; b* Q, |6 {- q
- protected void handleTextMessage(WebSocketSession session,
- O6 O/ x5 t( k& o4 M$ h - TextMessage message) throws Exception {
4 s8 C$ B6 I1 w" V" k6 W - super.handleTextMessage(session, message);
' n/ L! M/ Q1 s0 J }3 [0 R - sendMessageToUsers(session,message);
0 N% l" z' |* a2 Z - }
* O$ S$ i: a5 H" _- K0 W - @Override2 m, b8 d+ l. T- V8 ]7 Y2 H
- public void handleTransportError(WebSocketSession session, Throwable exception) throws IOException {9 k+ A# Y# s5 K' ?7 C% _( r. g
- if(session.isOpen()){: w: h4 n8 t% k; q$ s3 u
- session.close();
. r+ Y d$ i/ Y z3 @2 U& t' s - }) |0 U' V$ ~' q
- users.remove(session);
) j% t; a5 ?& c$ S - }7 A( U/ J: {0 z1 f8 G d
- - y4 T" E3 P4 m
- @Override
) v- H* J$ Y+ D, N. e6 ]; }. h - public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
% |" `1 b3 ]0 O6 d1 r - users.remove(session);+ M% H/ Z( W; G; r4 ^
- }& a! B9 Y+ n% D4 G- b( H7 @
5 x+ c, f/ v" T! a- @Override0 D7 Q/ A+ G, ~* z S% R( \4 R
- public boolean supportsPartialMessages() {4 q& N7 i( E$ c6 Q E
- return false;
1 F1 v) t% ?- n3 K" x - }0 w z9 a- w4 d( @- d! e+ I
- /**
* ], Z$ @( A9 A) u, U# k - * 给所有在线用户发送消息# s( @; I: V' p9 a' @
- * @Author 张志朋
, P: z0 a! g8 d" {$ h3 S. J. a) |2 e - * @param message void
- D6 Q8 y( m% d$ `/ a - * @Date 2016年3月4日
3 P! j, ^% [4 @' X: D+ T& H" N - * 更新日志+ }$ f0 G6 J/ X. M* G' F
- * 2016年3月4日 张志朋 首次创建. ~6 J' w5 F& n* B; ~* j% o
- *
+ B; _/ E7 C; c ^ - */0 k: `( x7 ?7 a9 ? n8 l. v
- public void sendMessageToUsers(WebSocketSession session,TextMessage message) {
9 k: _9 z5 ]+ }2 e& v4 A$ Y# N0 ~ - for (WebSocketSession user : users) {
% P8 z6 @, B1 E* t - try {
2 h: J/ m# h) J% l, g% f - if (user.isOpen()) {
5 E" u W/ ?9 ~8 X! P2 g - user.sendMessage(message);, O* W4 Z5 Y! l* {
- }) g% f. v7 r, n2 y7 ^. R' W3 L
- } catch (IOException e) {4 u* {) E% {, j0 u1 i
- e.printStackTrace();
" i4 q; e' O5 b: l9 L" v# z) w - }
8 x' v0 c9 s! g; B6 J+ F" ~ I - }
6 L% D( h) o: [( f) n" w9 x' J - }; G3 L5 ^: E9 G, X+ n0 w7 |
- }
8 f- h4 y1 M; p0 D
复制代码 信息输入 index.html:
1 M+ I' p" s6 H/ x- <html xmlns="http://www.w3.org/1999/xhtml">
; w3 P* o) d% F* D- ? - <head>. j4 ?2 ]& t O
- <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />) y. i- y' }* R0 a
- <title>请输入任意消息</title>. A6 ?, J# @8 Q4 E$ O+ | Q! X
- <script type="text/javascript" src="js/jquery-1.10.2.min.js"></script>
4 M. m' u2 C. K: R - <script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>4 e2 h7 m0 X( u0 k# _" r
- <script type="text/javascript">
; b5 |: P$ P1 k% `2 w - var ws = null;' k' p, @% n, G6 O. b+ @* C/ [
- $(function () {+ M# d0 H9 @+ k2 F
- if ('WebSocket' in window) {( a$ k4 k' y4 n5 W: t
- ws = new WebSocket('ws://127.0.0.1:8080/webSocketServer');
1 a {4 |& H& z. a% {' X - } 8 u* y: b$ i z7 c! b; Y
- else if ('MozWebSocket' in window) {
- j1 D7 _) P9 t4 G8 G - ws = new MozWebSocket("ws://127.0.0.1:8080/webSocketServer");
, ^* z1 U; \2 [ y D0 o# u - } 3 x% k8 c4 a8 {
- else {
) H' F6 o! P1 ^5 J - ws = new SockJS("ws://127.0.0.1:8080/webSocketServer");- D; b% q1 y7 |6 v& N) A
- }
" ]+ r, g0 P2 `! \( K3 z- x% b - ws.onopen = function () {
* U2 C9 e s: P. p - - F, f! b5 ^1 a7 F4 l1 y! |) p v, r! U
- };( ?: G. T2 w+ t& `6 z$ u5 J
- ws.onmessage = function (event) {
; l$ w6 a8 R6 S( _: X. Q; C! @5 k: }! a, L - 5 ?# N* A) @3 K0 \, |0 E
- };! v' h+ N7 D& h5 l( @) V3 L
- ws.onclose = function (event) {4 |# ?) g% `# O7 r0 K6 g3 ?
" j. r% R5 S5 T( `1 U% m1 m- };
; t0 w' K' i6 @! f, |' I - });
- q+ H5 K9 i0 X+ K7 ^, h9 J0 R4 X4 C7 J - function stop(){& m: T% [$ o0 c+ V; ~
- var message = $("#message").val();
; b+ u) x' J4 ~* ~2 c+ }; v - ws.send(message);
+ a( [, E' n" ]; Q# w7 Q8 W1 k/ s$ k - }7 ~4 L# d$ V! ? C+ S
- </script>
& k, e1 P; A+ k9 J, b1 l, p* X+ Y - </head>
! E- N! u! z6 J- U2 o" L3 o4 ^1 s0 X - <body class="keBody">! G% a0 u! P" n, X* `& Y
- 请输入提示信息: <textarea id="message"></textarea><br />
# v/ Q- ]) V6 r; m/ \ - <input type="button" value="开始" />% ^ J* J2 Y( Y) V6 m% Y
- </body># A H" F# ^& P3 ^1 ]
- </html>
复制代码 6 w, _, m5 N4 J5 F( U- @8 R) x% U
webSocket.js 用于导入项目。- document.write("<script language=javascript src='http://127.0.0.1:8080/js/jquery-1.10.2.min.js'></script>");
2 I. B6 H. @* @5 L/ N+ {& ]& H - document.write("<script language=javascript src='http://127.0.0.1:8080/layer/layer.js'></script>");. S& v( ^7 P7 ~( g8 V
- document.write("<script language=javascript src='http://cdn.sockjs.org/sockjs-0.3.min.js'></script>");
) G* _( k, u. Z7 d9 r - var ws = null;2 |) |) A5 q& N- D' g3 g! B$ Z
- var basePath = "ws://127.0.0.1:8080/";7 `/ E/ s$ E' [$ F
- if ('WebSocket' in window) {" N1 O2 u" U% G! g: u( I; p
- ws = new WebSocket(basePath+'webSocketServer');
& f$ e9 [( }$ c0 x0 |4 { - } 3 D; p3 Y; n0 x* K! p# i
- else if ('MozWebSocket' in window) {
$ J1 T7 i) y. T# J - ws = new MozWebSocket(basePath+"webSocketServer");
: M# v4 r# d' A - }
$ k( q& r( v* b9 A# k8 I - else {
+ C( X. U$ }2 p" c D: d/ u4 @9 i0 }5 V - ws = new SockJS(basePath+"sockjs/webSocketServer");
% y9 x) {5 P, q* D5 s& {8 z - }6 n- t3 h* T) ]
- ws.onopen = function () {: ^# K2 K& O2 d% j4 W8 O p" V6 y
- ! _/ i- ^0 D0 e7 Z# S3 T, @
- };
" O6 e5 F; U3 [) N8 A6 s - ws.onmessage = function (event) {, m4 T7 w8 |) c" f+ q
- pop(event.data);
) @$ S( B l; P7 @: K - };
6 Z1 y; o e8 @6 ?3 ~) M - ws.onclose = function (event) {
' Y0 m9 `$ d4 Z1 @$ j' G+ \2 p - ws.close();6 {8 E" [; a2 n2 g
- };: g1 a. q2 ^, P q7 I* g/ [
- //提示信息; R' o; `+ S" v" L' l8 {' V
- function pop(message){
6 M6 d; m# G o! ?1 ]0 c - layer.alert(message);
+ D) u) M2 \+ P% c# p - }
复制代码
) O) T0 r9 i$ u( U6 m- G5、在项目头部引入2 P: I4 Q. w& a& e; E6 b, O
<script language=javascript src='http://127.0.0.1:8080/webSocket.js '></script>5 k o. G" |9 u6 a" |
$ Y D6 y* J$ F) [
0 S% G3 n5 h2 j3 L9 u这时查看后台 会有以下信息 说明 引入成功。
+ k' m+ O/ r2 I Y, ~0 j1 n7 `
0 W, e# ^7 c; ~5 f
6 N4 h0 t, z/ i1 S* A
然后在打开页面 index.html 输入以下内容 点击开始即可。
: l! _# R6 `4 a* p3 u3 A/ _- U6 L
. \9 h! G; r! F" v3 Q" a( @# S
, T2 d, H$ M- [如果在网站出现一下提示说明配置成功,这时候所有网站登录用户都可以收到此信息。
* g! Q7 _/ Q3 E8 t+ ]1 Y. Q
( x) y J7 K$ x! D: Y0 s2 ]. I+ u0 D
6 @: O7 `) R( n" N% i. ~
l! R% R; g1 i# h项目下载地址:. V, L. Q- ]1 J, X' j, ^2 q0 Q) a' @: I
* q/ E' q" I; H8 E4 {; M* O% `
& P% [7 D" S7 Y# T
- }5 b0 ^8 M* p- F& q8 y! l
& h1 `! W& Q4 `- J! B |
|