用websocket实现简单的在线聊天,先画个时序图,直观感受下流程
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
/**
* @描述 WebSocket消息推送控制器
* @创建人 poneco
* @创建时间 2021/11/16
*/
@RestController
public class SystemController {
// 推送数据到websocket客户端 接口
@GetMapping("/socket/push/{cid}")
public Map pushMessage(@PathVariable("cid") String cid, String message) {
Map<String, Object> result = new HashMap<>();
try {
HashSet<String> sids = new HashSet<>();
sids.add(cid);
WebSocketServer.sendMessage("服务端推送消息:" + message, sids);
result.put("code", cid);
result.put("msg", message);
} catch (IOException e) {
e.printStackTrace();
}
return result;
}
}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* @描述 开启WebSocket支持的配置类
* @创建人 poneco
* @创建时间 2021/11/16
*/
@Configuration
public class WebSocket {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @描述 WebSocket核心配置类
* @创建人 poneco
* @创建时间 2021/11/16
*/
/**
* @ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端。
*/
@Component
@Slf4j
@Service
@ServerEndpoint("/api/websocket/{userID}/{touserID}")
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static AtomicInteger onlineCount = new AtomicInteger(0);
//concurrent包的线程安全Set,用来存放每个客户端对应的WebSocket对象。
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
// private static ConcurrentHashMap webSocketSet = new ConcurrentHashMap<>();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
//接收sid
private String userID = "";
private String touserID="";
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session, @PathParam("userID") String userID,@PathParam("touserID") String touserID) {
log.info(String.valueOf(session.getRequestURI()));
this.session = session;
this.userID = userID;
this.touserID = touserID;
log.info("接受者"+touserID);
webSocketSet.add(this); // 加入set中
addOnlineCount(); // 在线数加1
try {
sendMessage("conn_success");
log.info("有新客户端开始监听,sid=" + userID + ",当前在线人数为:" + getOnlineCount());
} catch (IOException e) {
log.error("websocket IO Exception");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); // 从set中删除
subOnlineCount(); // 在线数减1
// 断开连接情况下,更新主板占用情况为释放
log.info("释放的sid=" + userID + "的客户端");
releaseResource();
}
private void releaseResource() {
// 这里写释放资源和要处理的业务
log.info("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @Param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自客户端 sid=" + userID + " 的信息:" + message);
// 群发消息
HashSet<String> sids = new HashSet<>();
if(this.touserID.equals("1")){
for (WebSocketServer item : webSocketSet) {
sids.add(item.userID);
}
}else {
log.info("推送到:"+this.touserID);
sids.add(this.touserID);
}
try {
sendMessage(message, sids);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 发生错误回调
*/
@OnError
public void onError(Session session, Throwable error) {
log.error(session.getBasicRemote() + "客户端发生错误");
error.printStackTrace();
}
/**
* 群发自定义消息
*/
public static void sendMessage(String message, HashSet<String> toSids) throws IOException {
log.info("推送消息到客户端 " + toSids + ",推送内容:" + message);
JSONObject jsonObject = JSONObject.parseObject(message);
//webSocketSet.get();
for (WebSocketServer item : webSocketSet) {
try {
//这里可以设定只推送给传入的sid,为null则全部推送
if (toSids.size() <= 0) {
item.sendMessage(jsonObject.getString("msg"));
} else if (toSids.contains(item.userID)) {
item.sendMessage(jsonObject.getString("msg"));
}
} catch (IOException e) {
continue;
}
}
}
/**
* 实现服务器主动推送消息到 指定客户端
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
/**
* 获取当前在线人数
*
* @return
*/
public static int getOnlineCount() {
return onlineCount.get();
}
/**
* 当前在线人数 +1
*
* @return
*/
public static void addOnlineCount() {
onlineCount.getAndIncrement();
}
/**
* 当前在线人数 -1
*
* @return
*/
public static void subOnlineCount() {
onlineCount.getAndDecrement();
}
/**
* 获取当前在线客户端对应的WebSocket对象
*
* @return
*/
public static CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
return webSocketSet;
}
}
<!--websocket依赖包添加-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.12</version>
</dependency>
<!--日志依赖添加-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="JS/index.js" type="text/javascript"></script>
<link rel="stylesheet" href="CSS/style.css" type="text/css">
<style>
body{margin: 0px;margin: 0px;background-image: url("image/bk-login.png")}
</style>
<body>
<div align="center">
你的账户号<input type="text" id="userID" value="528409336">
接受者账户号<input type="text" id="touserID" value="2995460432">
<input type="button" onclick="con()" value="连接对方">
<hr>
<table class="table">
<tr>
<td>
<input type="text" value="78269" style="display: none" id="accentID">
<div style="height: 560px">
<div id="accentinformation"></div>
<div id="div">
</div>
</div>
</td>
</tr>
</table>
<table>
<tr>
<td><textarea id="text"></textarea></td>
<td><input type="button" style="width: 100px;height: 40px;background: #31acfb;border: none" onclick="send()" value="发送"></td>
</tr>
</table>
</div>
</body>
</html>
<script type="text/javascript">
var websocket = null;
function con(){
//判断当前websocket是否处于连接状态
try {
if(websocket.readyState==1){
websocket.close();
}
}catch (e){
}
var userID=document.getElementById("userID").value;
var touserID =document.getElementById("touserID").value;
//判断当前浏览器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8066/api/websocket/" + userID+"/"+touserID);// 改成你的地址
} else {
alert('当前浏览器 Not support websocket')
}
//连接发生错误的回调方法
websocket.onerror = function () {
};
//连接成功建立的回调方法
websocket.onopen = function () {
document.getElementById("accentinformation").innerHTML ="";
}
//接收到消息的回调方法
websocket.onmessage = function (event) {
var str = event.data;
jieshou(str);
}
}
//连接关闭的回调方法
websocket.onclose = function () {
alert("websocket.onclose: WebSocket连接关闭");
}
//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
window.onbeforeunload = function () {
closeWebSocket();
}
//关闭WebSocket连接
function closeWebSocket() {
websocket.close();
alert('websocket.close: 关闭websocket连接')
}
//发送消息
function send() {
var message = document.getElementById('text').value;
try {
websocket.send('{"msg":"' + message + '"}');
fasong(message);
} catch (err) {
console.error("websocket.send: " + message + " 失败");
}
}
</script>
#div{width: 600px;height: 500px;}
#div div{width:100%;display: inline-block;padding: 0px;}
#div img{width: 40px;height: 40px}
#div li{list-style: none}
.right{float: right;}
.left{float: left}
.left,.right{padding: 10px;}
.left li,.right li{display: inline-table}
.div ul{padding: 0px;margin: 0px}
#text{width: 260px;height: 54px}
#frinddiv,#text,#div{overflow:auto;resize:none}
#frinddiv::-webkit-scrollbar,#text::-webkit-scrollbar,#div::-webkit-scrollbar{width: 1px;}
#frinddiv::-webkit-scrollbar-thumb,#text::-webkit-scrollbar-thumb,#div::-webkit-scrollbar-thumb{background: #31acfb}
.cueright,.cueleft{border-radius: 10px;}
.cueright p,.cueleft p{margin:5px}
.cueleft{background:#31acfb}
.cueright{background: #cccccc}
.table{padding: 0px;margin: 0px;border-collapse: collapse; border: aliceblue 1px solid}
#frinddiv{width: 300px;height: 560px;}
#frinddiv ul:hover{background: #cccccc}
#frinddiv li{list-style: none;padding: 0px;margin: 0px}
.friendul li{display: inline-table;}
.friendul img{width: 40px;height: 40px;float: left}
.xuanze li{display: inline-table;margin: 0px}
.xuanze span{display: inline-block;margin: 0px}
.spanfl{border-radius:10px;text-align:center;float:right;margin-top: 5px;margin-right: 5px;background: #f11d38;font-size: 4px;width: 15px;height: 15px;}
function fasong(data){
var str = document.getElementById("text");
var div = document.getElementById("div");
if(str.value!=""){
var str0="";
var count=0;
var str1 = Array.from(data);
for(var i=0;i<str1.length;i++){
if(i>25){
str0 +="
"
i=0;
}
if(count==str1.length){
break;
}
str0+=str1[count];
count++;
}
div.innerHTML +=
""+
""
+
""
+str0+""+
""+
""+
"";
}
div.scrollTop = div.scrollHeight;
document.getElementById("text").value =""
}
function jieshou(data){
var div = document.getElementById("div");
var str0="";
var count=0;
var str1 = Array.from(data);
for(var i=0;i<str1.length;i++){
if(i>25){
str0 +="
"
i=0;
}
if(count==str1.length){
break;
}
str0+=str1[count];
count++;
}
div.innerHTML +=
"\n" +
" \n"
+
" \n" +
" "
+str0+"\n" +
" \n" +
" ";
document.getElementById("text").value =""
div.scrollTop = div.scrollHeight;
}
项目最终运行效果图,该项目中有三张图片没有上传,如有需要自行上网搜索使用自己喜欢的替换即可
欢迎大家github源码项目下载地址更多学习资料加QQ(528409336)