当前位置:首页 > 科技  > 软件

SpringBoot使用WebSocket实现即时消息

来源: 责编: 时间:2023-08-14 22:01:09 459观看
导读环境:SpringBoot2.4.12.依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></dependency><dependency> <groupId>org.springframework.boot</groupI

环境:SpringBoot2.4.12.vdF28资讯网——每日最新资讯28at.com


依赖

<dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-web</artifactId></dependency><dependency>  <groupId>org.springframework.boot</groupId>  <artifactId>spring-boot-starter-websocket</artifactId></dependency>

定义消息类型

  • 抽象消息对象
public class AbstractMessage {  /**   *   消息类型   */  protected String type ;    /**   *   消息内容   */  protected String content ;  /**   *   消息日期   */  protected String date ;}

消息对象子类vdF28资讯网——每日最新资讯28at.com

1、Ping检查消息vdF28资讯网——每日最新资讯28at.com

public class PingMessage extends AbstractMessage {  public PingMessage() {}  public PingMessage(String type) {    this.type = type ;  }}

2、系统消息vdF28资讯网——每日最新资讯28at.com

public class SystemMessage extends AbstractMessage {  public SystemMessage() {}  public SystemMessage(String type, String content) {    this.type = type ;    this.content = content ;  }}

3、点对点消息vdF28资讯网——每日最新资讯28at.com

public class PersonMessage extends AbstractMessage {  private String fromName ;  private String toName ;}

消息类型定义vdF28资讯网——每日最新资讯28at.com

public enum MessageType {    /**   *   系统消息 0000;心跳检查消息 0001;点对点消息2001   */  SYSTEM("0000"), PING("0001"), PERSON("2001") ;    private String type ;    private MessageType(String type) {    this.type = type ;  }  public String getType() {    return type;  }  public void setType(String type) {    this.type = type;  }  }

WebSocket服务端点

该类作用就是定义客户端连接的地址vdF28资讯网——每日最新资讯28at.com

@ServerEndpoint(value = "/message/{username}",   encoders = {WsMessageEncoder.class},  decoders = {WsMessageDecoder.class},  subprotocols = {"gmsg"},  configurator = MessageConfigurator.class)  @Component  public class GMessageListener {        public static ConcurrentMap<String, UserSession> sessions = new ConcurrentHashMap<>();    private static Logger logger = LoggerFactory.getLogger(GMessageListener.class) ;      private String username ;        @OnOpen      public void onOpen(Session session, EndpointConfig config, @PathParam("username") String username){      UserSession userSession = new UserSession(session.getId(), username, session) ;      this.username = username ;      sessions.put(username, userSession) ;      logger.info("【{}】用户进入, 当前连接数:{}", username, sessions.size()) ;     }        @OnClose      public void onClose(Session session, CloseReason reason){        UserSession userSession = sessions.remove(this.username) ;      if (userSession != null) {        logger.info("用户【{}】, 断开连接, 当前连接数:{}", username, sessions.size()) ;      }    }        @OnMessage    public void pongMessage(Session session, PongMessage message) {      ByteBuffer buffer = message.getApplicationData() ;      logger.debug("接受到Pong帧【这是由浏览器发送】:" + buffer.toString());    }        @OnMessage    public void onMessage(Session session, AbstractMessage message) {      if (message instanceof PingMessage) {        logger.debug("这里是ping消息");        return ;      }      if (message instanceof PersonMessage) {        PersonMessage personMessage = (PersonMessage) message ;        if (this.username.equals(personMessage.getToName())) {          logger.info("【{}】收到消息:{}", this.username, personMessage.getContent());        } else {          UserSession userSession = sessions.get(personMessage.getToName()) ;          if (userSession != null) {            try {            userSession.getSession().getAsyncRemote().sendText(new ObjectMapper().writeValueAsString(message)) ;          } catch (JsonProcessingException e) {            e.printStackTrace();          }          }        }        return ;      }      if (message instanceof SystemMessage) {        logger.info("接受到消息类型为【系统消息】") ;         return ;      }    }        @OnError    public void onError(Session session, Throwable error) {      logger.error(error.getMessage()) ;    }}

WsMessageEncoder.java类
该类的主要作用是,当发送的消息是对象时,该如何转换vdF28资讯网——每日最新资讯28at.com

public class WsMessageEncoder implements Encoder.Text<AbstractMessage> {  private static Logger logger = LoggerFactory.getLogger(WsMessageDecoder.class) ;  @Override  public void init(EndpointConfig endpointConfig) {  }  @Override  public void destroy() {  }  @Override  public String encode(AbstractMessage tm) throws EncodeException {    String message = null ;    try {      message = new ObjectMapper().writeValueAsString(tm);    } catch (JsonProcessingException e) {      logger.error("JSON处理错误:{}", e) ;    }    return message;  }}

