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的理解&应用&场景

WebSocket的实现与应用的更多相关文章

  1. 漫扯:从polling到Websocket

    Http被设计成了一个单向的通信的协议,即客户端发起一个request,然后服务器回应一个response.这让服务器很为恼火:我特么才是老大,我居然不能给小弟发消息... 轮询 老大发火了,小弟们自 ...

  2. 细说WebSocket - Node篇

    在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...

  3. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  4. WebSocket - ( 一.概述 )

    说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...

  5. php+websocket搭建简易聊天室实践

    1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...

  6. Demo源码放送:打通B/S与C/S !让HTML5 WebSocket与.NET Socket公用同一个服务端!

    随着HTML5 WebSocket技术的日益成熟与普及,我们可以借助WebSocket来更加方便地打通BS与CS -- 因为B/S中的WebSocket可以直接连接到C/S的服务端,并进行双向通信.如 ...

  7. Cowboy 开源 WebSocket 网络库

    Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...

  8. 借助Nodejs探究WebSocket

    文章导读: 一.概述-what's WebSocket? 二.运行在浏览器中的WebSocket客户端+使用ws模块搭建的简单服务器 三.Node中的WebSocket 四.socket.io 五.扩 ...

  9. 细说websocket - php篇

    下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket ...

  10. webSocket and LKDBHelper的使用说明

    socketket与lkdbhelper来处理数据 客户需求: 当我们有需要从自己的后台推送消息给我们的用户时,用户需要实时的接收到来自我们的推送消息.前提是没有使用第三方的推送框架,那么这个使用we ...

随机推荐

  1. 给VS设置代码创建人的宏

    Sub AddFunComment() Dim DocSel As EnvDTE.TextSelection DocSel = DTE.ActiveDocument.Selection DocSel. ...

  2. python爬取拉勾网数据并进行数据可视化

    爬取拉勾网关于python职位相关的数据信息,并将爬取的数据已csv各式存入文件,然后对csv文件相关字段的数据进行清洗,并对数据可视化展示,包括柱状图展示.直方图展示.词云展示等并根据可视化的数据做 ...

  3. 渐进式web应用开发---service worker (二)

    阅读目录 1. 创建第一个service worker 及环境搭建 2. 使用service worker 对请求拦截 3. 从web获取内容 4. 捕获离线请求 5. 创建html响应 6. 理解 ...

  4. scrapy 发post请求

    可以使用 yield scrapy.FormRequest(url, formdata, callback)方法发送POST请求. 如果希望程序执行一开始就发送POST请求,可以重写Spider类的s ...

  5. flask高级编程 LocalStack 线程隔离

    转:https://www.cnblogs.com/wangmingtao/p/9372611.html   30.LocalStack作为线程隔离对象的意义 30.1 数据结构 限制了某些能力 30 ...

  6. Android安卓书籍推荐《Android驱动开发与移植实战详解》下载

    百度云下载地址:点我 Android凭借其开源性.优异的用户体验和极为方便的开发方式,赢得了广大用户和开发者的青睐,目前已经发展成为市场占有率很高的智能手机操作系统. <Android驱动开发与 ...

  7. Java项目案例之---常用工具类练习

    常用工具类练习 1. 请根据控制台输入的特定日期格式拆分日期,如:请输入一个日期(格式如:**月**日****年),经过处理得到:****年**月**日 import java.util.Scanne ...

  8. JSP之BBS论坛网站的创建

    游戏论坛 工具:myeclipse10.0版本 Tomacat是在外部导入的apache-tomcat-7.0.93 JDK版本为1.6.0_13 字符编码统一为utf-8 一.建立数据库(chat) ...

  9. python爬虫常用正则表达式组合匹配

    ["\']匹配什么?(.*?)匹配什么? ["\']       -----------     匹配单双引号 (.*?)xxx ----------- 匹配任意长度字符+xxx ...

  10. BFM使用 - 获取平均脸模型的68个特征点坐标

    使用版本:2009 数据说明网址:https://faces.dmi.unibas.ch/bfm/index.php?nav=1-1-0&id=details 数据下载网址:https://f ...