Springboot + WebSocket + HTML 搭建简易的在线聊天
初衷
在以前的公司呆的时候,只用WebSocket用来做过游戏服务端,只知道怎么用,但没有做过IM类的东西;已经有段时间没接触过长链了,这次就用WebSocket整个简易的聊天,当作复习下。
WebSocket的原理
这个就麻烦大伙自行百度了,这里不用CV大法了。
最终的效果图
广播和私聊
实现了广播和私聊,指定私聊号就可以单对单聊天,不指定则为群发广播消息。
后端代码
后端用了Springboot框架,因为够快。上代码…
1.WebSocket配置
package com.ssss.websocket.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 开启WebSocket支持
* Created by L on 2020/6/8.
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
2.WebSocket核心
package com.ssss.websocket.controller;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.ssss.websocket.json.MessageJson;
import com.ssss.websocket.utils.MyDateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.concurrent.ConcurrentHashMap;
/**
* Created by L on 2020/6/8.
*/
@ServerEndpoint("/imServer/{username}")
@Component
@Slf4j
public class WebSocketServer {
/**
* 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
*/
private static int onlineCount = 0;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
*/
private static ConcurrentHashMap<String, WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 接收username
*/
private String username = "";
@PostConstruct
public void init() {
System.out.println(">>>>>>>>>websocket 加载");
}
/**
* 连接建立成功调用的方法
*
* @param session
* @param username
*/
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
this.session = session;
this.username = username;
String sessionId = session.getId();
if (webSocketMap.containsKey(sessionId)) {
webSocketMap.remove(sessionId);
webSocketMap.put(sessionId, this);
} else {
webSocketMap.put(sessionId, this);
addOnlineCount();
}
log.info("用户连接:{},当前在线人数为:{}", username, getOnlineCount());
broadcast("用户:" + username + " 已上线,私聊号为:" + sessionId + "。当前在线人数为:" + getOnlineCount());
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
String sessionId = this.session.getId();
if (webSocketMap.containsKey(sessionId)) {
webSocketMap.remove(sessionId);
subOnlineCount();
}
log.info("用户:{} 退出,当前在线人数为:{}", username, getOnlineCount());
broadcast("用户:" + username + " 已下线,当前在线人数为:" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message
* @param session
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("用户消息:" + username + ",内容:" + message);
if (StringUtils.isNotBlank(message)) {
try {
//解析前端发送的报文
JSONObject jsonObject = JSON.parseObject(message);
// 消息类型type: one/all
String type = jsonObject.getString("type");
// 消息内容
String messageModel = jsonObject.getString("message");
// 消息私聊接收方userId
String toUserId = jsonObject.getString("toUserId");
String currentTime = MyDateUtils.getDateToStr(new Date(), MyDateUtils.DATETIME_DEFAULT_FORMAT);
String fromUserId = session.getId();
if (StringUtils.isNotEmpty(type) && "all".equals(type)) {
//广播
webSocketMap.forEach((userId, server) -> {
try {
MessageJson messageJson = new MessageJson();
messageJson.setType(fromUserId.equals(userId) ? "0" : "1");
messageJson.setUsername(this.username);
messageJson.setMsg(messageModel);
messageJson.setCurrentTime(currentTime);
server.sendMessage(JSON.toJSONString(messageJson));
} catch (IOException e) {
log.error("广播请求的userId:{}不在该服务器上", userId);
}
});
return;
}
//私聊
//传送给对应toUserId用户的websocket
if (StringUtils.isNotBlank(toUserId) && webSocketMap.containsKey(toUserId)) {
MessageJson messageJson = new MessageJson();
messageJson.setUsername(this.username);
messageJson.setMsg(messageModel);
messageJson.setCurrentTime(currentTime);
//发送给自己
WebSocketServer webSocketServerA = webSocketMap.get(fromUserId);
messageJson.setType("0");
webSocketServerA.sendMessage(JSON.toJSONString(messageJson));
//发送给对方
WebSocketServer webSocketServerB = webSocketMap.get(toUserId);
messageJson.setType("1");
webSocketServerB.sendMessage(JSON.toJSONString(messageJson));
} else {
log.error("请求私聊号toUserId:{}不在该服务器上", toUserId);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 报错
*
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("用户错误:{},原因:{}", this.username, error.getMessage());
error.printStackTrace();
}
/**
* 实现服务器主动推送
*
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 广播群发消息
*
* @param message
*/
public void broadcast(String message) {
webSocketMap.forEach((userId, server) -> {
try {
server.sendMessage(message);
} catch (IOException e) {
log.error("广播请求的userId:{}不在该服务器上", userId);
}
});
}
/**
* 发送自定义消息
*
* @param message
* @throws IOException
*/
public static void sendInfo(String message) throws IOException {
webSocketMap.forEach((id, server) -> {
try {
server.sendMessage("超级管理员:" + message + " | " + MyDateUtils.getDateToStr(new Date(), MyDateUtils.DATETIME_DEFAULT_FORMAT));
} catch (IOException e) {
}
});
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
public static synchronized ConcurrentHashMap<String, WebSocketServer> getMap() {
return WebSocketServer.webSocketMap;
}
}
前端代码
前端只用html写了个简单的交互
<html>
<head>
<title>Springboot+WebSocket的聊天</title>
</head>
<body>
Welcome<br/>
<input type="text" id="nickname"/>
<button onclick="connectWebSocket()">连接WebSocket</button>
<button onclick="closeWebSocket()">关闭WebSocket连接</button>
<hr/>
私聊号:<input id="toUserId" type="text" style="width: 50px"/><br/>
消息:<input id="text" type="text" style="width: 500px"/>
<button onclick="send()">发送消息</button>
<hr/>
<div id="message" style="border: 2px solid #979797; width: 50%; height: 400px; overflow-y:auto;"></div>
</body>
<script type="text/javascript">
var websocket = null;
function connectWebSocket() {
var nickname = document.getElementById("nickname").value;
if (nickname === "") {
alert("请输入昵称");
return;
}
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://127.0.0.1:2306/imServer/" + nickname);
} else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket连接发生错误");
};
//连接成功建立的回调方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket连接成功");
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//连接关闭的回调方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
}
//将消息显示在网页上
function setMessageInnerHTML(innerHTML) {
try {
var jsonObj = JSON.parse(innerHTML);
var username = jsonObj.username;
var type = jsonObj.type;
//消息类型type:0.自己,1.别人
if ("0" === type) {
document.getElementById('message').innerHTML += '<div style="text-align: center">' + jsonObj.currentTime + '</div>';// 消息发送时间
document.getElementById('message').innerHTML += '<div style="text-align: right;">' + "我" + '</div>';// 消息发送人
document.getElementById('message').innerHTML += '<div style="text-align: right; margin-right: 30px;">' + jsonObj.msg + '</div>';// 内容
document.getElementById('message').scrollTop = document.getElementById('message').scrollHeight;// 当出现滚动条时,滚动条将自动保持在底部
} else {
document.getElementById('message').innerHTML += '<div style="text-align: center">' + jsonObj.currentTime + '</div>';// 消息发送时间
document.getElementById('message').innerHTML += '<div style="text-align: left;">' + username + '</div>';// 消息发送人
document.getElementById('message').innerHTML += '<div style="text-align: left; margin-left: 30px;">' + jsonObj.msg + '</div>';// 内容
document.getElementById('message').scrollTop = document.getElementById('message').scrollHeight;// 当出现滚动条时,滚动条将自动保持在底部
}
} catch (e) {
document.getElementById('message').innerHTML += '<div>' + innerHTML + '</div>';
}
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
}
//发送消息
function send() {
var message = document.getElementById('text').value;
var toUserId = document.getElementById('toUserId').value;
var jsonObj = {message: message, toUserId: toUserId};
if (toUserId === '') {
jsonObj.type = 'all';
} else {
jsonObj.type = 'one';
}
websocket.send(JSON.stringify(jsonObj));
}
</script>
</html>
运行
1.运行后端Springboot项目
2.打开html页
简易的聊天就好了,下面看交互效果。
交互效果
广播聊
开三个窗口,名字分别为:“东东”,“西西”和“灯泡”
私聊,通过私聊号
可以看出,后面通过了私聊,名为“灯泡”的男子,已经看不到另外两个人的聊天。
总结
例子只是用WebSocket简易建立起实时聊天,还有很多要优化的地方,这个以后有空再处理。源码莫得地方上传,网络被限制了github,没法上传,恶心了,后续再处理。