博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
前端那些事之websoket篇
阅读量:6841 次
发布时间:2019-06-26

本文共 15565 字,大约阅读时间需要 51 分钟。

hot3.png

WebSocket协议概述

  • WebSocket protocol 是HTML5一种新的协议。它实现了浏览器与服务器全双工通信(full-duplex)。一开始的握手需要借助HTTP请求完成。 WebSocket是真正实现了全双工通信的服务器向客户端推的互联网技术。 它是一种在单个TCP连接上进行全双工通讯协议。Websocket通信协议与2011年倍IETF定为标准RFC 6455,Websocket API被W3C定为标准。

  • 全双工和单工的区别(了解):  全双工(Full Duplex)是通讯传输的一个术语。通信允许数据在两个方向上同时传输,它在能力上相当于两个单工通信方式的结合。全双工指可以同时(瞬时)进行信号的双向传输(A→B且B→A)。指A→B的同时B→A,是瞬时同步的。  单工、半双工(Half Duplex),所谓半双工就是指一个时间段内只有一个动作发生,举个简单例子,一条窄窄的马路,同时只能有一辆车通过,当目前有两辆车对开,这种情况下就只能一辆先过,等到头儿后另一辆再开,这个例子就形象的说明了半双工的原理。早期的对讲机、以及早期集线器等设备都是基于半双工的产品。随着技术的不断进步,半双工会逐渐退出历史舞台。

  • 推的技术和拉的技术(了解): 推送(PUSH)技术是一种建立在客户服务器上的机制,就是由服务器主动将信息发往客户端的技术。就像是广播电台播音。 同传统的拉(PULL)技术相比,最主要的区别在于推送(PUSH)技术是由服务器主动向客户机发送信息,而拉(PULL)技术则是由客户机主动请求信息。PUSH技术的优势在于信息的主动性和及时性。

  • 简单的说,相对于服务端:拉的技术是被动向客户端提供数据,推的技术是主动向客户端提供数据。

  • 互联网技术(了解): 互联网技术定义:互联网技术指在计算机技术的基础上开发建立的一种信息技术(直译:Internet Technology;简称:IT)。 该技术把互联网上分散的资源融为有机整体,实现资源的全面共享和有机协作,使人们能够透明地使用资源的整体能力并按需获取信息。

详细的通讯过程

  • 1)客户端发起http请求,附加头信息为:“Upgrade Websocket” 输入图片说明

输入图片说明

  • 2)服务端解析,并返回握手信息,从而建立连接 输入图片说明
  • 3)传输数据(双向) 输入图片说明 4)客户端或服务端主动断开连接。客户端主动断开:客户端发起http请求,请求断开连接,服务端收到消息后断开WebSocket连接;服务端主动断开:直接断开WebSocket连接,客户端的API会立刻得知。

客户端-浏览器的支持

WebSocket通信的客户端使用的是浏览器,客户端操作的API是HTML5中新增的API,使用这些API可以让客户端(浏览器)和服务端(服务器)进行全双工的通讯。 支持的浏览器如下: 浏览器类型 浏览器版本 Chrome Supported in version 4+ Firefox Supported in version 4+ Internet Explorer Supported in version 10+ Opera Supported in version 10+ Safari Supported in version 5+ 问题出现了,Html5 websocket兼容性还不是很好,不是所有的浏览器都支持这些新的API,特别是在IE10以下。 但幸运的是现在绝大多数主流的浏览器都支持这些API,即使不支持的哪些旧的浏览器,也有解决方案。如: 为了处理不同浏览器和浏览器版本的兼容性,spring webscoket基于SockJS protocol提供了一种解决兼容性的方法,在底层屏蔽兼容性的问题,提供统一的,透明的,可理解性的webscoket解决方案。 SockJS 是一个浏览器上运行的 JavaScript 库,如果浏览器不支持 WebSocket,该库可以模拟对 WebSocket 的支持,实现浏览器和 Web 服务器之间低延迟、全双工、跨域的通讯通道。

客户端的API

