WebSocket从入门到实战
文章目录
WebSocket
WebSocket 介绍
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双向通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
WebSocket API
WebSocket 对象
创建 WebSocket 对象:
var socket = new WebSocket(url, [protocol] );
WebSocket属性
WebSocket对象属性如下:
属性 | 描述 |
socket.readyState | 只读属性 readyState 表示连接状态,可以是以下值: 0 - 表示连接尚未建立。 1 - 表示连接已建立,可以进行通信。 2 - 表示连接正在进行关闭。 3 - 表示连接已经关闭或者连接不能打开。 |
socket.bufferedAmount | 只读属性 bufferedAmount 已被 send() 放入正在队列中等待传输,但是还没有发出的 UTF-8 文本字节数。 |
WebSocket事件:
事件 | 事件处理程序 | 描述 |
open | socket.onopen | 连接建立时触发 |
message | socket.onmessage | 客户端接收服务端数据时触发 |
error | socket.onerror | 通信发生错误时触发 |
close | socket.onclose | 连接关闭时触发 |
WebSocket方法:
方法 | 描述 |
socket.send() | 使用连接发送数据 |
socket.close() | 关闭连接 |
WebSocket 实例
客户端
WebSocket 协议本质上是一个基于 TCP 的协议。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息“Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
我们按照上面的API实现客户端代码如下:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>websocket</title> <!-- <script src="jquery-3.2.1.min.js"></script>--> <script src="https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js"></script> <script> var socket; if(typeof(WebSocket) == "undefined") { console.log("您的浏览器不支持WebSocket"); }else{ console.log("您的浏览器支持WebSocket"); //实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接 socket = new WebSocket("ws://localhost:18085/socket/zhangsan"); //打开事件 socket.onopen = function() { console.log("Socket 已打开"); }; //获得消息事件 socket.onmessage = function(msg) { console.log(msg.data); //发现消息进入 调后台获取 //getCallingList(); $("#msg").append(msg.data+"</br>"); }; //关闭事件 socket.onclose = function() { console.log("Socket已关闭"); }; //发生了错误事件 socket.onerror = function() { alert("Socket发生了错误"); } //关闭连接 function closeWebSocket() { socket.close(); } //发送消息 function send() { var message = document.getElementById('text').value; socket.send(message); } } </script> </head> <body> <div id="msg"></div> </body> </html>
服务端
工程结构如下:
1)引入依赖包
在工程中引入如下依赖:
<!--websocket--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
2)websocket消息处理
在服务端,我们可以创建一个类 WebSocketServer,并暴露websocket地址出去,代码如下:
package com.yyl.wsdemo.websocket; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CopyOnWriteArraySet; @Slf4j @ServerEndpoint(value = "/socket/{userid}") @Component public class WebSocketServer { //concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketServer对象。 private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private static Map<String, Session> sessions=new HashMap<String,Session>(); //用户唯一标识符 private String userid; /** * 连接建立成功调用的方法*/ @OnOpen public void onOpen(@PathParam("userid") String userid, Session session) { sessions.put(userid,session); this.userid=userid; webSocketSet.add(this); //加入set中 try { sendMessage("连接成功"); } catch (IOException e) { log.error("websocket IO异常"); } } /** * 连接关闭调用的方法 */ @OnClose public void onClose() { webSocketSet.remove(this); //从set中删除 sessions.remove(userid); //移除会话 } /** * 收到客户端消息后调用的方法 * * @param message 客户端发送过来的消息*/ @OnMessage public void onMessage(String message) { log.info("来自客户端的消息:" + message); //群发消息 for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { e.printStackTrace(); } } } /** * 发生异常处理方法 * @param session * @param error */ @OnError public void onError(Session session, Throwable error) { error.printStackTrace(); } /*** * 群发 * @param message * @throws IOException */ public void sendMessage(String message) throws IOException { for (Map.Entry<String, Session> sessionEntry : sessions.entrySet()) { sessionEntry.getValue().getBasicRemote().sendText(message); } } /*** * 给指定用户发送消息 * @param message * @param userid * @throws IOException */ public void sendMessage(String message,String userid) throws IOException { System.out.println("================="); sessions.get(userid).getBasicRemote().sendText(message); } /** * 群发自定义消息 * */ public static void sendInfo(String message) throws IOException { for (WebSocketServer item : webSocketSet) { try { item.sendMessage(message); } catch (IOException e) { continue; } } } }
我们编写一个测试类 WebSocketController,用于实现给指定用户发消息,代码如下:
package com.yyl.wsdemo.controller; import com.yyl.wsdemo.utils.Result; import com.yyl.wsdemo.utils.StatusCode; import com.yyl.wsdemo.websocket.WebSocketServer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.io.IOException; @RestController @RequestMapping(value = "/ws") public class WebSocketController { @Autowired private WebSocketServer webSocketServer; /*** * 模拟给指定用户发消息 */ @GetMapping(value = "/send/{userid}") public Result sendMessage(@PathVariable(value = "userid")String userid, String msg) throws IOException, IOException { webSocketServer.sendMessage(msg,userid); return new Result(true, StatusCode.OK,"发送消息成功@"); } }
测试效果如下:
代码链接
全部代码如下:https://download.csdn.net/download/weixin_45525272/87399321
WebSocket 应用场景
总结:适用于高即时性服务,比如聊天室的群聊,server顺序收到了张三,李四的消息,立即就推送给王五,不能让王五等半天。虽然Ajax也可以一秒一刷,让王五去问张三说话没,如果张三10分钟没说话,王五要去问600次。 用websocket 王五不用刷,等着就好了,服务器有了消息会自动给王五的。
基于websocket的事实通信的特点,其存在的应用场景大概有:
1.websocket社交订阅
对社交类的应用的一个裨益之处就是能够即时的知道你的朋友正在做什么。虽然听起来有点可怕,但是我们都喜欢这样做。你不会想要在数分钟之后才能知道一个家庭成员在馅饼制作大赛获胜或者一个朋友订婚的消息。你是在线的,所以你的订阅的更新应该是实时的。
2.websocket多玩家游戏
网络正在迅速转变为游戏平台。在不使用插件(我指的是Flash)的情况下,网络开发者现在可以在浏览器中实现和体验高性能的游戏。无论你是在处理DOM元素、css动画,html5的canvas或者尝试使用WebGL,玩家之间的互动效率是至关重要的。我不想在我扣动扳机之后,我的对手却已经移动位置。
3.websocket协同编辑/编程
我们生活在分布式开发团队的时代。平时使用一个文档的副本就满足工作需求了,但是你最终需要有一个方式来合并所有的编辑副本。版本控制系统,比如Git能够帮助处理某些文件,但是当Git发现一个它不能解决的冲突时,你仍然需要去跟踪人们的修改历史。通过一个协同解决方案,比如WebSocket,我们能够工作在同一个文档,从而省去所有的合并版本。这样会很容易看出谁在编辑什么或者你在和谁同时在修改文档的同一部分。
4.websocket收集点击流数据
分析用户与你网站的互动是提升你的网站的关键。HTTP的开销让我们只能优先考虑和收集最重要的数据部分。然后,经过六个月的线下分析,我们意识到我们应该收集一个不同的判断标准——一个看起来不是那么重要但是现在却影响了一个关键的决定。与HTTP请求的开销方式相比,使用Websocket,你可以由客户端发送不受限制的数据。想要在除页面加载之外跟踪鼠标的移动?只需要通过WebSocket连接发送这些数据到服务器,并存储在你喜欢的NoSQL数据库中就可以了(MongoDB是适合记录这样的事件的)。现在你可以通过回放用户在页面的动作来清楚的知道发生了什么。
5.股票基金报价
金融界瞬息万变——几乎是每毫秒都在变化。我们人类的大脑不能持续以那样的速度处理那么多的数据,所以我们写了一些算法来帮我们处理这些事情。虽然你不一定是在处理高频的交易,但是,过时的信息也只能导致损失。当你有一个显示盘来跟踪你感兴趣的公司时,你肯定想要随时知道他们的价值,而不是10秒前的数据。使用WebSocket可以流式更新这些数据变化而不需要等待。
6.体育实况更新
现在我们开始讨论一个让人们激情澎湃的愚蠢的东西——体育。我不是运动爱好者,但是我知道运动迷们想要什么。当爱国者在打比赛的时候,我的妹夫将会沉浸于这场比赛中而不能自拔。那是一种疯狂痴迷的状态,完全发自内心的。我虽然不理解这个,但是我敬佩他们与运动之间的这种强烈的联系,所以,最后我能做的就是给他的体验中降低延迟。如果你在你的网站应用中包含了体育新闻,WebSocket能够助力你的用户获得实时的更新。
7.多媒体聊天
视频会议并不能代替和真人相见,但当你不能在同一个屋子里见到你谈话的对象时,视频会议是个不错的选择。尽管视频会议私有化做的“不错”,但其使用还是很繁琐。我可是开放式网络的粉丝,所以用WebSockets getUserMedia API和html5音视频元素明显是个不错的选择。WebRTC的出现顺理成章的成为我刚才概括的组合体,它看起来很有希望,但其缺乏目前浏览器的支持,所以就取消了它成为候选人的资格。
8.基于位置的应用
越来越多的开发者借用移动设备的GPS功能来实现他们基于位置的网络应用。如果你一直记录用户的位置(比如运行应用来记录运动轨迹),你可以收集到更加细致化的数据。如果你想实时的更新网络数据仪表盘(可以说是一个监视运动员的教练),HTTP协议显得有些笨拙。借用WebSocket TCP链接可以让数据飞起来。
9.在线教育
上学花费越来越贵了,但互联网变得更快和更便宜。在线教育是学习的不错方式,尤其是你可以和老师以及其他同学一起交流。很自然,WebSockets是个不错的选择,可以多媒体聊天、文字聊天以及其它优势如与别人合作一起在公共数字黑板上画画…
10.论坛的消息广播
早期的论坛消息通知,靠的都是js轮询,现在有了websocket 可以改改了。
11.秒杀系统用户状态长链接
在秒杀系统中,无论是非热点商品还是热点商品抢单,抢单完成后,我们应该要通知用户抢单状态,非热点商品可以直接响应抢单结果,但热点商品目前还没有实现通知响应,通知用户抢单状态用户可以通过定时向后台发出请求查询实现,但这种短连接方式效率低,会和服务器进行多次通信,这块我们可以使用长连接websocket实现。