WsMessageDecoder.java类
该类的作用是,当接收到消息时如何转换成对象。vdF28资讯网——每日最新资讯28at.com

public class WsMessageDecoder implements  Decoder.Text<AbstractMessage> {  private static Logger logger = LoggerFactory.getLogger(WsMessageDecoder.class) ;  private static Set<String> msgTypes = new HashSet<>() ;    static {    msgTypes.add(MessageType.PING.getType()) ;    msgTypes.add(MessageType.SYSTEM.getType()) ;    msgTypes.add(MessageType.PERSON.getType()) ;  }  @Override  @SuppressWarnings("unchecked")  public AbstractMessage decode(String s) throws DecodeException {    AbstractMessage message = null ;    try {      ObjectMapper mapper = new ObjectMapper() ;      Map<String,String> map = mapper.readValue(s, Map.class) ;      String type = map.get("type") ;      switch(type) {        case "0000":          message = mapper.readValue(s, SystemMessage.class) ;          break;        case "0001":          message = mapper.readValue(s, PingMessage.class) ;          break;        case "2001":          message = mapper.readValue(s, PersonMessage.class) ;          break;      }    } catch (JsonProcessingException e) {      logger.error("JSON处理错误:{}", e) ;    }    return message ;  }  // 该方法判断消息是否可以被解码(转换)  @Override  @SuppressWarnings("unchecked")  public boolean willDecode(String s) {    Map<String, String> map = new HashMap<>() ;    try {      map = new ObjectMapper().readValue(s, Map.class);    } catch (JsonProcessingException e) {      e.printStackTrace();    }    logger.debug("检查消息:【" + s + "】是否可以解码") ;    String type = map.get("type") ;    if (StringUtils.isEmpty(type) || !msgTypes.contains(type)) {      return false ;    }    return true ;  }  @Override  public void init(EndpointConfig endpointConfig) {  }  @Override  public void destroy() {  }}

MessageConfigurator.java类
该类的作用是配置服务端点,比如配置握手信息vdF28资讯网——每日最新资讯28at.com

public class MessageConfigurator extends ServerEndpointConfig.Configurator {  private static Logger logger = LoggerFactory.getLogger(MessageConfigurator.class) ;  @Override  public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {    logger.debug("握手请求头信息:" + request.getHeaders());    logger.debug("握手响应头信息:" + response.getHeaders());    super.modifyHandshake(sec, request, response);  }  }

WebSocke配置类

@Configurationpublic class WebSocketConfig {    @Bean    public ServerEndpointExporter serverEndpointExporter (){          return new ServerEndpointExporter();      }    }

当以jar包形式运行时需要配置该bean,暴露我们配置的@ServerEndpoint;当我们以war独立tomcat运行时不能配置该bean。vdF28资讯网——每日最新资讯28at.com

前端页面

<!doctype html><html> <head>  <meta charset="UTF-8">  <meta name="Author" content="">  <meta name="Keywords" content="">  <meta name="Description" content="">  <script src="g-messages.js?v=1"></script>  <title>WebSocket</title>  <style type="text/css"></style>  <script>  let gm = null ;  let username = null ;  function ListenerMsg({url, protocols = ['gmsg'], options = {}}) {    if (!url){       throw new Error("未知服务地址") ;    }    gm = new window.__GM({      url: url,      protocols: protocols    }) ;    gm.open(options) ;  }  ListenerMsg.init = (user) => {    if (!user) {      alert("未知的当前登录人") ;      return ;    }    let url = `ws://localhost:8080/message/${user}` ;    let msg = document.querySelector("#msg")    ListenerMsg({url, options: {      onmessage (e) {        let data = JSON.parse(e.data) ;        let li = document.createElement("li") ;        li.innerHTML = "【" + data.fromName + "】对你说:" + data.content ;        msg.appendChild(li) ;      }    }}) ;  }  function enter() {    username = document.querySelector("#nick").value ;    ListenerMsg.init(username) ;    document.querySelector("#chat").style.display = "block" ;    document.querySelector("#enter").style.display = "none" ;    document.querySelector("#cu").innerText = username ;  }  function send() {    let a = document.querySelector("#toname") ;    let b = document.querySelector("#content") ;    let toName = a.value ;    let content = b.value ;    gm.sendMessage({type: "2001", content, fromName: username, toName}) ;    a.value = '' ;    b.value = '' ;  }</script> </head> <body>  <id="enter">    <input id="nick"/><button type="button" onclick="enter()">进入</button>  </div>  <hr/>  <id="chat" style="display:none;">    当前用户:<b id="cu"></b><br/>    用户:<input id="toname" name="toname"/><br/><br/>    内容:<textarea id="content" rows="3" cols="22"></textarea><br/>    <button type="button" onclick="send()">发送</button>  </div>  <div>    <ul id="msg">    </ul>  </div> </body></html>

这里有个g-messages.js文件是我写的一个工具类,用来做连接及心跳检查用的。vdF28资讯网——每日最新资讯28at.com

到此所有的代码完毕,接下来测试vdF28资讯网——每日最新资讯28at.com

测试

打开两个标签页,以不同的用户进入。vdF28资讯网——每日最新资讯28at.com

图片vdF28资讯网——每日最新资讯28at.com


vdF28资讯网——每日最新资讯28at.com

图片vdF28资讯网——每日最新资讯28at.com


vdF28资讯网——每日最新资讯28at.com

输入对方用户名发送消息vdF28资讯网——每日最新资讯28at.com

图片vdF28资讯网——每日最新资讯28at.com


vdF28资讯网——每日最新资讯28at.com

图片vdF28资讯网——每日最新资讯28at.com


vdF28资讯网——每日最新资讯28at.com

成功了,简单的websocket。我们生产环境还就这么完的,8g内存跑了6w的用户。vdF28资讯网——每日最新资讯28at.com

本文链接:http://www.28at.com/showinfo-26-5718-0.htmlSpringBoot使用WebSocket实现即时消息

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 停止过度设计中等规模的前端应用程序

下一篇: 提升Go的HTTP路由器的提案

标签:
  • 热门焦点
  • Find N3入网:最高支持16+1TB

