当前位置:   article > 正文

SpringBoot+WebSocket实现服务端、客户端_springboot websocket客户端

springboot websocket客户端

一、引言

小编最近一直在使用springboot框架开发项目,毕竟现在很多公司都在采用此框架,之后小编也会陆续写关于springboot开发常用功能的文章。

什么场景下会要使用到websocket的呢?

websocket主要功能就是实现网络通讯,比如说最经典的客服聊天窗口、您有新的消息通知,或者是项目与项目之间的通讯,都可以采用websocket来实现。

二、websocket介绍

百度百科介绍:WebSokcet

在公司实际使用websocket开发,一般来都是这样的架构,首先websocket服务端是一个单独的项目,其他需要通讯的项目都是以客户端来连接,由服务端控制消息的发送方式(群发、指定发送)。但是也会有服务端、客户端在同一个项目当中,具体看项目怎么使用。

本文呢,采用的是服务端与客户端分离来实现,包括使用springboot搭建websokcet服务端、html5客户端、springboot后台客户端, 具体看下面代码。

三、服务端实现

*步骤一*:springboot底层帮我们自动配置了websokcet,引入maven依赖

  1. <dependency>
  2.     <groupId>org.springframework.boot</groupId>
  3.     <artifactId>spring-boot-starter-websocket</artifactId>
  4. </dependency>

*步骤二*:如果是你采用springboot内置容器启动项目的,则需要配置一个Bean。如果是采用外部的容器,则可以不需要配置。

  1. /**
  2.  * @Auther: liaoshiyao
  3.  * @Date2019/1/11 11:49
  4.  * @Description: 配置类
  5.  */
  6. @Component
  7. public class WebSocketConfig {
  8.     /**
  9.      * ServerEndpointExporter 作用
  10.      *
  11.      * 这个Bean会自动注册使用@ServerEndpoint注解声明的websocket endpoint
  12.      *
  13.      * @return
  14.      */
  15.     @Bean
  16.     public ServerEndpointExporter serverEndpointExporter() {
  17.         return new ServerEndpointExporter();
  18.     }
  19. }

