WebSocket介绍

websocket是html中一种新的协议,它实现了真正的长连接,实现了浏览器与服务器的全双工通信(指在通信的任意时刻,线路上存在A到B和B到A的双向信号传输)。 现在我们接触的协议大多是htttp协议,在浏览器中通过http协议实现了单向的通信,浏览器发出请求,服务器在响应,一次客户端与服务器的请求就结束了,服务器不能主动响应客户端,主动往客户端返回数据,而在某些需求上要实时刷新数据,获取服务器上的最新数据,显示给客户端。为了实现这样的需求,大多数公司使用了轮询的技术。轮询技术,在特定的时间间隔(如1秒)由浏览器发出http request,服务器再将最新数据返回给浏览器,实现了数据的实时刷新,很明显,通过这种技术实现的伪长连接,存在着一些缺陷,每隔一段时间的http request,不见得每一次的请求都是有意义的,因为客户端不会知道服务器上的数据有没有更新,这样在多次请求当中肯定会存在无效的请求(上一次请求回来的数据跟本次的完全一样)。 可见轮询这种技术,存在很大的弊端,而websocket实现了真正的长连接,服务器可以主动向客户端发送数据,正是这样的特点,就能很好的实现这种需求,当服务器有数据变化时,服务器就可以将新的数据返回给客户端,没有无效的请求回复

环境配置和工具

STS     ,JDK1.8,     Spring boot 2.0.4

新建spring boot项目,在pom.xml文件中加入WebSocket依赖

<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency><!-- websocket依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>

WebSocket配置类

*** 使用@ServerEndpoint创立websocket endpoint* 首先要注入 ServerEndpointExporter,* 这个bean会自动注册使用了 @ServerEndpoint 注* 解声明的 Web Socket endpoint。* 要注意,如果使用独立的 Servlet 容器,* 而不是直接使用 Spring Boot 的内置容器,* 就不要注入 ServerEndpointExporter,* 因为它将由容器自己提供和管理*/
@Configuration
public class WebSocketConfig{@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

注意!!!这里要说明一下,由于我们是点对点通信,不同于广播式通信的是,必须要区分不同的客户端,那么我们怎么来让服务端区分出不同的客户端呢?

经过查找资料和试验,我找到了两种可行获取客户端userId的方法。

一种是通过在Server取HttpSession中的值获取当前用户

一种是直接在客户端建立连接时附带上用户的值。

先说第一种,新建一个MyEndpointConfigure类,代码如下,

/*** * @author lipengbin**/
public class MyEndpointConfigure extends ServerEndpointRegistration.Configurator implements ApplicationContextAware
{private static volatile BeanFactory context;@Overridepublic <T> T getEndpointInstance(Class<T> clazz) throws InstantiationException{return context.getBean(clazz);}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException{System.out.println("auto load"+this.hashCode());MyEndpointConfigure.context = applicationContext;}
}

然后在配置类里面添加如下代码,用来向spring注册服务

@Beanpublic MyEndpointConfigure newConfigure(){return new MyEndpointConfigure();}

最后我们编写WebSocket服务端的类

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;/*** Created by jack on 2017/10/25.*/
/*** websocket的具体实现类*/
@ServerEndpoint(value = "/websocket",configurator = MyEndpointConfigure.class)
@Component
public class WebSocketServer {//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。private static int onlineCount = 0;//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<WebSocketServer>();//与某个客户端的连接会话,需要通过它来给客户端发送数据private Session session;String str=""; /*** 连接建立成功调用的方法*/@OnOpenpublic void onOpen(Session session,EndpointConfig config) {this.session = session;HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());webSocketSet.add(this);     //加入set中addOnlineCount();           //在线数加1try {//sendMessage(CommonConstant.CURRENT_WANGING_NUMBER.toString());sendMessage("服务端连接成功");} catch (IOException e) {System.out.println("IO异常");}}/*** 连接关闭调用的方法*/@OnClosepublic void onClose() {webSocketSet.remove(this);  //从set中删除subOnlineCount();   System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());}/*** 收到客户端消息后调用的方法** @param message 客户端发送过来的消息*/@OnMessagepublic void onMessage(String message, Session session) {System.out.println("来自客户端的消息:" + message);str=message;
//        System.out.println("onMessage sessionId is : "+session.getId());//群发消息
//        for (WebSocketServer item : webSocketSet) {
//            try {
//                item.sendMessage(message);
//            } catch (IOException e) {
//                e.printStackTrace();
//            }
//        }}/*** 发生错误时调用*/@OnErrorpublic void onError(Session session, Throwable error) {for (WebSocketServer item : webSocketSet) {try {item.sendMessage("响应超时");} catch (IOException e) {e.printStackTrace();}}error.printStackTrace();}public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText("服务端消息:"+message);//this.session.getAsyncRemote().sendText(message);}/*** 群发自定义消息*/public static void sendInfo(String message) throws IOException {for (WebSocketServer item : webSocketSet) {try {item.sendMessage(message);} catch (IOException e) {continue;}}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount++;}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}
}

注意这一行

@ServerEndpoint(value = "/websocket",configurator = MyEndpointConfigure.class)

加上了它。我们就可以用

@OnOpenpublic void onOpen(Session session, EndpointConfig config){HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());}

