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必然有其诸多应用场景。如:
- 实时显示网站在线人数
- 账户余额等数据的实时更新
- 多玩家网络游戏
- 多媒体聊天,如聊天室
- 。。。
其实总结一下,websocket的应用场景就俩字:实时
无论是多玩家网络游戏,网站在线人数等都是由于实时性的需求,才用上了websocket(后面用缩写ws)。
谈几个在我项目中用到的情景:
- 在线教育项目中的课件系统,通过ws实现学生端课件与教师端课件的实时交互
- 物联网项目中的报警系统,通过ws实现报警信息的实时推送
- 大数据项目中的数据展示,通过ws实现数据的实时更新
- 物联网项目中的硬件交互系统,通过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的实现与应用的更多相关文章
- 漫扯:从polling到Websocket
Http被设计成了一个单向的通信的协议,即客户端发起一个request,然后服务器回应一个response.这让服务器很为恼火:我特么才是老大,我居然不能给小弟发消息... 轮询 老大发火了,小弟们自 ...
- 细说WebSocket - Node篇
在上一篇提高到了 web 通信的各种方式,包括 轮询.长连接 以及各种 HTML5 中提到的手段.本文将详细描述 WebSocket协议 在 web通讯 中的实现. 一.WebSocket 协议 1. ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- WebSocket - ( 一.概述 )
说到 WebSocket,不得不提 HTML5,作为近年来Web技术领域最大的改进与变化,包含CSS3.离线与存储.多媒体.连接性( Connectivity )等一系列领域,而即将介绍的 WebSo ...
- php+websocket搭建简易聊天室实践
1.前言 公司游戏里面有个简单的聊天室,了解了之后才知道是node+websocket做的,想想php也来做个简单的聊天室.于是搜集各种资料看文档.找实例自己也写了个简单的聊天室. http连接分为短 ...
- Demo源码放送:打通B/S与C/S !让HTML5 WebSocket与.NET Socket公用同一个服务端!
随着HTML5 WebSocket技术的日益成熟与普及,我们可以借助WebSocket来更加方便地打通BS与CS -- 因为B/S中的WebSocket可以直接连接到C/S的服务端,并进行双向通信.如 ...
- Cowboy 开源 WebSocket 网络库
Cowboy.WebSockets 是一个托管在 GitHub 上的基于 .NET/C# 实现的开源 WebSocket 网络库,其完整的实现了 RFC 6455 (The WebSocket Pro ...
- 借助Nodejs探究WebSocket
文章导读: 一.概述-what's WebSocket? 二.运行在浏览器中的WebSocket客户端+使用ws模块搭建的简单服务器 三.Node中的WebSocket 四.socket.io 五.扩 ...
- 细说websocket - php篇
下面我画了一个图演示 client 和 server 之间建立 websocket 连接时握手部分,这个部分在 node 中可以十分轻松的完成,因为 node 提供的 net 模块已经对 socket ...
- webSocket and LKDBHelper的使用说明
socketket与lkdbhelper来处理数据 客户需求: 当我们有需要从自己的后台推送消息给我们的用户时,用户需要实时的接收到来自我们的推送消息.前提是没有使用第三方的推送框架,那么这个使用we ...
随机推荐
- yii DAO操作总结
数据库代码: /* Navicat MySQL Data Transfer Source Server : lonxom Source Server Version : 50524 S ...
- HDU 1286:找新朋友(欧拉函数)
http://acm.hdu.edu.cn/showproblem.php?pid=1286 题意:中文. 思路:求欧拉函数. #include <cstdio> #include < ...
- [转]sublime text3在指定浏览器上本地服务器(localhost)运行文件(php)
昨天在使用sublime text3时,发现能在本地服务器上运行php文件,于是百度了一下有关知识, 终于成功了,今天总结一下. 首先要让sublime text3 出现侧边栏sidebar,不会的可 ...
- 那些有实力进入 BAT 的本科生,都做对了什么事?
作者:黄小斜 文章来源:微信公众号[黄小斜] 最近这段时间,我们部门来了几个年纪轻轻的本科生,最小的比我们小五岁左,这对于我来说还是比较有冲击力的. 想想我也是九0出头的老腊肉了,想当年我上大学的时候 ...
- ABP开发框架前后端开发系列---(15)ABP框架的服务端和客户端缓存的使用
缓存在一个大型一点的系统里面是必然会涉及到的,合理的使用缓存能够给我们的系统带来更高的响应速度.由于数据提供服务涉及到数据库的相关操作,如果客户端的并发数量超过一定的数量,那么数据库的请求处理则以爆发 ...
- 控制台程序秒变Windows服务(Topshelf)
项目中有些时候需要写服务,一般我们都是先创建控制台程序,测试,运行,成功之后再创建windows服务程序,这样好麻烦啊,有没有简单的控制台程序直接变成Widnows服务,经过查找,找到了Topshel ...
- DStream转为DF的两种方式(突破map时元组22的限制)
在进行Spark Streaming的开发时,我们常常需要将DStream转为DataFrame来进行进一步的处理, 共有两种方式,方式一: val spark = SparkSession.buil ...
- WeUI Picker组件 源代码分析
前言 由于最近做的一个移动端项目需要使用到类似 WeUI Picker组件 的选择效果, 所以在这里来分析下 WeUI Picker 的实现逻辑.(weui.js项目地址) 之前也做过类似的组件, ...
- 个人永久性免费-Excel催化剂功能第52波-相同内容批量合并单元格,取消合并单元格并填充内容
在高级Excel用户群体中无比痛恨的合并单元格,在现实的表格中却阴魂不散的纠缠不断.今天Excel催化剂也来成为“帮凶”,制造更多的合并单元格.虽然开发出此功能,请使用过程中务必要保持节制,在可以称为 ...
- Java中的I/O输入输出流概述
流是一组有序的数据序列,根据操作类型,可以分为输入流和输出流两种,Java语言中定义的负责各种输入输出的类都被放在java.io包中.其中所有的输入流类都是抽象类InputStream(字节输入流)或 ...