zoukankan      html  css  js  c++  java
  • WebSocket的实现与应用

    WebSocket的实现与应用

    前言

    说到websocket,就不得不提http协议的连接特点特点与交互模型。

    首先,http协议的特点是无状态连接。即http的前一次连接与后一次连接是相互独立的。

    其次,http的交互模型是请求/应答模型。即交互是通过C/B端向S端发送一个请求,S端根据请求,返回一个响应。

    那么这里就有一个问题了--S端无法主动向C/B端发送消息。而交互是双方的事情,怎么能限定一方发数据,另一方接数据呢。

    传统解决方案:

    传统的解决方案就俩字:轮询。

    长短连接轮询就不详细说了,就说说轮询。大概的场景是这样的:

    客户端(Request):有消息不?

    服务端(Response):No

    客户端(Request):有消息不?

    服务端(Response):No

    客户端(Request):有消息不?

    服务端(Response):No

    客户端(Request):有消息不?

    服务端(Response):有了。你妈叫你回家吃饭。

    客户端(Request):有消息不?

    服务端(Response):No

    ==================================> loop

    看着都累,资源消耗那就更不必说了。尤其有些对实时性要求高的数据,那可能就是1s请求一次。目测服务器已经泪奔。

    websocket解决方案:

    那么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必然有其诸多应用场景。如:

    1. 实时显示网站在线人数
    2. 账户余额等数据的实时更新
    3. 多玩家网络游戏
    4. 多媒体聊天,如聊天室
    5. 。。。

    其实总结一下,websocket的应用场景就俩字:实时

    无论是多玩家网络游戏,网站在线人数等都是由于实时性的需求,才用上了websocket(后面用缩写ws)。

    谈几个在我项目中用到的情景:

    1. 在线教育项目中的课件系统,通过ws实现学生端课件与教师端课件的实时交互
    2. 物联网项目中的报警系统,通过ws实现报警信息的实时推送
    3. 大数据项目中的数据展示,通过ws实现数据的实时更新
    4. 物联网项目中的硬件交互系统,通过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();
    	    }  
    	  
    	} 
    
    

    代码实现:

    WebSocketServer的实现:

    
    	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);
    					}
    				}
    			}
    		}
    	
    	}
    
    

    WesocketController

    为了便于调试与展示效果,写一个控制层,用于推送消息

    
    	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.");
    		}
    	}
    
    

    WesocketTestIndex

    这里建立了一个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>
    
    

    效果展示

    再次强调,图片很大很清晰。如果看不清楚,请单独打开图片。

    B端网页初始化:

    调用S端WarningWebsocketController下pushToWeb()接口,对sid=2的B端发送消息:

    B端网页接收到专门发给sid=2的消息后的效果:

    调用S端WarningWebsocketController下pushToWeb()接口,所有连接该websocket的B端群发消息:

    B端网页接收到群发消息后的效果:

    S端接收到消息后的日志打印:

    S端在B端关闭连接后的日志打印:

    总结

    至此,websocket的应用就算入门了。至于实际的使用,其实就是服务端自己调用WebSocket的sendInfo接口。当然也可以自己扩展更为细致的逻辑,方法等。

    另外,需要注意的是,别忘了及时关闭webocket的连接。尤其在负载较大的情况下,更需要注意即使关闭不必要的连接。

    架构的技术选型,需要的不是最好的,而是最适合的。

    扩展:

    如果想要了解更多概念上的细节,可以看看这篇文章:

    websocket的理解&应用&场景

  • 相关阅读:
    优化页面响应时间
    php性能优化
    加快compser install 和update的方法
    好用的类库
    php会话(session)实现原理
    mysql引擎
    数据库事物四大特性
    数据库索引
    insert和insertSelective区别
    java面试题之int和Integer的区别
  • 原文地址:https://www.cnblogs.com/Tiancheng-Duan/p/11288593.html
Copyright © 2011-2022 走看看