*步骤三*:最后一步当然是编写服务端核心代码了,其实小编不是特别想贴代码出来,贴很多代码影响文章可读性。

  1. package com.example.socket.code;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.springframework.stereotype.Component;
  4. import javax.websocket.OnClose;
  5. import javax.websocket.OnMessage;
  6. import javax.websocket.OnOpen;
  7. import javax.websocket.Session;
  8. import javax.websocket.server.PathParam;
  9. import javax.websocket.server.ServerEndpoint;
  10. import java.util.concurrent.ConcurrentHashMap;
  11. /**
  12.  * @Auther: liaoshiyao
  13.  * @Date2019/1/11 11:48
  14.  * @Description: websocket 服务类
  15.  */
  16. /**
  17.  *
  18.  * @ServerEndpoint 这个注解有什么作用?
  19.  *
  20.  * 这个注解用于标识作用在类上,它的主要功能是把当前类标识成一个WebSocket的服务端
  21.  * 注解的值用户客户端连接访问的URL地址
  22.  *
  23.  */
  24. @Slf4j
  25. @Component
  26. @ServerEndpoint("/websocket/{name}")
  27. public class WebSocket {
  28.     /**
  29.      *  与某个客户端的连接对话,需要通过它来给客户端发送消息
  30.      */
  31.     private Session session;
  32.      /**
  33.      * 标识当前连接客户端的用户名
  34.      */
  35.     private String name;
  36.     /**
  37.      *  用于存所有的连接服务的客户端,这个对象存储是安全的
  38.      */
  39.     private static ConcurrentHashMap<String,WebSocket> webSocketSet = new ConcurrentHashMap<>();
  40.     @OnOpen
  41.     public void OnOpen(Session session, @PathParam(value = "name"String name){
  42.         this.session = session;
  43.         this.name = name;
  44.         // name是用来表示唯一客户端,如果需要指定发送,需要指定发送通过name来区分
  45.         webSocketSet.put(name,this);
  46.         log.info("[WebSocket] 连接成功,当前连接人数为:={}",webSocketSet.size());
  47.     }
  48.     @OnClose
  49.     public void OnClose(){
  50.         webSocketSet.remove(this.name);
  51.         log.info("[WebSocket] 退出成功,当前连接人数为:={}",webSocketSet.size());
  52.     }
  53.     @OnMessage
  54.     public void OnMessage(String message){
  55.         log.info("[WebSocket] 收到消息:{}",message);
  56.         //判断是否需要指定发送,具体规则自定义
  57.         if(message.indexOf("TOUSER"== 0){
  58.             String name = message.substring(message.indexOf("TOUSER")+6,message.indexOf(";"));
  59.             AppointSending(name,message.substring(message.indexOf(";")+1,message.length()));
  60.         }else{
  61.             GroupSending(message);
  62.         }
  63.     }
  64.     /**
  65.      * 群发
  66.      * @param message
  67.      */
  68.     public void GroupSending(String message){
  69.         for (String name : webSocketSet.keySet()){
  70.             try {
  71.                 webSocketSet.get(name).session.getBasicRemote().sendText(message);
  72.             }catch (Exception e){
  73.                 e.printStackTrace();
  74.             }
  75.         }
  76.     }
  77.     /**
  78.      * 指定发送
  79.      * @param name
  80.      * @param message
  81.      */
  82.     public void AppointSending(String name,String message){
  83.         try {
  84.             webSocketSet.get(name).session.getBasicRemote().sendText(message);
  85.         }catch (Exception e){
  86.             e.printStackTrace();
  87.         }
  88.     }
  89. }

四、客户端实现

*HTML5实现*:以下就是核心代码了,其实其他博客有很多,小编就不多说了。

  1.  var websocket = null;
  2.     if('WebSocket' in window){
  3.         websocket = new WebSocket("ws://192.168.2.107:8085/websocket/testname");
  4.     }
  5.     websocket.onopen = function(){
  6.         console.log("连接成功");
  7.     }
  8.     websocket.onclose = function(){
  9.         console.log("退出连接");
  10.     }
  11.     websocket.onmessage = function (event){
  12.         console.log("收到消息"+event.data);
  13.     }
  14.     websocket.onerror = function(){
  15.         console.log("连接出错");
  16.     }
  17.     window.onbeforeunload = function () {
  18.         websocket.close(num);
  19.     }

*SpringBoot后台实现*:小编发现多数博客都是采用js来实现客户端,很少有用后台来实现,所以小编也就写了写,大神请勿喷?。很多时候,项目与项目之间通讯也需要后台作为客户端来连接。

*步骤一*:首先我们要导入后台连接websocket的客户端依赖

  1. <!--websocket作为客户端-->
  2. <dependency>
  3.     <groupId>org.java-websocket</groupId>
  4.     <artifactId>Java-WebSocket</artifactId>
  5.     <version>1.3.5</version>
  6. </dependency>

*步骤二*:把客户端需要配置到springboot容器里面去,以便程序调用。

  1. package com.example.socket.config;
  2. import lombok.extern.slf4j.Slf4j;
  3. import org.java_websocket.client.WebSocketClient;
  4. import org.java_websocket.drafts.Draft_6455;
  5. import org.java_websocket.handshake.ServerHandshake;
  6. import org.springframework.context.annotation.Bean;
  7. import org.springframework.stereotype.Component;
  8. import java.net.URI;
  9. /**
  10.  * @Auther: liaoshiyao
  11.  * @Date: 2019/1/11 17:38
  12.  * @Description: 配置websocket后台客户端
  13.  */
  14. @Slf4j
  15. @Component
  16. public class WebSocketConfig {
  17.     @Bean
  18.     public WebSocketClient webSocketClient() {
  19.         try {
  20.             WebSocketClient webSocketClient = new WebSocketClient(new URI("ws://localhost:8085/websocket/test"),new Draft_6455()) {
  21.                 @Override
  22.                 public void onOpen(ServerHandshake handshakedata) {
  23.                     log.info("[websocket] 连接成功");
  24.                 }
  25.                 @Override
  26.                 public void onMessage(String message) {
  27.                     log.info("[websocket] 收到消息={}",message);
  28.                 }
  29.                 @Override
  30.                 public void onClose(int code, String reason, boolean remote) {
  31.                     log.info("[websocket] 退出连接");
  32.                 }
  33.                 @Override
  34.                 public void onError(Exception ex) {
  35.                     log.info("[websocket] 连接错误={}",ex.getMessage());
  36.                 }
  37.             };
  38.             webSocketClient.connect();
  39.             return webSocketClient;
  40.         } catch (Exception e) {
  41.             e.printStackTrace();
  42.         }
  43.         return null;
  44.     }
  45. }

*步骤三*:使用后台客户端发送消息

1、首先小编写了一个接口,里面有指定发送和群发消息两个方法。

2、实现发送的接口,区分指定发送和群发由服务端来决定(小编在服务端写了,如果带有TOUSER标识的,则代表需要指定发送给某个websocket客户端)

3、最后采用get方式用浏览器请求,也能正常发送消息

  1. package com.example.socket.code;
  2. /**
  3.  * @Auther: liaoshiyao
  4.  * @Date2019/1/12 10:57
  5.  * @Description: websocket 接口
  6.  */
  7. public interface WebSocketService {
  8.     /**
  9.      * 群发
  10.      * @param message
  11.      */
  12.      void groupSending(String message);
  13.     /**
  14.      * 指定发送
  15.      * @param name
  16.      * @param message
  17.      */
  18.      void appointSending(String name,String message);
  19. }
  20. package com.example.socket.code;
  21. import org.java_websocket.client.WebSocketClient;
  22. import org.springframework.beans.factory.annotation.Autowired;
  23. import org.springframework.stereotype.Component;
  24. /**
  25.  * @Auther: liaoshiyao
  26.  * @Date2019/1/12 10:56
  27.  * @Description: websocket接口实现类
  28.  */
  29. @Component
  30. public class ScoketClient implements WebSocketService{
  31.     @Autowired
  32.     private WebSocketClient webSocketClient;
  33.     @Override
  34.     public void groupSending(String message) {
  35.         // 这里我加了6666-- 是因为我在index.html页面中,要拆分用户编号和消息的标识,只是一个例子而已
  36.         // 在index.html会随机生成用户编号,这里相当于模拟页面发送消息
  37.         // 实际这样写就行了 webSocketClient.send(message)
  38.         webSocketClient.send(message+"---6666");
  39.     }
  40.     @Override
  41.     public void appointSending(String name, String message) {
  42.         // 这里指定发送的规则由服务端决定参数格式
  43.         webSocketClient.send("TOUSER"+name+";"+message);
  44.     }
  45. }
  46. package com.example.socket.chat;
  47. import com.example.socket.code.ScoketClient;
  48. import org.springframework.beans.factory.annotation.Autowired;
  49. import org.springframework.web.bind.annotation.GetMapping;
  50. import org.springframework.web.bind.annotation.RequestMapping;
  51. import org.springframework.web.bind.annotation.RestController;
  52. /**
  53.  * @Auther: liaoshiyao
  54.  * @Date2019/1/11 16:47
  55.  * @Description: 测试后台websocket客户端
  56.  */
  57. @RestController
  58. @RequestMapping("/websocket")
  59. public class IndexController {
  60.     @Autowired
  61.     private ScoketClient webScoketClient;
  62.     @GetMapping("/sendMessage")
  63.     public String sendMessage(String message){
  64.         webScoketClient.groupSending(message);
  65.         return message;
  66.     }
  67. }

五、最后

其实,贴了这么多大一片的代码,感觉上影响了博客的美观,也不便于浏览,如果没看懂小伙伴,可以下载源码看下。

里面一共两个项目,服务端、客户端(html5客户端、后台客户端),是一个网页群聊的小案例。

https://download.csdn.net/download/weixin_38111957/10912384

*祝大家学习愉快~~~*

六、针对评论区的小伙伴提出的疑点进行解答

看了小伙伴提出的疑问,小编也是非常认可的,如果是单例的情况下,这个对象的值都会被修改。

小编就抽了时间Debug了一下,经过下图也可以反映出,能够看出,webSokcetSet中存在三个成员,并且vlaue值都是不同的,所以在这里没有出现对象改变而把之前对象改变的现象。

服务端这样写是没问题的。

紧接着,小编写了一个测试类,代码如下,经过测试输出的结果和小伙伴提出的疑点是一致的。

最后总结:这位小伙伴提出的观点确实是正确的,但是在实际WebSocket服务端案例中为什么没有出现这种情况,当WebSokcet这个类标识为服务端的时候,每当有新的连接请求,这个类都是不同的对象,并非单例。

这里也感谢“烟花苏柳”所提出的问题。

  1. import com.alibaba.fastjson.JSON;
  2. import java.util.concurrent.ConcurrentHashMap;
  3. /**
  4.  * @Auther: IT贱男
  5.  * @Date2018/11/1 16:15
  6.  * @Description:
  7.  */
  8. public class TestMain {
  9.     /**
  10.      * 用于存所有的连接服务的客户端,这个对象存储是安全的
  11.      */
  12.     private static ConcurrentHashMap<String, Student> webSocketSet = new ConcurrentHashMap<>();
  13.     public static void main(String[] args) {
  14.         Student student = Student.getStudent();
  15.         student.name = "张三";
  16.         webSocketSet.put("1", student);
  17.         Student students = Student.getStudent();
  18.         students.name = "李四";
  19.         webSocketSet.put("2", students);
  20.         System.out.println(JSON.toJSON(webSocketSet));
  21.     }
  22. }
  23. /**
  24.  * 提供一个单例类
  25.  */
  26. class Student {
  27.     public String name;
  28.     private Student() {
  29.     }
  30.     private static final Student student = new Student();
  31.     public static Student getStudent() {
  32.         return student;
  33.     }
  34. }

{"1":{"name":"李四"},"2":{"name":"李四"}}

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/从前慢现在也慢/article/detail/456912
推荐阅读
相关标签
  

闽ICP备14008679号