来获取httpSession对象了,然后直接取出登录时存储的用户对象就可以了。

但是!!!这么做有问题啊

原则上来讲我在server获取的httpsession中取出来的用户对象就是现在正和服务端建立连接的对象,因为这种情况的操作肯定是先登录,然后直接建立连接,可是在实际中多用户同时登录时就不一定是这样子了。因为登录是客户端发起的操作,建立连接也是客户端发起的操作,且不说在客户端这两个操作是否是紧密相连,就算是紧密相连,从服务器验证成功(此时已经放入currentUser对象)返回登录结果给客户端到客户端向服务端发起连接这中间因为网络原因也是会消耗一定时间的。那么这时候一件尴尬的事情就发生了:此时,另一个用户也在登录,并且在之前用户两个操作期间完成了登录验证操作,那么第一个用户连接建立之后取出的use对象就不是这个用户的而是第二个用户的,这就乱套了。这种方法相当于是 ,用户A先对服务器说,记住了,我叫A,然后过了一会儿来说,我要建立连接,我是刚刚告诉你名字那个人。那如果B在A离开那会儿也告诉了服务器我叫B,那么服务器就会把A当成B了。

但是,哎,不慌!我们还有planB呢

服务端可以用@PathParam获取用户对象,如此这般,用户在建立WebSocket连接的时候告诉服务器自己的用户id,这样服务器就肯定不会把用户搞错了。

服务端注解的地方改成这样写

@ServerEndpoint(value = "/websocket/{userId}")

建立连接的方法参数

 @OnOpen//public void onOpen(Session session,EndpointConfig config) {public void onOpen(@PathParam("userId")String userId,Session session){this.session = session;String[] userArray = userId.split(",");this.userid = userArray[0];webSocketMap.put(userid, this);addOnlineCount();           //在线数加1send("服务端连接成功", this.userid);System.out.println("服务端连接成功");}

客户端建立连接时的请求如下

ws = "ws://localhost:8080"  + "/websocket"+"/${userId}";

userId就是你的用户id,可以从session中获取。使用这种方法获取用户id,也就不用再配置类里添加下面这段代码了

@Beanpublic MyEndpointConfigure newConfigure(){return new MyEndpointConfigure();}

以上,实现了一个websocket作为服务端,html页面作为客户端的一个websocket连接的例子,下面介绍java作为客户端的例子。

spring boot项目引入依赖

<dependency><groupId>org.java-websocket</groupId><artifactId>Java-WebSocket</artifactId><version>1.3.4</version><scope>test</scope>
</dependency>

建立一个测试类

import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;/*** Created by jack on 2018.9.25*/
public class WebsocketClient {public static WebSocketClient client;public static void main(String[] args) throws InterruptedException {try {client = new WebSocketClient(new URI("ws://localhost:8081/websocket"),new Draft_6455()) {//client = new WebSocketClient(new URI("ws://192.168.87.59:80/websocket"),new Draft_6455()) {@Overridepublic void onOpen(ServerHandshake serverHandshake) {System.out.println("打开链接");}@Overridepublic void onMessage(String s) {System.out.println("收到消息"+s);}@Overridepublic void onClose(int i, String s, boolean b) {System.out.println("链接已关闭");}@Overridepublic void onError(Exception e) {e.printStackTrace();System.out.println("发生错误已关闭");}};} catch (URISyntaxException e) {e.printStackTrace();}client.connect();System.out.println(client.getDraft());while(!client.getReadyState().equals(WebSocket.READYSTATE.OPEN)){System.out.println("还没有打开");Thread.sleep(1000);}try {for(int i=0;i<1000;i++){String str = "打开了"+i;System.out.println(str);send("hello world".getBytes("utf-8"));Thread.sleep(3000);client.send(str);}} catch (UnsupportedEncodingException e) {e.printStackTrace();}}public static void send(byte[] bytes){client.send(bytes);}}

这里我还是用的第一种获取session的方法,改成第二种只需要在建立连接的时候后面加上Id即可。

运行结果如下

参考:https://blog.csdn.net/wonderful_life_mrchi/article/details/52535463

https://blog.csdn.net/qq_33171970/article/details/55001587

https://blog.csdn.net/j903829182/article/details/78342941

spring boot中使用websocket实现点对点通信与服务器推送相关推荐

  1. Spring之WebSocket网页聊天以及服务器推送

