背景

之前再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. How To Scan QRCode For UWP (3)

    这一节主要介绍如何去设置MediaCapture拍照的分辨率. MediaCapture 包含一个 VideoDeviceController对象,凭借它可以控制摄像头的很多设置,其中包括设置拍照的分 ...

  2. Java实现二分法排序

    二分法:(二分法不是只能做数组,这里的数组只是为了举例) 在给出的有序排列的数组中,把目标值和数组中间值进行比较,如果相等,则返回中间值下标,如果目标值小于中间值,就从数组的前半段再次执行二分法查找, ...

  3. eclipse下搭建shell脚本编辑器--安装开发shell的eclipse插件shelled

    具体请看: 亲测有效: http://www.cnblogs.com/shellshell/p/6122811.html

  4. React VR 技术开发群 579149907

    React VR 技术开发群 579149907,欢迎加入讨论!分享经验!

  5. ASP.NET Core 1.0 中使用 Log 日志配置

    https://github.com/aspnet/Logging https://docs.asp.net/en/latest/fundamentals/logging.html ASP.NET C ...

  6. 分布式锁的两种实现方式(基于redis和基于zookeeper)

    先来说说什么是分布式锁,简单来说,分布式锁就是在分布式并发场景中,能够实现多节点的代码同步的一种机制.从实现角度来看,主要有两种方式:基于redis的方式和基于zookeeper的方式,下面分别简单介 ...

  7. CVPR2019 | Mask Scoring R-CNN 论文解读

    Mask Scoring R-CNN CVPR2019 | Mask Scoring R-CNN 论文解读 作者 | 文永亮 研究方向 | 目标检测.GAN 推荐理由: 本文解读的是一篇发表于CVPR ...

  8. apk的php解析

    <?php/****************************************************** * Android APK File Parser * Author: ...

  9. layer插件学习——提示层

    本文是自己整理的关于layer插件的提示层的结果 一.准备工作 下载jQuery插件和layer插件,并引入插件(注意:jQuery插件必须在layer插件之前引用) 百度云资源链接: jQuery插 ...

  10. SpringMvc @JsonView 使用方式

    准确来说,@JsonView注解不是Spring的,它位于jackson-annotation包中: 作用:SpringMvc使用@ResponseBody将结果以json返回客户端,  有些实体类的 ...