温故而知新,可以为师矣。 ——孔子
前言
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
一、环境准备
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>
<groupId>com.aichen</groupId>
<artifactId>socket-web-chat</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<spring.boot-version>2.3.11.RELEASE</spring.boot-version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot-version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>${spring.boot-version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
<optional>true</optional>
</dependency>
</dependencies>
</project>
前端js库:
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.5.1/sockjs.min.js"></script>
二、组织结构
三、主要实现代码
1、服务端代码(JAVA)
在SpringBoot中配置WebSocket
package com.aichen.socketwebchat.config;
import org.springframework.boot.autoconfigure.websocket.servlet.WebSocketMessagingAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Configuration
public class SocketConfig extends WebSocketMessagingAutoConfiguration {
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
定义消息体的结构
package com.aichen.socketwebchat.socket;
import lombok.Data;
import java.util.Date;
@Data
public class MessageBody {
private String fromName;
private String toName;
private String content;
private Date sendTime;
}
定义会话存储
package com.aichen.socketwebchat.socket;
import javax.websocket.Session;
import java.util.concurrent.ConcurrentHashMap;
public class SocketStorage {
private SocketStorage(){}
public static ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();
public static ConcurrentHashMap<String, String> nameMap = new ConcurrentHashMap<>();
}
服务器端逻辑实现
package com.aichen.socketwebchat.socket;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.EOFException;
import java.io.IOException;
import java.util.Date;
@Component
@ServerEndpoint("/socket/{name}")
@Slf4j
public class WebChatSocket {
@OnOpen
public void onOpen(@PathParam("name") String name, Session session) throws IOException {
if (SocketStorage.sessionMap.putIfAbsent(name, session) != null){
log.error("用户名:{} 已存在", name);
throw new RuntimeException("用户名已存在,请更换用户名。");
}
SocketStorage.nameMap.put(session.getId(), name);
log.info("{}上线了", name);
MessageBody systemMessage = new MessageBody();
systemMessage.setContent(name + "成功建立连接");
systemMessage.setFromName("system");
session.getBasicRemote().sendText(JSONObject.toJSONString(systemMessage));
}
@OnClose
public void onClose(Session session){
String name = SocketStorage.nameMap.remove(session.getId());
SocketStorage.sessionMap.remove(name);
log.info("{}下线了", name);
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
log.info("监听到消息:{}", message);
MessageBody messageBody = JSONObject.parseObject(message, MessageBody.class);
if (messageBody == null){
log.warn("监听到的消息为空或格式不正确,message:{}", message);
return;
}
String fromUser = SocketStorage.nameMap.get(session.getId());
messageBody.setFromName(fromUser);
messageBody.setSendTime(new Date());
Session toSession = SocketStorage.sessionMap.get(messageBody.getToName());
if (toSession != null){
toSession.getBasicRemote().sendText(JSONObject.toJSONString(messageBody));
}else {
log.info("{}不在线,发送失败", messageBody.getToName());
MessageBody systemMessage = new MessageBody();
systemMessage.setContent(messageBody.getToName() + "不在线,发送失败");
systemMessage.setFromName("system");
session.getBasicRemote().sendText(JSONObject.toJSONString(systemMessage));
}
}
@OnError
public void onError(Session session, Throwable error){
if (error instanceof EOFException && error.getMessage() == null){
String name = SocketStorage.nameMap.get(session.getId());
SocketStorage.nameMap.remove(session.getId());
SocketStorage.sessionMap.remove(name);
log.info("{}长时间没有活动,连接已断开", name);
}
}
}
2、客户端实现(html+js)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>聊天</title>
</head>
<body>
当前登录用户:<input id="name"/><button id="connBtn" onclick="connection()">建立连接</button><br/>
接收人:<input id="toName"/><br/>
<input id="sendContent"/><button onclick="sendMessage()">发送</button><br/>
<div id="messageView"></div>
</body>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/sockjs-client/1.5.1/sockjs.min.js"></script>
<script>
var webSocket;
function connection() {
let name = $("#name").val();
if (name){
if ("WebSocket" in window) {
webSocket = new WebSocket("ws://localhost:8080/socket/"+name);
webSocket.onopen = function() {
console.log("已经连通了websocket");
$("#name").attr('disabled',true);
$("#connBtn").attr('disabled',true);
};
webSocket.onmessage = function (evt) {
console.log("数据已接收:" +JSON.stringify(evt.data));
const messageBody = JSON.parse(evt.data);
viewMessage(messageBody);
};
webSocket.onclose = function() {
console.log("连接已关闭...");
let message = new Object();
message.fromName = "system";
message.content = "连接已关闭";
viewMessage(message);
};
}else{
alert("您的浏览器不支持 WebSocket!");
}
}else {
alert("请输入你的名字")
}
}
function sendMessage(){
let content = $("#sendContent").val();
if (content){
let message = new Object();
message.fromName = $("#name").val();
message.toName = $("#toName").val();
message.content = content;
$("#sendContent").val("");
viewMessage(message);
webSocket.send(JSON.stringify(message));
}else {
console.log("输入内容为空")
}
}
function viewMessage(message){
let messageHtml = "<div>"+message.fromName+": "+message.content+"</div>";
$("#messageView").append(messageHtml);
}
</script>
</html>
四、实现效果
|