    OPPO将于近期登场的Find N3折叠屏目前已经正式入网,型号为PHN110。本次Find N3在外观方面相比前两代有很大的变化,不再是小号的横向折叠屏,而是跟别的厂商一样采用了较为常见的
  • 一加Ace2 Pro真机揭晓 钛空灰配色质感拉满

    终于,在经过了几波预热之后,一加Ace2 Pro的外观真机图在网上出现了。还是博主数码闲聊站曝光的,这次的外观设计还是延续了一加11的方案,只是细节上有了调整,例如新加入了钛空灰
  • 官方承诺:K60至尊版将会首批升级MIUI 15

    全新的MIUI 15今天也有了消息,在官宣了K60至尊版将会搭载天玑9200+处理器和独显芯片X7的同时,Redmi给出了官方承诺,K60至尊重大更新首批升级,会首批推送MIUI 15。也就是说虽然
  • 5月安卓手机好评榜:魅族20 Pro夺冠

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年5月1日至5月31日,仅限国内市场。第一名:魅族20 Pro好评率:97.50%不得不感慨魅族老品牌还
  • JVM优化:实战OutOfMemoryError异常

    一、Java堆溢出堆内存中主要存放对象、数组等,只要不断地创建这些对象,并且保证 GC Roots 到对象之间有可达路径来避免垃 圾收集回收机制清除这些对象,当这些对象所占空间超过
  • 东方甄选单飞:有些鸟注定是关不住的

    文/彭宽鸿编辑/罗卿东方甄选创始人俞敏洪带队的&ldquo;7天甘肃行&rdquo;直播活动已在近日顺利收官。成立后一年多时间里,东方甄选要脱离抖音自立门户的传闻不绝于耳,&ldquo;7
  • 网传小米汽车开始筛选交付中心 建筑面积不低于3000平方米

    7月7日消息,近日有微博网友@长三角行健者爆料称,据经销商集团反馈,小米汽车目前已经开始了交付中心的筛选工作,要求候选场地至少有120个车位,建筑不能低
  • 半导体需求下滑 三星电子DS业务部门今年营业亏损预计超10万亿韩元

    7月17日消息,据外媒报道,去年下半年开始的半导体需求下滑,影响到了三星电子、SK海力士、英特尔等诸多厂商,营收明显下滑,部分厂商甚至出现了亏损。作为
  • AI艺术欣赏体验会在上海梅赛德斯奔驰中心音乐俱乐部上演

    光影交错的镜像世界,虚实幻化的视觉奇观,虚拟偶像与真人共同主持,这些场景都出现在2019世界人工智能大会的舞台上。8月29日至31日,“AI艺术欣赏体验会”在上海
Top