背景

之前再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. odoo开发环境搭建(二):安装Ubuntu 17虚拟机

    odoo开发环境搭建(二):安装Ubuntu 17虚拟机 下载镜像文件: 配置网络: 安装vmware tools: 配置共享文件夹: 选中虚拟机,右键编辑设置里边配置共享文件夹,指定windows本 ...

  2. 【原创】Your Connection is not private

    用Chrome打开google等https网站时碰到问题: “your connection is not private”. 后来发现是跟GoAgent的安全证书有关系(我用XX.NETFQ) 解决 ...

  3. Android Design Support Library——Navigation View

    前沿 Android 从5.0开始引入了Material design元素的设计,这种新的设计语言让整个安卓的用户体验焕然一新,google在Android Design Support Librar ...

  4. 使用ssh-keygen生成ssh公钥和私钥

    默认是在用户目录下: windows一般为 c:\User\username\.ssh下 linux一般为 /home/username/.ssh 生成的命令如下: ssh-keygen -t rsa ...

  5. 《Netty权威指南》(二)NIO 入门

    [TOC]   2.1 同步阻塞 I/O 采用 BIO 通信模型的服务器,通常由一个独立的 Acceptor 线程负责监听客户端的连接,它接收到客户端连接请求之后为每个客户端创建一个新的线程进行处理, ...

  6. java.rmi.server.ExportException: Port already in use: 1099; nested exception is

    现象 mac上tomcat启动时报错: java.rmi.server.ExportException: Port already in use: 1099; nested exception is. ...

  7. java Multimap

    实现 { "a": [ , , ], "b": [ , ] } 当然, HashMap<String, List<Integer>> 是 ...

  8. [转]【mysql监控】查看mysql库大小,表大小,索引大小

    本文转自:http://blog.sina.com.cn/s/blog_4c197d420101fbl9.html 查看所有库的大小 mysql> use information_schema; ...

  9. ASP.NET MVC加载用户控件后并获取其内控件值或赋值

    有网友看了这篇<ASP.NET MVC加载ASCX之后,并为之赋值>http://www.cnblogs.com/insus/p/3643254.html 之后,问及Insus.NET,不 ...

  10. Async和await关键字的用法

    async & await 的前世今生(Updated) 1. 方法打上Async关键字, 就可以使用await调用别的Async方法了 2. 记得在需要异步执行的方法里面调用await或者n ...