背景

之前再SpringBoot2.0集成WebSocket,实现后台向前端推送信息中已经进行过一次demo,而这次的demo更加明确,优化了相关代码,为IM而生

前提

前提当然是导入相关的包,以及配置WebSocketConfig.java,请用上篇文章的内容即可。这里只做优化。

实战

例如从CopyOnWriteArraySet改为ConcurrentHashMap,保证多线程安全同时方便利用map.get(userId)进行推送到指定端口。

相比之前的Set,Set遍历是费事且麻烦的事情,而Map的get是简单便捷的,当WebSocket数量大的时候,这个小小的消耗就会聚少成多,影响体验,所以需要优化。

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint; import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.softdev.system.likeu.util.ApiReturnUtil;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import lombok.extern.slf4j.Slf4j; @ServerEndpoint("/im/{userId}")
@Component
public class ImController { static Log log=LogFactory.get(ImController.class);
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = ;
//旧:concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//private static CopyOnWriteArraySet<ImController> webSocketSet = new CopyOnWriteArraySet<ImController>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//新:使用map对象,便于根据userId来获取对应的WebSocket
private static ConcurrentHashMap<String,ImController> websocketList = new ConcurrentHashMap<>();
//接收sid
private String userId="";
/**
* 连接建立成功调用的方法*/
@OnOpen
public void onOpen(Session session,@PathParam("userId") String userId) {
this.session = session;
websocketList.put(userId,this);
log.info("websocketList->"+JSON.toJSONString(websocketList));
//webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
log.info("有新窗口开始监听:"+userId+",当前在线人数为" + getOnlineCount());
this.userId=userId;
try {
sendMessage(JSON.toJSONString(ApiReturnUtil.success("连接成功")));
} catch (IOException e) {
log.error("websocket IO异常");
}
} /**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
if(websocketList.get(this.userId)!=null){
websocketList.remove(this.userId);
//webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
} /**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+userId+"的信息:"+message);
if(StringUtils.isNotBlank(message)){
JSONArray list=JSONArray.parseArray(message);
for (int i = ; i < list.size(); i++) {
try {
//解析发送的报文
JSONObject object = list.getJSONObject(i);
String toUserId=object.getString("toUserId");
String contentText=object.getString("contentText");
object.put("fromUserId",this.userId);
//传送给对应用户的websocket
if(StringUtils.isNotBlank(toUserId)&&StringUtils.isNotBlank(contentText)){
ImController socketx=websocketList.get(toUserId);
//需要进行转换,userId
if(socketx!=null){
socketx.sendMessage(JSON.toJSONString(ApiReturnUtil.success(object)));
//此处可以放置相关业务代码,例如存储到数据库
}
}
}catch (Exception e){
e.printStackTrace();
}
}
}
} /**
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
/**
* 实现服务器主动推送
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
} /**
* 群发自定义消息
* */
/*public static void sendInfo(String message,@PathParam("userId") String userId) throws IOException {
log.info("推送消息到窗口"+userId+",推送内容:"+message);
for (ImController item : webSocketSet) {
try {
//这里可以设定只推送给这个sid的,为null则全部推送
if(userId==null) {
item.sendMessage(message);
}else if(item.userId.equals(userId)){
item.sendMessage(message);
}
} catch (IOException e) {
continue;
}
}
}*/ public static synchronized int getOnlineCount() {
return onlineCount;
} public static synchronized void addOnlineCount() {
ImController.onlineCount++;
} public static synchronized void subOnlineCount() {
ImController.onlineCount--;
}
}

网页

