标签:
一、WebSocket简介
WebSocket protocol是HTML5一种新的协议,WebSocket 是目前唯一真正实现全双工通信的服务器向客户端推送的互联网技术。WebSocket的出现使得浏览器提供对Socket的支持成为可能,从而在浏览器和服务器之间提供了一个基于 TCP 连接的双向通道。
HTML5 WebSocket 设计出来的目的就是要取代轮询和 Comet 技术,使客户端浏览器具备像 C/S 架构下桌面系统的实时通讯能力。 浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及 Comet 技术比较,具有很大的性能优势。
二、WebSocket出现之前实时web应用
Web 应用的信息交互过程通常是客户端通过浏览器发出一个请求,服务器端接收和审核完请求后进行处理并返回结果给客户端,然后客户端浏览器将信息呈现出来,这种 机制对于信息变化不是特别频繁的应用尚能相安无事,但是对于那些实时要求比较高的应用来说,比如说在线游戏、在线证券、设备监控、新闻在线播报、RSS 订阅推送等等,当客户端浏览器准备呈现这些信息的时候,这些信息在服务器端可能已经过时了。所以保持客户端和服务器端的信息同步是实时 Web 应用的关键要素,对 Web 开发人员来说也是一个难题。在 WebSocket 规范出来之前,开发人员想实现这些实时的 Web 应用,不得不采用一些折衷的方案,其中最常用的就是轮询 (Polling) 和 Comet 技术,而 Comet 技术实际上是轮询技术的改进,又可细分为两种实现方式,一种是长轮询机制,一种称为流技术
轮询:
这是最早的一种实现实时 Web 应用的方案。客户端以一定的时间间隔向服务端发出请求,以频繁请求的方式来保持客户端和服务器端的同步。这种同步方案的最大问题是,当客户端以固定频率向 服务器发起请求的时候,服务器端的数据可能并没有更新,这样会带来很多无谓的网络传输,所以这是一种非常低效的实时方案。
长轮询:
长轮询是对定时轮询的改进和提高,目地是为了降低无效的网络传输。当服务器端没有数据更新的时候,连接会保持一段时间周期直到数据或状态改变或者时间过期,通过这种机制来减少无效的客户端和服务器间的交互。当然,如果服务端的数据变更非常频繁的话,这种机制和定时轮询比较起来没有本质上的性能的提高。
流:
流技术方案通常就是在客户端的页面使用一个隐藏的窗口向服务端发出一个长连接的请求。服务器端接到这个请求后作出回应并不断更新连接状态以保证客户 端和服务器端的连接不过期。通过这种机制可以将服务器端的信息源源不断地推向客户端。这种机制在用户体验上有一点问题,需要针对不同的浏览器设计不同的方 案来改进用户体验,同时这种机制在并发比较大的情况下,对服务器端的资源是一个极大的考验。
综合这几种方案,您会发现这些目前我们所使用的 所谓的实时技术并不是真正的实时技术,它们只是在用 Ajax 方式来模拟实时的效果,在每次客户端和服务器端交互的时候都是一次 HTTP 的请求和应答的过程,而每一次的 HTTP 请求和应答都带有完整的 HTTP 头信息,这就增加了每次传输的数据量,而且这些方案中客户端和服务器端的编程实现都比较复杂,在实际的应用中,为了模拟比较真实的实时效果,开发人员往往 需要构造两个 HTTP 连接来模拟客户端和服务器之间的双向通讯,一个连接用来处理客户端到服务器端的数据传输,一个连接用来处理服务器端到客户端的数据传输,这不可避免地增加了编程实现的复杂度,也增加了服务器端的负载,制约了应用系统的扩展性。
三、WebSocket规范
WebSocket 协议本质上是一个基于 TCP 的协议。为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息”Upgrade: WebSocket”表 明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
客户端到服务端:
GET /demo HTTP/1.1
Host: example.com
Connection: Upgrade
Sec-WebSocket-Key2: 12998 5 Y3 1 .P00
Upgrade: WebSocket
Sec-WebSocket-Key1: 4@1 46546xW%0l 1 5
Origin: http://example.com
[8-byte security key]
服务端到客户端:
HTTP/1.1 101 WebSocket Protocol Handshake
Upgrade: WebSocket
Connection: Upgrade
WebSocket-Origin: http://example.com
WebSocket-Location: ws://example.com/demo
[16-byte hash response]
”Upgrade:WebSocket”表示这是一个特殊的 HTTP 请求,请求的目的就是要将客户端和服务器端的通讯协议从 HTTP 协议升级到 WebSocket 协议。从客户端到服务器端请求的信息里包含有”Sec-WebSocket-Key1”、“Sec-WebSocket-Key2”和”[8-byte securitykey]”这样的头信息。这是客户端浏览器需要向服务器端提供的握手信息,服务器端解析这些头信息,并在握手的过程中依据这些信息生成一 个 16 位的安全密钥并返回给客户端,以表明服务器端获取了客户端的请求,同意创建 WebSocket 连接。一旦连接建立,客户端和服务器端就可以通过这个通道双向传输数据了。
摘抄自
http://www.ibm.com/developerworks/cn/web/1112_huangxa_websocket/
四、Websocket实现:
Tomcat7.0.5以后开始支持websocket。下面根据tomcat7.0.63+jdk1.7.0.79构建了一个小例子
(必须配置jdk环境变量)
第一种方式,继承org.apache.catalina.WebSocketServlet,该方法在tomcat7.0.63中已过时。
这种方式没有做demo,可以参考下面这篇文章
http://www.alixixi.com/web/a/2014032492868.shtml
第二种方式:注解方式实现
客户端:
<script type="text/javascript"> var socket = null; $(function(){ //建立websocket连接 socket = new WebSocket("ws://"+window.location.host+"${pageContext.request.contextPath}/mySocket"); //打开websocket时触发 socket.onopen = function(){ $("#showMsg").append("连接成功..<br/>"); console.log("websocket open"); } //服务端有消息推送到客户端时触发 socket.onmessage = function(message){ console.log(message.data); var dataJson = JSON.parse(message.data); } //websocket关闭时触发 socket.onclose = function(event){ console.log("websocket close"); } //websocket出错时触发 socket.onerror = function(event){ socket.close(); console.log("websocket error"); } $("#sendButton").click(function(){ //通过websocket对象的send方法发送数据到服务端,该方法不能直接传送json对象,//可以先将json对象转换成json格式字符串发送给服务端 var obj = { message:’客户端发送消息, type:1 }; //或者使用JSON.stringify(obj);如果js报错找不到该方法,可以自定义一个简单的//jquery插件,功能就是将简单json对象转换成json格式字符串 socket.send(obj.toJSONString()); //socket.send($.simpleJsonToString(obj)); }); }) </script">
$.extend({ simpleJsonToString : function(jsonObj){ var jsonStr = “”; for(var I in jsonObj){ jsonStr = jsonStr+i+”:’”+jsonObj[i]+”’”; } jsonStr = jsonStr.substring(0,jsonStr.length-1); jsonStr = jsonStr+”}”; return jsonStr; } });
服务端:
package com.h3c.socket; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpSession; import javax.websocket.CloseReason; import javax.websocket.EndpointConfig; import javax.websocket.OnClose; import javax.websocket.OnError; import javax.websocket.OnMessage; import javax.websocket.OnOpen; import javax.websocket.Session; import javax.websocket.server.ServerEndpoint; import com.google.gson.Gson; import net.sf.json.JSONObject; //value为访问websocket的路径,configurator为websocket初始化配//置器 @ServerEndpoint(value="/mySocket",configurator=HttpSessionConfigurator.class) public class MySocket { static Map<String,Session> sessionMap = new HashMap<String, Session>(); private HttpSession httpSession; @OnOpen public void onOpen(Session session,EndpointConfig config){ System.out.println("websocket open"); this.httpSession = (HttpSession) config.getUserProperties().get("httpSession"); sessionMap.put((String) httpSession.getAttribute("userAccount"),session); } @OnMessage public void onMessage(String msg,Session session) throws IOException{ //将客户端发送的json格式字符串转换成实体对象,客户端徐传送json格式数据 //需要jar包:json-lib-2.4-jdk15.jar JSONObject jsonObject = JSONObject.fromObject(msg); transferClassMessage transfer = (transferClassMessage) jsonObject.toBean(jsonObject, transferClassMessage.class); Gson gson = new Gson(); Set<Map.Entry<String, Session>> set = sessionMap.entrySet(); for(Map.Entry<String,Session> sessionEntry:set){ //向指定客户端发送消息 if("fw1778".equals(sessionEntry.getKey())){ sessionEntry.getValue().getBasicRemote().sendText(gson.toJson("服务端返回消息")); } } } @OnClose public void onClose(Session session,CloseReason closeReason){ sessionMap.remove(session.getId()); System.out.println("websocket close"); } @OnError public void error(Session session,Throwable ta){ sessionMap.remove(session.getId()); System.out.println(ta); } }
Websocket通过javax.websocket.Session向客户端发送数据,
package com.h3c.socket; import javax.servlet.http.HttpSession; import javax.websocket.HandshakeResponse; import javax.websocket.server.HandshakeRequest; import javax.websocket.server.ServerEndpointConfig; public class HttpSessionConfigurator extends ServerEndpointConfig.Configurator{ @Override public void modifyHandshake(ServerEndpointConfig config, HandshakeRequest request, HandshakeResponse response) { HttpSession httpSession = (HttpSession)request.getHttpSession(); if(null!=httpSession){ config.getUserProperties().put("httpSession", httpSession); } } }
四、Websocket与spring整合(spring版本为4.0或者以上)
第一种,配置方式。
http://www.tuicool.com/articles/QvUjUf
第二种:注解方式
Spring相关配置文件(略)
Spring websocket配置类
package com.h3c.itac.websocket; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; /** * spring websocket配置类,在tomcat启动时会调用该类注册websockethandlers的方法注册handler * @author lfw1950 */ @Configuration @EnableWebMvc @EnableWebSocket public class TranferWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ //注册handler,可以注册多个handler @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { /** * addHandler方法第一个参数为处理websocket的handler,第二个参数为访问路径,addInterceptors方法徐传入一个handshake拦截器 * 该拦截器会在建立wensocket连接之前执行 */ registry.addHandler(new TransferWebSocketHandler(), "/transfer").addInterceptors(new TransferHandshakeInterceptor()); } }
Spring websocket拦截器
package com.h3c.itac.websocket; import java.util.Map; import javax.servlet.http.HttpSession; import org.springframework.http.server.ServerHttpRequest; import org.springframework.http.server.ServerHttpResponse; import org.springframework.http.server.ServletServerHttpRequest; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.server.HandshakeInterceptor; public class TransferHandshakeInterceptor implements HandshakeInterceptor{ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Exception e) { System.out.println("after handshake"); } @Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler handler, Map<String, Object> attr) throws Exception { System.out.println("before handshake"); if(request instanceof ServletServerHttpRequest){ ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession httpSession = servletRequest.getServletRequest().getSession(); if(null!=httpSession){ String userName = (String) httpSession.getAttribute("userAccount"); /** * map中存放的数据可以通过session.getAttributes().get(key)获取 * 如:session.getAttributes().get("wsUserAccount") */ attr.put("wsUserAccount", userName); } } /** * 默认为false,要修改为true,否则无法建立websocket连接 * WebSocket connection to ‘ws://localhost:8888/ITAC_Cloud/transfer‘ failed: Error during WebSocket handshake: Unexpected response code: 200 */ return true; } }
Spring websocket处理器
package com.h3c.itac.websocket; import java.util.HashMap; import java.util.Map; import java.util.Set; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketMessage; import org.springframework.web.socket.WebSocketSession; public class TransferWebSocketHandler implements WebSocketHandler{ private Map<String, WebSocketSession> wsSessions = new HashMap<String, WebSocketSession>(); @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus arg1) throws Exception { System.out.println("websocket close"); wsSessions.remove(session.getAttributes().get("wsUserAccount")); } /** * 打开连接时执行 */ @Override public void afterConnectionEstablished(WebSocketSession session) throws Exception { System.out.println("websocket open"); wsSessions.put((String) session.getAttributes().get("wsUserAccount"), session); } @Override public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception { String receiveData = (String) message.getPayload();//从客户端接收到的数据 Set<Map.Entry<String, WebSocketSession>> set = wsSessions.entrySet(); for(Map.Entry<String,WebSocketSession> sessionEntry:set){ /** * 模拟交接班中被交接的坐席的域账号为fw1950,则只有登陆的账号为fw1950的客户端会收到服务端传送过去的数据 */ if("fw1950".equals(sessionEntry.getKey())){ sessionEntry.getValue().sendMessage(new TextMessage("服务端返回数据")); } } } @Override public void handleTransportError(WebSocketSession session, Throwable e) throws Exception { System.out.println("websocket error"); wsSessions.put((String) session.getAttributes().get("wsUserAccount"), session); } /** * 暂时不清楚该方法有什么作用 */ @Override public boolean supportsPartialMessages() { return false; } }
页面和普通的websocket实现基本一样
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> <script type="text/javascript" src="${pageContext.request.contextPath }/resources/js/jquery-1.11.2.js "></script> </head> <body> <div id="showMsg" style="border: :1px solid;width: 500px;height: 400px;overflow: auto;"></div> <div> <input type="text" id="msg"> <input type="button" id="sendButton" value="send"/> </div> <div id="toNext" title="接班详情" style="display: none;"> </div> </body> <script type="text/javascript"> var socket = null; $(function(){ socket = new WebSocket("ws://"+window.location.host+"${pageContext.request.contextPath}/transfer"); socket.onopen = function(){ $("#showMsg").append("连接成功..<br/>"); console.log("websocket open"); } socket.onmessage = function(message){ console.log(message.data); } socket.onclose = function(event){ console.log("websocket close"); } socket.onerror = function(event){ socket.close(); console.log("websocket error"); } $("#sendButton").click(function(){ socket.send("客户端发送消息"); }); }) </script> </html>
可以参考:
http://my.oschina.net/ldl123292/blog/304360
标签:
原文地址:http://www.cnblogs.com/qq931399960/p/4730493.html