说到websocket,就不得不提http协议的链接特色特色与交互模型。html
首先,http协议的特色是无状态链接。即http的前一次链接与后一次链接是相互独立的。前端
其次,http的交互模型是请求/应答模型。即交互是经过C/B端向S端发送一个请求,S端根据请求,返回一个响应。java
那么这里就有一个问题了--S端没法主动向C/B端发送消息。而交互是双方的事情,怎么能限定一方发数据,另外一方接数据呢。web
传统的解决方案就俩字:轮询。spring
长短链接轮询就不详细说了,就说说轮询。大概的场景是这样的:apache
客户端(Request):有消息不?安全
服务端(Response):No服务器
客户端(Request):有消息不?websocket
服务端(Response):No网络
客户端(Request):有消息不?
服务端(Response):No
客户端(Request):有消息不?
服务端(Response):有了。你妈叫你回家吃饭。
客户端(Request):有消息不?
服务端(Response):No
==================================> loop
看着都累,资源消耗那就更没必要说了。尤为有些对实时性要求高的数据,那可能就是1s请求一次。目测服务器已经泪奔。
那么websocket的解决方案,总结一下,就是:创建固定链接
说白了,就是C/B端与S端就一个websocket服务创建一个固定的链接,不断开。
大概的场景是这样的:
服务端:我创建了一个chat的websocket,欢迎你们链接。
客户端:我要和你的chat的websocket链接,个人sid(惟一标识)是No.1
服务端:好的,我已经记住你了。若是有发往chat下No.1的消息,我会告诉你的。
客户端:嗯。谢谢了哈。
==================================> 过了一段时间
(有一个请求调用了chat的websocket,而且指名是给No.1的消息)
服务端(发送消息给No.1):No.1,有你的消息。你妈妈叫你回家作做业。
客户端(No.1):好的。我收到了。谢谢。
因为此次只是简单说一下websocket,因此就不深刻解读网络相关知识了。
既然http没法知足用户的全部需求,那么为之诞生的websocket必然有其诸多应用场景。如:
其实总结一下,websocket的应用场景就俩字:实时
不管是多玩家网络游戏,网站在线人数等都是因为实时性的需求,才用上了websocket(后面用缩写ws)。
谈几个在我项目中用到的情景:
当你的项目中存在须要S端向C/B端发送数据的情形,那就能够考虑上一个websocket了。
<!-- websocket --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
忍不住想要吐槽,为何不能够如eureka等组件那样,直接在启动类写一个注解就Ok了呢。看来还得之后本身动手,丰衣足食啊。
package com.renewable.center.warning.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; /** * Websocket的配置 * 说白了就是引入Websocekt至spring容器 */ @Configuration public class WebSocketConfig { @Bean public ServerEndpointExporter serverEndpointExporter() { return new ServerEndpointExporter(); } }
package com.renewable.center.warning.controller.websocket; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; 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.concurrent.CopyOnWriteArraySet; /** * @Description: * @Author: jarry */ @Component @Slf4j @ServerEndpoint("/websocket/warning/{sid}") public class WarningWebSocketServer { // JUC包的线程安全Set,用来存放每一个客户端对应的WarningWebSocketServer对象。 // 用ConcurrentHashMap也是能够的。说白了就是相似线程池中的BlockingQueue那样做为一个容器 private static CopyOnWriteArraySet<WarningWebSocketServer> warningWebSocketSet = new CopyOnWriteArraySet<WarningWebSocketServer>(); // 与某个客户端的链接会话,须要经过它来给客户端发送数据 private Session session; // 接收sid private String sid=""; /** * 创建websocket链接 * 看起来很像JSONP的回调,由于前端那里是Socket.onOpne() * @param session * @param sid */ @OnOpen public void onOpen(Session session, @PathParam("sid") String sid){ this.session = session; this.sid = sid; warningWebSocketSet.add(this); sendMessage("websocket connection has created."); } /** * 关闭websocket链接 */ @OnClose public void onClose(){ warningWebSocketSet.remove(this); log.info("there is an wsConnect has close ."); } /** * websocket链接出现问题时的处理 */ @OnError public void onError(Session session, Throwable error){ log.error("there is an error has happen ! error:{}",error); } /** * websocket的server端用于接收消息的(目测是用于接收前端经过Socket.onMessage发送的消息) * @param message */ @OnMessage public void onMessage(String message){ log.info("webSocketServer has received a message:{} from {}", message, this.sid); // 调用消息处理方法(此时针对的WarningWebSocektServer对象,只是一个实例。这里进行消息的单发) // 目前这里尚未处理逻辑。故为了便于前端调试,这里直接返回消息 this.sendMessage(message); } /** * 服务器主动推送消息的方法 */ public void sendMessage(String message){ try { this.session.getBasicRemote().sendText(message); } catch (IOException e) { log.warn("there is an IOException:{}!",e.toString()); } } public static void sendInfo(String sid, String message){ for (WarningWebSocketServer warningWebSocketServerItem : warningWebSocketSet) { if (StringUtils.isBlank(sid)){ // 若是sid为空,即群发消息 warningWebSocketServerItem.sendMessage(message); log.info("Mass messaging. the message({}) has sended to sid:{}.", message,warningWebSocketServerItem.sid); } if (StringUtils.isNotBlank(sid)){ if (warningWebSocketServerItem.sid.equals(sid)){ warningWebSocketServerItem.sendMessage(message); log.info("single messaging. message({}) has sended to sid:{}.", message, warningWebSocketServerItem.sid); } } } } }
为了便于调试与展现效果,写一个控制层,用于推送消息
package com.renewable.center.warning.controller.websocket; import com.renewable.terminal.terminal.common.ServerResponse; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import java.io.IOException; /** * @Description: 用于测试WebsocketServer * @Author: jarry */ @Controller @RequestMapping("/websocket/test/") public class WarningWebsocketController { @GetMapping("link.do") @ResponseBody public ServerResponse link(@RequestParam(name = "sid") int sid){ return ServerResponse.createBySuccessMessage("link : "+sid); } /** * 调用WarningWebsocketServer的消息推送方法,从而进行消息推送 * @param sid 链接WarningWebsocketServer的前端的惟一标识。若是sid为空,即表示向全部链接WarningWebsocketServer的前端发送相关消息 * @param message 须要发送的内容主体 * @return */ @ResponseBody @RequestMapping("push.do") public ServerResponse pushToWeb(@RequestParam(name = "sid", defaultValue = "") String sid, @RequestParam(name = "message") String message) { WarningWebSocketServer.sendInfo(sid, message); return ServerResponse.createBySuccessMessage(message+"@"+sid+" has send to target."); } }
这里创建了一个B端页面,用于与S端进行交互,演示。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>WebsocketTestIndex</title> </head> <body> <h1>Websocket Test</h1> <script> var socket; if(typeof(WebSocket) == "undefined") { console.log("Your browser not support WebSocket !"); }else{ console.log("Your browser support WebSocket"); // 实例化WebSocket对象 // 指定要链接的服务器地址与端口 // 创建链接 socket = new WebSocket("ws://localhost:10706/websocket/warning/2"); // 打开事件 socket.onopen = function() { console.log("You has connect to WebSocketServer"); }; // 得到消息事件 socket.onmessage = function(msg) { // 打印接收到的消息 console.log(msg.data); }; // 关闭事件 socket.onclose = function() { console.log("Socket has closed"); }; // 发生了错误事件 socket.onerror = function() { alert("Socket happen an error !"); } } </script> </body> </html>
再次强调,图片很大很清晰。若是看不清楚,请单独打开图片。
至此,websocket的应用就算入门了。至于实际的使用,其实就是服务端本身调用WebSocket的sendInfo接口。固然也能够本身扩展更为细致的逻辑,方法等。
另外,须要注意的是,别忘了及时关闭webocket的链接。尤为在负载较大的状况下,更须要注意即便关闭没必要要的链接。
架构的技术选型,须要的不是最好的,而是最适合的。
若是想要了解更多概念上的细节,能够看看这篇文章: