springboot+websocket实现聊天即时通讯
1.在很多业务场景中,对实时数据要求比较高,我们就不能采用轮训拉取的方式来获取数据了。就可以采用websocket的长链接的形式,实时有服务端或者客户端推送数据,已达到数据的实时展示。
目录
WebSocketConfig
package com.example.springboot_websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class WebSocketConfig {
/**
* 服务器节点
*
* 如果使用独立的servlet容器,而不是直接使用springboot的内置容器,就不要注入ServerEndpointExporter,因为它将由容器自己提供和管理
* @return
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebsocketController
package com.example.springboot_websocket.controller;
import com.alibaba.fastjson.JSON;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* websocket类
* @ServerEndpoint: socket链接地址
*/
@ServerEndpoint("/websocket/{username}")
@Controller
public class WebsocketController {
private Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 在线人数
*/
public static int onlineNumber = 0;
/**
* 以用户的姓名为key,WebSocket为对象保存起来
*/
private static Map<String, WebsocketController> clients = new ConcurrentHashMap<String, WebsocketController>();
/**
* 会话
*/
private Session session;
/**
* 用户名称
*/
private String username;
/**
* 进入聊天室 --> 项目中读取用户信息获取用户名
*/
@RequestMapping("/websocket")
public String webSocket(Model model) {
//定义随机时间戳名称
String name = "游客:";
//String datename = new SimpleDateFormat("yyyyMMddHHmmsss").format(new Date());
String datename = new SimpleDateFormat("msss").format(new Date());
name = name + datename;
//websock链接地址+游客名--> 项目中请定义在配置文件 -->或直接读取服务器,ip 端口
// 读取服务器,ip 端口可看:
String path="ws://127.0.0.1:8080/websocket/";
model.addAttribute("path",path);
model.addAttribute("username",name);
return "socket";
}
/**
* 监听连接(有用户连接,立马到来执行这个方法)
* session 发生变化
*
* @param session
*/
@OnOpen
public void onOpen(@PathParam("username") String username, Session session) {
onlineNumber++;
//把新用户名赋给变量
this.username = username;
//把新用户的 session 信息赋给变量
this.session = session;
//输出 websocket 信息
logger.info("现在来连接的客户id:" + session.getId() + "用户名:" + username);
logger.info("有新连接加入! 当前在线人数" + onlineNumber);
try {
//把自己的信息加入到map当中去,this=当前类(把当前类作为对象保存起来)
clients.put(username, this);
//获得所有的用户
Set<String> lists = clients.keySet();
// 先给所有人发送通知,说我上线了
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String, Object> map1 = new HashMap();
// 把所有用户列表
map1.put("onlineUsers", lists);
// 返回上线状态
map1.put("messageType", 1);
// 返回用户名
map1.put("username", username);
// 返回在线人数
map1.put("number", onlineNumber);
// 发送全体信息(用户上线信息)
sendMessageAll(JSON.toJSONString(map1), username);
// 给自己发一条消息:告诉自己现在都有谁在线
Map<String, Object> map2 = new HashMap();
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
map2.put("messageType", 3);
//把所有用户放入map2
map2.put("onlineUsers", lists);
//返回在线人数
map2.put("number", onlineNumber);
// 消息发送指定人(所有的在线用户信息)
sendMessageTo(JSON.toJSONString(map2), username);
} catch (IOException e) {
logger.info(username + "上线的时候通知所有人发生了错误");
}
}
/**
* 监听连接断开(有用户退出,会立马到来执行这个方法)
*/
@OnClose
public void onClose() {
onlineNumber--;
//所有在线用户中去除下线用户
clients.remove(username);
try {
//messageType 1代表上线 2代表下线 3代表在线名单 4代表普通消息
Map<String, Object> map1 = new HashMap();
map1.put("messageType", 2);
//所有在线用户
map1.put("onlineUsers", clients.keySet());
//下线用户的用户名
map1.put("username", username);
//返回在线人数
map1.put("number", onlineNumber);
//发送信息,所有人,通知谁下线了
sendMessageAll(JSON.toJSONString(map1), username);
} catch (IOException e) {
logger.info(username + "下线的时候通知所有人发生了错误");
}
logger.info("有连接关闭! 当前在线人数" + onlineNumber);
}
@OnError
public void onError(Session session, Throwable error) {
logger.info("服务端发生了错误" + error.getMessage());
//error.printStackTrace();
}
/**
* 监听消息(收到客户端的消息立即执行)
*
* @param message 消息
* @param session 会话
*/
@OnMessage
public void onMessage(String message, Session session) {
try {
logger.info("来自客户端消息:" + message + "客户端的id是:" + session.getId());
//用户发送的信息
com.alibaba.fastjson.JSONObject jsonObject = JSON.parseObject(message);
//发送的内容
String textMessage = jsonObject.getString("message");
//发送人
String fromusername = jsonObject.getString("username");
//接收人 to=all 发送消息给所有人 || to= !all to == 用户名
String tousername = jsonObject.getString("to");
//发送消息 -- messageType 1代表上线 2代表下线 3代表在线名单 4代表消息
Map<String, Object> map1 = new HashMap();
map1.put("messageType", 4);
map1.put("textMessage", textMessage);
map1.put("fromusername", fromusername);
if (tousername.equals("All")) {
//消息发送所有人(同步)
map1.put("tousername", "所有人");
sendMessageAll(JSON.toJSONString(map1), fromusername);
} else {
//消息发送指定人(同步)
map1.put("tousername", tousername);
sendMessageTo(JSON.toJSONString(map1), tousername);
}
} catch (Exception e) {
logger.info("发生了错误了");
}
}
/**
* 消息发送指定人
*/
public void sendMessageTo(String message, String ToUserName) throws IOException {
//遍历所有用户
for (WebsocketController item : clients.values()) {
if (item.username.equals(ToUserName)) {
//消息发送指定人(同步)
item.session.getBasicRemote().sendText(message);
break;
}
}
}
/**
* 消息发送所有人
*/
public void sendMessageAll(String message, String FromUserName) throws IOException {
for (WebsocketController item : clients.values()) {
//消息发送所有人(同步)getAsyncRemote
item.session.getBasicRemote().sendText(message);
}
}
public static synchronized int getOnlineCount() {
return onlineNumber;
}
}
/*
* 注解说明
* @MessageMapping(value = "/chat") // 匹配客户端 send 消息时的URL
* @SendTo("/topic/getResponse") //用于给客户端订阅广播消息
* @SendToUser(value = "/personal") //用于给客户端订阅点对点消息;
* @Payload:使用客户端 STOMP 帧的 body 赋值
* @Header(“xxx”):使用客户端 STOMP 帧的 headers 中的 xxx 赋值
*
**/
/**
* 广播推送
**/
// @MessageMapping(value = "/chat") // 匹配客户端 send 消息时的URL
// @SendTo("/topic/getResponse") //分别用于给客户端订阅广播消息
// public String talk(@Payload String text, @Header("simpSessionId") String sessionId) throws Exception {
// return "【" + sessionId + "】说:【" + text + "】";
// }
/**
* 点对点推送
*/
/*
@MessageMapping(value = "/speak") // 匹配客户端 send 消息时的URL
@SendToUser(value = "/personal") //分别用于给客户端订阅点对点消息;
public String speak(@Payload String text, @Header("simpSessionId") String sessionId) throws Exception {
return text;
}
*/
/**
* 异常信息推送
*/
/*
@MessageExceptionHandler
@SendToUser(value = "/errors")
public String handleException(Throwable exception) {
return exception.getMessage();
}*/
SpringbootWebsocketApplication
package com.example.springboot_websocket;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringbootWebsocketApplication {
public static void main(String[] args) {
SpringApplication.run(SpringbootWebsocketApplication.class, args);
}
}
socket.html
<!DOCTYPE html>
<html xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<meta name="renderer" content="webkit">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="stylesheet" href="../frame/layui/css/layui.css">
<link rel="stylesheet" href="../frame/static/css/style.css">
<link rel="icon" href="../frame/static/image/code.png">
<title>websocket</title>
<script type="text/javascript" src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.min.js"></script>
<script src="http://cdn.bootcss.com/stomp.js/2.3.3/stomp.min.js"></script>
<script src="https://cdn.bootcss.com/sockjs-client/1.1.4/sockjs.min.js"></script>
</head>
<css>
</css>
<body>
<!-- socket url -->
<input type="hidden" th:value="${path}" id="path" style="display: none" />
<!-- 用户名 -->
<input type="hidden" th:value="${username}" id="username" style="display: none" />
<!-- =============================================================================================================== -->
<br>
<!--<input type="hidden" value="所有人" id="onLineUser" text="所有人" style="display: none" />-->
<!-- =============================================================================================================== -->
<!-- overflow-y :auto;overflow :auto; 宽高自适应滚动条-->
<span id = "miqx" style="width:80%;height:300px; background-color: papayawhip;float:left;overflow-y :auto;overflow :auto;">
<li style="text-align: center">群聊信息</li>
</span>
<span id = "miax" style="width:20%;background-color: #F2F2F2;float:left;overflow-y :auto;overflow :auto;">
<li style="text-align: center">在线列表</li>
</span>
<textarea id="text" placeholder="请输入内容-发送消息[Ctrl+回车键]" rows="3%" cols="60%"></textarea>
<input onclick="send()" type="button" value="发送">
<td>消息发送至:</td>
<select id="onLineUser" size="1" style="width: 10%;height:30px">
<option value="所有人">所有人</option>
</select>
<div id = "mizx" style="width:80%;height:300px;background-color: #FFEEE8;float:left;overflow-y :auto;overflow :auto;">
<li style="text-align: center">私聊信息</li>
<!-- <li style="text-align: right">靠右</li>
<li style="text-align: left" >靠左</li>-->
</div>
<br>
<br>
<!-- =============================================================================== -->
</body>
<script type="text/javascript">
function uaername(name){
alert(name)
}
var miqx = $("#miqx"); //群聊
var miax = $("#miax"); //在线列表
var mizx = $("#mizx"); //私聊
var onLineUser = $("#onLineUser"); //发送人select选择框
var webSocket;
var commWebSocket;http:
if ("WebSocket" in window){
//192.168.100.7:8080/
webSocket = new WebSocket(document.getElementById('path').value +document.getElementById('username').value);
//连通之后的回调事件
webSocket.onopen = function()
{
miqx.html(miqx.html()+" <li style='text-align: center'>系统消息:[登陆成功]</li>")
};
//接收后台服务端的消息
webSocket.onmessage = function (evt)
{
var received_msg = evt.data; //接收到的数据
var obj = JSON.parse(received_msg); //json数据
var messageType = obj.messageType; //数据类型(1上线/2下线/3在线名单/4发信息)
var onlineName = obj.username; //用户名
var number = obj.number; //在线人数
//上线通知+在线列表刷新
if(obj.messageType==1){
if((onlineName != $("#username").val())){ //展示除不等于自己的所有用户
miqx.html(miqx.html()+" <li style='text-align: center'>系统消息:["+ onlineName+"]上线了"+"</li>");
onLineUser.html(onLineUser.html()+"<option value='"+ onlineName +"'>"+ onlineName +"</option>");
}
var onlineName = obj.onlineUsers; //所有在线用户
miax.html("<li style='text-align: center'>在线用户--["+ onlineName.length +"]</li>");
for(var i=0;i<onlineName.length;i++){
if((onlineName[i] != $("#username").val())){ //展示除不等于自己的所有用户
miax.html(miax.html()+"<li style='text-align: left'>---"+ onlineName[i] +"</li>" );
}
}
//miax.html(miax.html()+" <li style='text-align: center'>"+ onlineName +"</li>");
}
//下线通知+在线列表刷新
else if(obj.messageType==2){
if((onlineName != $("#username").val())){ //展示除不等于自己的所有用户
miqx.html(miqx.html()+" <li style='text-align: center'>系统消息:["+ onlineName+"]下线了"+"</li>");
}
var onlineName = obj.onlineUsers; //剩余所有在线用户
miax.html("<li style='text-align: center'>在线用户--["+ onlineName.length +"]</li>");
onLineUser.html("<option value='所有人'>所有人</option>");
for(var i=0;i<onlineName.length;i++){
if((onlineName[i] != $("#username").val())){ //展示除不等于自己的所有用户
miax.html(miax.html()+"<li style='text-align: left'>---"+ onlineName[i] +"</li>" );
onLineUser.html(onLineUser.html()+"<option value='"+ onlineName[i] +"'>"+ onlineName[i] +"</option>");
}
}
}
//在线列表
else if(obj.messageType==3){
var onlineName = obj.onlineUsers; //所有在线用户
miax.html("<li style='text-align: center'>在线用户--["+ onlineName.length +"]</li>");
onLineUser.html("<option value='所有人'>所有人</option>");
for(var i=0;i<onlineName.length;i++){
if(onlineName[i] != $("#username").val()){ //展示除不等于自己的所有用户
miax.html(miax.html()+ " <li style='text-align: left'>---"+ onlineName[i] +"</li>" );
onLineUser.html(onLineUser.html()+"<option value='"+ onlineName[i] +"'>"+ onlineName[i] +"</option>");
}
}
}
//信息接收
else{
var time2 = new Date();
var date = time2.getHours()+":"+time2.getMinutes()+":"+ time2.getSeconds(); //时间
if(obj.fromusername != $("#username").val() ){ //自己不接自己的消息
if(obj.tousername=="所有人"){
//发给所有人
miqx.html(miqx.html()+" <li style='text-align: left'>["+ obj.fromusername+"]说:-"+obj.textMessage +"</li>");
}else {
//发给指定人
mizx.html(mizx.html()+" <li style='text-align: left'>["+ obj.fromusername+"]说:-"+obj.textMessage+"</li>");
}
}
//setMessageInnerHTML(obj.fromusername+"对"+obj.tousername+"说:"+obj.textMessage);
}
};
//连接关闭的回调事件
webSocket.onclose = function()
{
console.log("连接已关闭...");
setMessageInnerHTML("连接已经关闭....");
};
}
else{
// 浏览器不支持 WebSocket
alert("您的浏览器不支持 WebSocket!");
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
function closeWebSocket() {
//直接关闭websocket的连接
webSocket.close();
}
//信息发送+ 页面显示发送信息
$(document).keyup(function(event){
//浏览器适应
if(event.ctrlKey && event.which == 13 || event.which == 10) {
send();
} else if (event.shiftKey && event.which==13 || event.which == 10) {
send();
}
});
//信息发送+ 页面显示发送信息
function send() {
var usernameX = $("#username").val() //发送数据人
var usernameY = $("#onLineUser").val(); //接收数据人
var message = $("#text").val(); //发送的数据
if(usernameY=="所有人"){
usernameY = "All";
/* <li style="text-align: center">群聊信息</li>
<li style="text-align: right">靠右</li>
<li style="text-align: left" >靠左</li>*/
miqx.html(miqx.html()+" <li style='text-align: right'>"+ message+" -- ["+usernameX +"]</li>");
}
else{
mizx.html(mizx.html()+" <li style='text-align: right'>"+ "你对-["+usernameY+"]说:-"+message+"</li>");
}
var message = {
"message":message,
"username":usernameX,
"to":usernameY
};
//发送数据
webSocket.send(JSON.stringify(message));
$("#text").val("");
}
layui.use(['form', 'layedit', 'laydate'], function () {
var form = layui.form
, layer = layui.layer
, layedit = layui.layedit
, laydate = layui.laydate;
//监听指定开关
form.on('switch(switchTest)', function (data) {
layer.msg('你以' + (this.checked ? '上线' : '下线'), {
offset: '6px'
});
//layer.tips('温馨提示:请注意开关状态的文字可以随意定义,而不仅仅是ON|OFF', data.othis)
});
});
</script>
</html>
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>springboot_websocket</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_websocket</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--解析html包 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- json 返回数据需要 -->
<!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- websocket 在线聊天支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
测试结果