这里的路径是写死的,反正你如果有freemarker最好是根据${request.contextPath}这种动态变量来获取。

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>websocket通讯</title>
</head>
<script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.js"></script>
<script>
var socket;
function openSocket() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else{
console.log("您的浏览器支持WebSocket");
//实现化WebSocket对象,指定要连接的服务器地址与端口 建立连接
//等同于socket = new WebSocket("ws://localhost:8888/xxxx/im/25");
//var socketUrl="${request.contextPath}/im/"+$("#userId").val();
var socketUrl="http://localhost:8888/xxxx/im/"+$("#userId").val();
socketUrl=socketUrl.replace("https","ws").replace("http","ws");
console.log(socketUrl)
socket = new WebSocket(socketUrl);
//打开事件
socket.onopen = function() {
console.log("websocket已打开");
//socket.send("这是来自客户端的消息" + location.href + new Date());
};
//获得消息事件
socket.onmessage = function(msg) {
console.log(msg.data);
//发现消息进入 开始处理前端触发逻辑
};
//关闭事件
socket.onclose = function() {
console.log("websocket已关闭");
};
//发生了错误事件
socket.onerror = function() {
console.log("websocket发生了错误");
}
}
}
function sendMessage() {
if(typeof(WebSocket) == "undefined") {
console.log("您的浏览器不支持WebSocket");
}else {
console.log("您的浏览器支持WebSocket");
console.log('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
socket.send('[{"toUserId":"'+$("#toUserId").val()+'","contentText":"'+$("#contentText").val()+'"}]');
}
}
</script>
<body>
<p>【userId】:<div><input id="userId" name="userId" type="text" value=""></div>
<p>【toUserId】:<div><input id="toUserId" name="toUserId" type="text" value=""></div>
<p>【toUserId】:<div><input id="contentText" name="contentText" type="text" value="嗷嗷嗷"></div>
<p>【操作】:<div><a onclick="openSocket()">开启socket</a></div>
<p>【操作】:<div><a onclick="sendMessage()">发送消息</a></div>
</body> </html>

效果

SpringBoot2+WebSocket之聊天应用实战(优化版本)的更多相关文章

  1. Ext JS学习第十六天 事件机制event(一) DotNet进阶系列(持续更新) 第一节:.Net版基于WebSocket的聊天室样例 第十五节:深入理解async和await的作用及各种适用场景和用法 第十五节:深入理解async和await的作用及各种适用场景和用法 前端自动化准备和详细配置(NVM、NPM/CNPM、NodeJs、NRM、WebPack、Gulp/Grunt、G

    code&monkey   Ext JS学习第十六天 事件机制event(一) 此文用来记录学习笔记: 休息了好几天,从今天开始继续保持更新,鞭策自己学习 今天我们来说一说什么是事件,对于事件 ...

  2. Netty网络聊天(一) 聊天室实战

    首发地址; Netty网络聊天(一) 聊天室实战 之前做过一个IM的项目,里面涉及了基本的聊天功能,所以注意这系列的文章不是练习,不含基础和逐步学习的部分,直接开始实战和思想引导,基础部分需要额外的去 ...

  3. Node+Express+MongoDB+Socket.io搭建实时聊天应用实战教程(一)--MongoDB入门

    前言 本文并不是网上流传的多少天学会MongoDB那种全面的教程,而意在总结这几天使用MongoDB的心得,给出一个完整的Node+Express+MongoDB+Socket.io搭建实时聊天应用实 ...

  4. Netty 仿QQ聊天室 (实战二)

    Netty 聊天器(百万级流量实战二):仿QQ客户端 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之15 [博客园 总入口 ] 源码IDEA工程获取链接:Java 聊天室 实战 源码 写在 ...

  5. 基于websocket vue 聊天demo 解决方案

    基于websocket vue 聊天demo 解决方案 demo 背景 电商后台管理的客服 相关技术 vuex axios vue websocket 聊天几种模型 一对一模型 一对一 消息只一个客户 ...

  6. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室 实战系列。开源啦!!!

    自此系列博客开写以来,好多同学关心开源问题,之前由于网络问题,发布到Github上老是失败,今天终于在精简了好多无用的文件之后发布上去了. 注意:layim源代码并不开源,由于版权问题,请大家去官网了 ...

  7. wzplayer for ios 针对(mms)优化版本V1.0

    wzplayer for ios针对mms优化版本发布. 1.支持mms,http,rtmp,rtsp等协议 2.支持全格式 下载地址:http://www.coolradio.cn/WzPlayer ...

  8. JEECG 3.8宅男优化版本发布

    1024程序员节宅男节日快乐 -- JAVA快速开发平台,JEECG 3.8宅男优化版本发布 - JEECG开源社区 - CSDN博客https://blog.csdn.net/zhangdaisco ...

  9. 基于WebSocket实现聊天室(Node)

    基于WebSocket实现聊天室(Node) WebSocket是基于TCP的长连接通信协议,服务端可以主动向前端传递数据,相比比AJAX轮询服务器,WebSocket采用监听的方式,减轻了服务器压力 ...

随机推荐

  1. Nginx 简易教程

    Nginx 本项目是一个 Nginx 极简教程,目的在于帮助新手快速入门 Nginx. demos 目录中的示例模拟了工作中的一些常用实战场景,并且都可以通过脚本一键式启动,让您可以快速看到演示效果. ...

  2. ActiveMq使用笔记

    java JMS技术 .1.   什么是JMS JMS即Java消息服务(Java Message Service)应用程序接口是一个Java平台中关于面向消息中间件(MOM)的API,用于在两个应用 ...

  3. ES代码总结2

    本文部分转载于: http://www.cnblogs.com/luxiaoxun/p/4869509.html ElasticSearch的基本用法与集群搭建  一.简介 ElasticSearch ...

  4. echarts初探

    最近经常看到echarts,觉得很有意思,并且这个库是百度开发的,目前来说使用的也很广泛,包括百度.阿里.腾讯.网易.小米.新浪.华为.联想.美团等一大批一线互联网公司在使用,且github上的sta ...

  5. 关于一点儿对仓储(Repository)的理解

    仓储(Repository) 内容来源于dudu的 关于Repository模式一文 Repository是一个独立的层,介于领域层与数据映射层(数据访问层)之间.它的存在让领域层感觉不到数据访问层的 ...

  6. mysql-unsha1:在未知密码情况下,登录任意MYSQL数据库

    摘要 这个POC用于在不知道明文密码的情况下对启用了密码安全认证插件(默认开启插件:mysql_native_password)的MYSQL数据库进行登录. 前提条件为: 1.为了获取到已知用户的ha ...

  7. kafka 日志结构

    1.kafka日志结构 直接举例子: 例如kafka有个名字叫 haha 的topic,那么kafka日志下面有kafka-0,kafka-1,kafka-2...,kafka-n,具体多少个,创建分 ...

  8. Conditional特性用法

    说明:根据预处理标识符执行方法.Conditional 特性是 ConditionalAttribute 的别名,可应用于方法或属性类.相对于#if和#endif,更灵活更简洁和不易出错. 例如: # ...

  9. jdbc mysql driver 6.0.2

    url = jdbc:mysql://localhost:3306/hibernate?useUnicode=true&characterEncoding=UTF-8&useLegac ...

  10. C#获取电脑型号、系统版本、内存大小、硬盘大小、CPU信息

    摘要 有时需要获取电脑的相关信息.这时可以通过调用windows api的方式,进行获取. 方法 可以通过在powershell中 通过下面的命令进行查询,然后可以通过c#调用获取需要的信息. gwm ...