websocket总结
一、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: }; //或者使用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(,jsonStr.length-); 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配置出错,可能会出现handshake的错误,切勿忘记配置spring的自动扫描:<context:component-scan base-package="com.h3c.itac" />
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
websocket总结的更多相关文章
- 漫扯:从polling到Websocket
Http被设计成了一个单向的通信的协议,即客户端发起一个request,然后服务器回应一个response.这让服务器很为恼火:我特么才是老大,我居然不能给小弟发消息... 轮询 老大发火了,小弟们自 ...
- 细说WebSocket - Node篇
在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- WebSocket - ( 一.概述 )
说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...
- php+websocket搭建简易聊天室实践
1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...
- Demo源码放送:打通B/S与C/S !让HTML5 WebSocket与.NET Socket公用同一个服务端!
随着HTML5 WebSocket技术的日益成熟与普及,我们可以借助WebSocket来更加方便地打通BS与CS -- 因为B/S中的WebSocket可以直接连接到C/S的服务端,并进行双向通信.如 ...
- Cowboy 开源 WebSocket 网络库
Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...
- 借助Nodejs探究WebSocket
文章导读: 一.概述-what's WebSocket? 二.运行在浏览器中的WebSocket客户端+使用ws模块搭建的简单服务器 三.Node中的WebSocket 四.socket.io 五.扩 ...
- 细说websocket - php篇
下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket ...
- webSocket and LKDBHelper的使用说明
socketket与lkdbhelper来处理数据 客户需求: 当我们有需要从自己的后台推送消息给我们的用户时,用户需要实时的接收到来自我们的推送消息.前提是没有使用第三方的推送框架,那么这个使用we ...
随机推荐
- 如何修改 SQL Server 代理主作业 (Transact-SQL)
本主题介绍了如何使用存储过程修改 Microsoft SQL Server 代理主作业. 更改作业定义计划的详细信息 1. 执行 sp_update_schedule. 在作业中添加.更改 ...
- Android学习系列(40)--Android主题和样式之系统篇(下)
11)Widget样式(Widget Style) 特别说明,此处定义大量的系统内置控件的样式,对于重写原生控件的样式具有很大的参考价值. <!-- Widget styles --> & ...
- hdu1248完全背包
不死族的巫妖王发工资拉,死亡骑士拿到一张N元的钞票(记住,只有一张钞票),为了防止自己在战斗中频繁的死掉,他决定给自己买一些道具,于是他来到了地精商店前.死亡骑士:"我要买道具!" ...
- Matlab 查看变量信息who whos命令
- Javascript参考手册
---------------------------------------------------------------------------------------------------- ...
- wordpress安装步骤
步骤1.因为安装Wordpress需要用到Apache和Mysql数据库,可以选择单独安装这两个软件,但配置参数设置起来可能会遇到一些困扰,建议大家下载现成的PHP和Mysql的集成安装包,比如XAM ...
- BZOJ3764 : Petya的序列
首先如果一段连续子序列里没有任何幸运数,那么显然可以缩成一个点. 设幸运数个数为$m$,那么现在序列长度是$O(m)$的,考虑暴力枚举$R_1$,然后从右往左枚举$L_1$. 每次碰到一个幸运数,就将 ...
- float了的元素和内联元素不支持margin:auto
float了的元素和内联元素不支持margin:auto
- ACM 心急的C小加
心急的C小加 时间限制:1000 ms | 内存限制:65535 KB 难度:4 描述 C小加有一些木棒,它们的长度和质量都已经知道,需要一个机器处理这些木棒,机器开启的时候需要耗费一个单位的 ...
- 51Nod 1002 数字三角形 Label:水水水 && 非学习区警告
一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值. 每次只能走到下一层相邻的数上,例如从第3层的6向下走,只能走到第4层的2或9上. 5 8 4 3 6 9 7 ...