    Spring之WebSocket网页聊天以及服务器推送 转自:http://www.xdemo.org/spring-websocket-comet/ /Springframework /Spring ...

  2. 长连接/websocket/SSE等主流服务器推送技术比较

    最近做的某个项目有个需求,需要实时提醒client端有线上订单消息.所以保持客户端和服务器端的信息同步是关键要素,对此我们了解了可实现的方式.本文将介绍web常用的几种方式,希望给需要服务器端推送消息 ...

  3. springboot异步注解_Spring Boot 2 :Spring Boot 中的响应式编程和 WebFlux 入门

    [小宅按]Spring 5.0 中发布了重量级组件 Webflux,拉起了响应式编程的规模使用序幕. WebFlux 使用的场景是异步非阻塞的,使用 Webflux 作为系统解决方案,在大多数场景下可 ...

  4. (转)Spring Boot 2 (十):Spring Boot 中的响应式编程和 WebFlux 入门

    http://www.ityouknow.com/springboot/2019/02/12/spring-boot-webflux.html Spring 5.0 中发布了重量级组件 Webflux ...

  5. Spring Boot中文文档

    1.5.2.RELEASE Part I. Spring Boot 文档 本节简要介绍了Spring Boot文档,是整个文档的参考指南. 您可以完整阅读本参考指南,或者如果您不感兴趣的话可以跳过该部 ...

  6. 再谈Spring Boot中的乱码和编码问题

    编码算不上一个大问题,即使你什么都不管,也有很大的可能你不会遇到任何问题,因为大部分框架都有默认的编码配置,有很多是UTF-8,那么遇到中文乱码的机会很低,所以很多人也忽视了. Spring系列产品大 ...

  7. 【spring boot2】第8篇:spring boot 中的 servlet 容器及如何使用war包部署

    嵌入式 servlet 容器 在 spring boot 之前的web开发,我们都是把我们的应用部署到 Tomcat 等servelt容器,这些容器一般都会在我们的应用服务器上安装好环境,但是 spr ...

  8. Spring Boot 中使用 MongoDB 增删改查

    本文快速入门,MongoDB 结合SpringBoot starter-data-mongodb 进行增删改查 1.什么是MongoDB ? MongoDB 是由C++语言编写的,是一个基于分布式文件 ...

  9. Spring Boot 中使用@Async实现异步调用,加速任务执行!

    欢迎关注方志朋的博客,回复"666"获面试宝典 什么是"异步调用"?"异步调用"对应的是"同步调用",同步调用指程序按照 ...

  10. 徒手解密 Spring Boot 中的 Starter自动化配置黑魔法

    我们使用 Spring Boot,基本上都是沉醉在它 Stater 的方便之中.Starter 为我们带来了众多的自动化配置,有了这些自动化配置,我们可以不费吹灰之力就能搭建一个生产级开发环境,有的小 ...

最新文章

  1. 进程间通信-Queue
  2. CentOS修改MySql数据库目录datadir
  3. loadrunner之socket协议脚本编写
  4. C语言 · 输出日历
  5. 卑微测试员自述:入职新公司一个月,就让我做自动化测试?!
  6. turbo c 混编 汇编语言,浅谈Turbo C过程调用汇编
  7. [网络通信协议]websocket
  8. 离散数学耿素云计算机,离散数学,屈婉玲,耿素云,张立昂编著_考研教材_考试点...
  9. 本科双非,考研压线上北大,总结一下我的复习过程,希望对考研er有点帮助!
  10. 线段树的简单实现(引入lazy_tag)
  11. Groovy(二)groovy基础
  12. A Question of Ingestion Gym - 101673G
  13. 改名最成功的5所大学:改名如改命
  14. STM32使用虚拟示波器
  15. 一份机器学习的自白书
  16. 【论文阅读|深读】 GraphSAGE:Inductive Representation Learning on Large Graphs
  17. 搭建个人网站(2):Github和Vercel建站以及配置DNS
  18. 论文《Low Compute and Fully Parallel Computer Vision with 哈希匹配》学习
  19. Python集合类型详解(一)——集合定义与集合操作符
  20. 二相混合式步进电机开环细分控制simulink建模仿真含模型文件

热门文章

  1. Android 白天黑夜模式切换
  2. PowerPoint.Application win32 操作ppt 复制 新建 插入图片
  3. 全家福缺一个人怎么P图上去-免费+效果好
  4. 在线图片文字识别html,识别文字在线_识别图片文字的在线方法是什么?
  5. 记 · 寒风依旧 · 虎跑路和人生路
  6. XXX packages are looking for funding run `npm fund` for details解决方法
  7. 求解答!iframe在IE浏览器加载页面无反应问题
  8. 2021 ICPC Jinan C Optimal Strategy
  9. TMS570LS1224PWM的生成及捕获
  10. 【大咖有约】子衿技术团队徐戟:DBA职场进阶之路