// 创建一个Socket实例(需要浏览器支持)ws:WebSocket协议地址开头var socket = new WebSocket('ws://localhost:8080'); //下面有几个回调函数,自动调用(什么时候调用?)// 打开Socket socket.onopen = function(event) { //握手成功后,会自动调用该函数  }  // 监听消息:用来获取服务端的消息  socket.onmessage = function(event) {     console.log('Client received a message',event);   };   // 监听Socket的关闭  socket.onclose = function(event) {     console.log('Client notified socket has closed',event);   };   // 关闭Socket....   //socket.close()

服务端的API

/** *  * 说明:WebScoket配置处理器 * 把处理器和拦截器注册到spring websocket中 * @author  * @version 1.0 * @date 2016年10月27日 */@Component("webSocketConfig")//配置开启WebSocket服务用来接收ws请求@EnableWebSocketpublic class WebSocketConfig implements WebSocketConfigurer {	//注入处理器	@Autowired	private ChatWebSocketHandler webSocketHandler;	@Autowired	private ChatHandshakeInterceptor chatHandshakeInterceptor;	public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {		//添加一个处理器还有定义处理器的处理路径		registry.addHandler(webSocketHandler, "/ws").addInterceptors(chatHandshakeInterceptor);		/*		 * 在这里我们用到.withSockJS(),SockJS是spring用来处理浏览器对websocket的兼容性,		 * 目前浏览器支持websocket还不是很好,特别是IE11以下.		 * SockJS能根据浏览器能否支持websocket来提供三种方式用于websocket请求,		 * 三种方式分别是 WebSocket, HTTP Streaming以及 HTTP Long Polling		 */		registry.addHandler(webSocketHandler, "/ws/sockjs").addInterceptors(chatHandshakeInterceptor).withSockJS();	}	}/** * websocket的链接建立是基于http握手协议,我们可以添加一个拦截器处理握手之前和握手之后过程 * @author BoBo * */@Componentpublic class ChatHandshakeInterceptor implements HandshakeInterceptor{	/**     * 握手之前,若返回false,则不建立链接     */	@Override	public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,			Map
attributes) throws Exception { if (request instanceof ServletServerHttpRequest) { ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request; HttpSession session = servletRequest.getServletRequest().getSession(false); //如果用户已经登录,允许聊天 if(session.getAttribute("loginUser")!=null){ //获取登录的用户 User loginUser=(User)session.getAttribute("loginUser") ; //将用户放入socket处理器的会话(WebSocketSession)中 attributes.put("loginUser", loginUser); System.out.println("Websocket:用户[ID:" + (loginUser.getId() + ",Name:"+loginUser.getNickname()+"]要建立连接")); }else{ //用户没有登录,拒绝聊天 //握手失败! System.out.println("--------------握手已失败..."); return false; } } System.out.println("--------------握手开始..."); return true; } /** * 握手之后 */ @Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { System.out.println("--------------握手成功啦..."); }}/** * * 说明:WebSocket处理器 * @author * @version 1.0 * @date 2016年10月27日 */@Component("chatWebSocketHandler")public class ChatWebSocketHandler implements WebSocketHandler { //在线用户的SOCKETsession(存储了所有的通信通道) public static final Map
USER_SOCKETSESSION_MAP; //存储所有的在线用户 static { USER_SOCKETSESSION_MAP = new HashMap
(); } /** * webscoket建立好链接之后的处理函数--连接建立后的准备工作 */ @Override public void afterConnectionEstablished(WebSocketSession webSocketSession) throws Exception { //将当前的连接的用户会话放入MAP,key是用户编号 User loginUser=(User) webSocketSession.getAttributes().get("loginUser"); USER_SOCKETSESSION_MAP.put(loginUser.getId(), webSocketSession); //群发消息告知大家 Message msg = new Message(); msg.setText("风骚的【"+loginUser.getNickname()+"】踩着轻盈的步伐来啦。。。大家欢迎!"); msg.setDate(new Date()); //获取所有在线的WebSocketSession对象集合 Set
> entrySet = USER_SOCKETSESSION_MAP.entrySet(); //将最新的所有的在线人列表放入消息对象的list集合中,用于页面显示 for (Entry
entry : entrySet) { msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser")); } //将消息转换为json TextMessage message = new TextMessage(GsonUtils.toJson(msg)); //群发消息 sendMessageToAll(message); } @Override /** * 客户端发送服务器的消息时的处理函数,在这里收到消息之后可以分发消息 */ //处理消息:当一个新的WebSocket到达的时候,会被调用(在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理) public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage
message) throws Exception { //如果消息没有任何内容,则直接返回 if(message.getPayloadLength()==0)return; //反序列化服务端收到的json消息 Message msg = GsonUtils.fromJson(message.getPayload().toString(), Message.class); msg.setDate(new Date()); //处理html的字符,转义: String text = msg.getText(); //转换为HTML转义字符表示 String htmlEscapeText = HtmlUtils.htmlEscape(text); msg.setText(htmlEscapeText); System.out.println("消息(可存数据库作为历史记录):"+message.getPayload().toString()); //判断是群发还是单发 if(msg.getTo()==null||msg.getTo().equals("-1")){ //群发 sendMessageToAll(new TextMessage(GsonUtils.toJson(msg))); }else{ //单发 sendMessageToUser(msg.getTo(), new TextMessage(GsonUtils.toJson(msg))); } } @Override /** * 消息传输过程中出现的异常处理函数 * 处理传输错误:处理由底层WebSocket消息传输过程中发生的异常 */ public void handleTransportError(WebSocketSession webSocketSession, Throwable exception) throws Exception { // 记录日志,准备关闭连接 System.out.println("Websocket异常断开:" + webSocketSession.getId() + "已经关闭"); //一旦发生异常,强制用户下线,关闭session if (webSocketSession.isOpen()) { webSocketSession.close(); } //群发消息告知大家 Message msg = new Message(); msg.setDate(new Date()); //获取异常的用户的会话中的用户编号 User loginUser=(User)webSocketSession.getAttributes().get("loginUser"); //获取所有的用户的会话 Set
> entrySet = USER_SOCKETSESSION_MAP.entrySet(); //并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。) for (Entry
entry : entrySet) { if(entry.getKey().equals(loginUser.getId())){ msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经退出。。。!"); //清除在线会话 USER_SOCKETSESSION_MAP.remove(entry.getKey()); //记录日志: System.out.println("Socket会话已经移除:用户ID" + entry.getKey()); break; } } //并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。) for (Entry
entry : entrySet) { msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser")); } TextMessage message = new TextMessage(GsonUtils.toJson(msg)); sendMessageToAll(message); } @Override /** * websocket链接关闭的回调 * 连接关闭后:一般是回收资源等 */ public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception { // 记录日志,准备关闭连接 System.out.println("Websocket正常断开:" + webSocketSession.getId() + "已经关闭"); //群发消息告知大家 Message msg = new Message(); msg.setDate(new Date()); //获取异常的用户的会话中的用户编号 User loginUser=(User)webSocketSession.getAttributes().get("loginUser"); Set
> entrySet = USER_SOCKETSESSION_MAP.entrySet(); //并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。) for (Entry
entry : entrySet) { if(entry.getKey().equals(loginUser.getId())){ //群发消息告知大家 msg.setText("万众瞩目的【"+loginUser.getNickname()+"】已经有事先走了,大家继续聊..."); //清除在线会话 USER_SOCKETSESSION_MAP.remove(entry.getKey()); //记录日志: System.out.println("Socket会话已经移除:用户ID" + entry.getKey()); break; } } //并查找出在线用户的WebSocketSession(会话),将其移除(不再对其发消息了。。) for (Entry
entry : entrySet) { msg.getUserList().add((User)entry.getValue().getAttributes().get("loginUser")); } TextMessage message = new TextMessage(GsonUtils.toJson(msg)); sendMessageToAll(message); } @Override /** * 是否支持处理拆分消息,返回true返回拆分消息 */ //是否支持部分消息:如果设置为true,那么一个大的或未知尺寸的消息将会被分割,并会收到多次消息(会通过多次调用方法handleMessage(WebSocketSession, WebSocketMessage). ) //如果分为多条消息,那么可以通过一个api:org.springframework.web.socket.WebSocketMessage.isLast() 是否是某条消息的最后一部分。 //默认一般为false,消息不分割 public boolean supportsPartialMessages() { return false; } /** * * 说明:给某个人发信息 * @param id * @param message * @author 传智.BoBo老师 * @throws IOException * @time:2016年10月27日 下午10:40:52 */ private void sendMessageToUser(String id, TextMessage message) throws IOException{ //获取到要接收消息的用户的session WebSocketSession webSocketSession = USER_SOCKETSESSION_MAP.get(id); if (webSocketSession != null && webSocketSession.isOpen()) { //发送消息 webSocketSession.sendMessage(message); } } /** * * 说明:群发信息:给所有在线用户发送消息 * @author 传智.BoBo老师 * @time:2016年10月27日 下午10:40:07 */ private void sendMessageToAll(final TextMessage message){ //对用户发送的消息内容进行转义 //获取到所有在线用户的SocketSession对象 Set
> entrySet = USER_SOCKETSESSION_MAP.entrySet(); for (Entry
entry : entrySet) { //某用户的WebSocketSession final WebSocketSession webSocketSession = entry.getValue(); //判断连接是否仍然打开的 if(webSocketSession.isOpen()){ //开启多线程发送消息(效率高) new Thread(new Runnable() { public void run() { try { if (webSocketSession.isOpen()) { webSocketSession.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } }).start(); } } } }

一个简单的demo

var path = '<%=basePath%>';		var uid='${sessionScope.loginUser.id}';	//发送人编号	var from='${sessionScope.loginUser.id}';	var fromName='${sessionScope.loginUser.nickname}';	//接收人编号	var to="-1";		// 创建一个Socket实例	//参数为URL,ws表示WebSocket协议。onopen、onclose和onmessage方法把事件连接到Socket实例上。每个方法都提供了一个事件,以表示Socket的状态。	var websocket;	//不同浏览器的WebSocket对象类型不同	//alert("ws://" + path + "/ws?uid="+uid);	if ('WebSocket' in window) {		websocket = new WebSocket("ws://" + path + "ws");		console.log("=============WebSocket");		//火狐	} else if ('MozWebSocket' in window) {		websocket = new MozWebSocket("ws://" + path + "ws");		console.log("=============MozWebSocket");	} else {		websocket = new SockJS("http://" + path + "ws/sockjs");		console.log("=============SockJS");	}		console.log("ws://" + path + "ws");		//打开Socket,	websocket.onopen = function(event) { 		console.log("WebSocket:已连接");	}		// 监听消息	//onmessage事件提供了一个data属性,它可以包含消息的Body部分。消息的Body部分必须是一个字符串,可以进行序列化/反序列化操作,以便传递更多的数据。	websocket.onmessage = function(event) { 		console.log('Client received a message',event);		//var data=JSON.parse(event.data);		var data=$.parseJSON(event.data);		console.log("WebSocket:收到一条消息",data);				//2种推送的消息		//1.用户聊天信息:发送消息触发		//2.系统消息:登录和退出触发				//判断是否是欢迎消息(没用户编号的就是欢迎消息)		if(data.from==undefined||data.from==null||data.from==""){			//===系统消息			$("#contentUl").append("
  • "+data.date+"系统消息:"+data.text+"
  • "); //刷新在线用户列表 $("#chatOnline").html("在线用户("+data.userList.length+")人"); $("#chatUserList").empty(); $(data.userList).each(function(){ $("#chatUserList").append("
  • "+this.nickname+"
  • "); }); }else{ //===普通消息 //处理一下个人信息的显示: if(data.fromName==fromName){ data.fromName="我"; $("#contentUl").append("
  • "+data.fromName+""+data.text+""+data.date+"
  • "); }else{ $("#contentUl").append("
  • "+data.date+""+data.fromName+""+data.text+"
  • "); } } scrollToBottom(); }; // 监听WebSocket的关闭 websocket.onclose = function(event) { $("#contentUl").append("
  • "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"系统消息:连接已断开!
  • "); scrollToBottom(); console.log("WebSocket:已关闭:Client notified socket has closed",event); }; //监听异常 websocket.onerror = function(event) { $("#contentUl").append("
  • "+new Date().Format("yyyy-MM-dd hh:mm:ss")+"系统消息:连接异常,建议重新登录
  • "); scrollToBottom(); console.log("WebSocket:发生错误 ",event); }; //onload初始化 $(function(){ //发送消息 $("#sendBtn").on("click",function(){ sendMsg(); }); //给退出聊天绑定事件 $("#exitBtn").on("click",function(){ closeWebsocket(); location.href="${pageContext.request.contextPath}/index.jsp"; }); //给输入框绑定事件 $("#msg").on("keydown",function(event){ keySend(event); }); //初始化时如果有消息,则滚动条到最下面: scrollToBottom(); }); //使用ctrl+回车快捷键发送消息 function keySend(e) { var theEvent = window.event || e; var code = theEvent.keyCode || theEvent.which; if (theEvent.ctrlKey && code == 13) { var msg=$("#msg"); if (msg.innerHTML == "") { msg.focus(); return false; } sendMsg(); } } //发送消息 function sendMsg(){ //对象为空了 if(websocket==undefined||websocket==null){ //alert('WebSocket connection not established, please connect.'); alert('您的连接已经丢失,请退出聊天重新进入'); return; } //获取用户要发送的消息内容 var msg=$("#msg").val(); if(msg==""){ return; }else{ var data={}; data["from"]=from; data["fromName"]=fromName; data["to"]=to; data["text"]=msg; //发送消息 websocket.send(JSON.stringify(data)); //发送完消息,清空输入框 $("#msg").val(""); } } //关闭Websocket连接 function closeWebsocket(){ if (websocket != null) { websocket.close(); websocket = null; } } //div滚动条(scrollbar)保持在最底部 function scrollToBottom(){ //var div = document.getElementById('chatCon'); var div = document.getElementById('up'); div.scrollTop = div.scrollHeight; } //格式化日期 Date.prototype.Format = function (fmt) { //author: meizz var o = { "M+": this.getMonth() + 1, //月份 "d+": this.getDate(), //日 "h+": this.getHours(), //小时 "m+": this.getMinutes(), //分 "s+": this.getSeconds(), //秒 "q+": Math.floor((this.getMonth() + 3) / 3), //季度 "S": this.getMilliseconds() //毫秒 }; if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length)); for (var k in o) if (new RegExp("(" + k + ")").test(fmt)) fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length))); return fmt; }

    聊天室系统

    当前登录用户:${sessionScope.loginUser!=null?sessionScope.loginUser.nickname:"请登录" }   

    在线用户(0人)

    demo2

    var loginUserID = 1;    var webSocketProtocol = ('https:' == document.location.protocol) ? "wss" : "ws";    var url = webSocketProtocol + "://" + window.location.host + ":8888/csm/v2.0/message/" + loginUserID + "/";    // var url = webSocketProtocol + "://" + window.location.host + "/csm/v2.0/message/" + loginUserID + "/";    console.log(url);    var socket = new WebSocket(url);    console.log(socket);    // handle the message    socket.onmessage = function (e) {        console.log(e.data);        var item = JSON.parse(e.data);        $("#dNotification").append(renderAlertItem(item));    }

    转载于:https://my.oschina.net/yongxinke/blog/897844

    你可能感兴趣的文章
    C++ Exercises(十八)
    查看>>
    21.5. 流量控制
    查看>>
    WSRP调用中的一些问题
    查看>>
    Android 正则表达式
    查看>>
    5.22. Spring boot with Cache
    查看>>
    [裴礼文数学分析中的典型问题与方法习题参考解答]4.3.13
    查看>>
    string Join
    查看>>
    布线须知:机柜在数据中心机房的三个新用途
    查看>>
    迁移到云:渐进但不可逆转
    查看>>
    Patchwork间谍组织将目标扩大至政府
    查看>>
    工业物联网为“两化融合”带来巨大推力
    查看>>
    《UNIXLinux程序设计教程》一3.7 非阻塞I/O
    查看>>
    IBM遭标普下调评级
    查看>>
    手机短信验证码真的安全吗?
    查看>>
    关于智慧城市建设的几点建议
    查看>>
    Facebook高管:我们是科技公司 不是媒体公司
    查看>>
    《领域特定语言》一2.3DSL的问题
    查看>>
    TensorFlow 1.0 正式发布 你需要知道的都在这里
    查看>>
    空调能窃听插座能放火?物联网成了“危”联网
    查看>>
    视频监控日常使用存在哪些故障
    查看>>