网站备案要营业执照原件吗,开发者模式是干什么的,智慧软文发稿平台,网站开发风险WebSocket是一种在单个TCP连接上进行全双工通信的协议#xff0c;其设计的目的是在Web浏览器和Web服务器之间进行实时通信#xff08;实时Web#xff09;
WebSocket协议的优点包括#xff1a;
1. 更高效的网络利用率#xff1a;与HTTP相比#xff0c;WebSocket的握手只…WebSocket是一种在单个TCP连接上进行全双工通信的协议其设计的目的是在Web浏览器和Web服务器之间进行实时通信实时Web
WebSocket协议的优点包括
1. 更高效的网络利用率与HTTP相比WebSocket的握手只需要一次之后客户端和服务器端可以直接交换数据
2. 实时性更高WebSocket的双向通信能够实现实时通信无需等待客户端或服务器端的响应
3. 更少的通信量和延迟WebSocket可以发送二进制数据而HTTP只能发送文本数据并且WebSocket的消息头比HTTP更小
项目内容
1.WebSocketConfig
表示这是一个配置类可以定义 Spring Bean
Spring 会扫描该类并将其中定义的 Bean 方法返回的对象注册到应用上下文中
Bean 方法
serverEndpointExporter 方法用来创建并注册一个 ServerEndpointExporter 实例
ServerEndpointExporter 是 Spring 提供的一个类用于自动注册基于 Java 标准的 WebSocket 端点由 ServerEndpoint 注解标注的类
它负责将 ServerEndpoint 注解标记的 WebSocket 类注册到容器中
ServerEndpointExporter 的作用
当应用运行在 Spring Boot 容器中时ServerEndpointExporter 会扫描所有带有ServerEndpoint 注解的类并将其注册为 WebSocket 端点适用于嵌入式的 Servlet 容器如 Tomcat如果使用的是独立的 Servlet 容器如外部的Tomcat则不需要配置 ServerEndpointExporter
package com.qcby.chatroom1117.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 {Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}
2.ChatController
获取在线用户列表
调用 WebSocketServer.getWebSocketSet() 获取所有在线用户如果用户的 sid 不是 admin则添加到返回列表中
管理员发送消息
使用 RequestParam 获取请求中的 sid目标用户 ID和 message消息内容调用 WebSocketServer.sendInfo 向指定用户发送消息
package com.qcby.chatroom1117.controller;import com.qcby.chatroom1117.server.WebSocketServer;
import org.springframework.web.bind.annotation.*;import java.io.IOException;
import java.util.ArrayList;
import java.util.List;RestController
RequestMapping(/api/chat)
public class ChatController {/*** 获取在线用户列表不包含管理员*/GetMapping(/online-users)public ListString getOnlineUsers() {ListString sidList new ArrayList();for (WebSocketServer server : WebSocketServer.getWebSocketSet()) {//排除管理员if (!server.getSid().equals(admin)) {sidList.add(server.getSid());}}return sidList;}/*** 管理员发送消息给指定用户*/PostMapping(/send)public void sendMessageToUser(RequestParam String sid, RequestParam String message) throws IOException {WebSocketServer.sendInfo(message, sid);}}3.WebSocketServer
OnOpen: 客户端连接时执行的操作维护连接集合并记录用户的 sidOnClose: 客户端断开时从集合中移除更新在线用户数OnMessage: 接收客户端消息解析后发送到指定用户sendMessage: 服务端向客户端单独发送消息sendInfo: 群发或向指定客户端发送消息getOnlineCount: 获取当前在线连接数addOnlineCount subOnlineCount: 管理在线人数的计数OnError: 捕获 WebSocket 连接中的异常记录日志以便排查
package com.qcby.chatroom1117.server;import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;/*** WebSocket 服务端*/
Component
Slf4j
Service
ServerEndpoint(/api/websocket/{sid})
public class WebSocketServer {//当前在线连接数private static int onlineCount 0;//存放每个客户端对应的 WebSocketServer 对象private static final CopyOnWriteArraySetWebSocketServer webSocketSet new CopyOnWriteArraySet();//用户信息private Session session;//当前用户的 sidprivate String sid ;//JSON解析工具private static final ObjectMapper objectMapper new ObjectMapper();/*** 连接建立成功调用的方法*/OnOpenpublic void onOpen(Session session, PathParam(sid) String sid) {this.session session;this.sid sid;webSocketSet.add(this); //加入集合addOnlineCount(); //在线数加1try {sendMessage(conn_success);log.info(有新窗口开始监听: sid , 当前在线人数为: getOnlineCount());} catch (IOException e) {log.error(WebSocket IO Exception, e);}}/*** 连接关闭调用的方法*/OnClosepublic void onClose() {webSocketSet.remove(this); //从集合中删除subOnlineCount(); //在线数减1log.info(释放的 sid 为 sid);log.info(有一连接关闭当前在线人数为 getOnlineCount());}/*** 收到客户端消息后调用的方法*/OnMessagepublic void onMessage(String message, Session session) {log.info(收到来自窗口 sid 的信息: message);//解析消息中的 targetSidString targetSid;String msgContent;try {MapString, String messageMap objectMapper.readValue(message, Map.class);targetSid messageMap.get(targetSid);msgContent messageMap.get(message);} catch (IOException e) {log.error(消息解析失败, e);return;}//构造消息MapString, String responseMap new HashMap();responseMap.put(sourceSid, sid);responseMap.put(message, msgContent);String jsonResponse;try {jsonResponse objectMapper.writeValueAsString(responseMap);} catch (IOException e) {log.error(JSON 序列化失败, e);return;}//按 targetSid 发送消息for (WebSocketServer item : webSocketSet) {try {if (targetSid.equals(item.sid)) {item.sendMessage(jsonResponse);break; //找到目标用户后不再继续发送}} catch (IOException e) {log.error(消息发送失败, e);}}}/*** 判断是否是管理员*/private boolean isAdmin(String sid) {return admin_sid.equals(sid);}/*** 发生错误时调用的方法*/OnErrorpublic void onError(Session session, Throwable error) {log.error(发生错误, error);}/*** 实现服务器主动推送*/public void sendMessage(String message) throws IOException {this.session.getBasicRemote().sendText(message);}/*** 群发自定义消息*/public static void sendInfo(String message, PathParam(sid) String sid) throws IOException {log.info(推送消息到窗口 sid 推送内容: message);for (WebSocketServer item : webSocketSet) {try {if (sid null) {item.sendMessage(message); //推送给所有人} else if (item.sid.equals(sid)) {item.sendMessage(message); //推送给指定 sid}} catch (IOException e) {log.error(推送消息失败, e);}}}public static synchronized int getOnlineCount() {return onlineCount;}public static synchronized void addOnlineCount() {WebSocketServer.onlineCount;}public static synchronized void subOnlineCount() {WebSocketServer.onlineCount--;}public static CopyOnWriteArraySetWebSocketServer getWebSocketSet() {return webSocketSet;}public String getSid() {return this.sid;}
}4.admin页面
!DOCTYPE html
html langen
headmeta charsetUTF-8meta nameviewport contentwidthdevice-width, initial-scale1.0title管理员端 - 聊天窗口/titlestyle* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;display: flex;height: 100vh;margin: 0;background-color: #f4f7fc;color: #333;}/* 左侧在线用户列表 */#onlineUsersContainer {width: 250px;padding: 20px;background-color: #fff;border-right: 1px solid #ddd;box-shadow: 2px 0 5px rgba(0, 0, 0, 0.1);overflow-y: auto;}#onlineUsers {list-style-type: none;padding: 0;margin-top: 20px;}#onlineUsers li {padding: 10px;cursor: pointer;border-radius: 5px;transition: background-color 0.3s ease;}#onlineUsers li:hover {background-color: #e9f1fe;}#onlineUsers li.selected {background-color: #d0e7fe;}/* 右侧聊天窗口 */#chatBox {flex: 1;display: flex;flex-direction: column;padding: 20px;background-color: #fff;}#messages {border: 1px solid #ddd;height: 500px;overflow-y: scroll;margin-bottom: 20px;padding: 15px;background-color: #f9f9f9;border-radius: 10px;box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.1);}.message {padding: 10px;margin: 8px 0;border-radius: 10px;max-width: 80%;line-height: 1.6;word-wrap: break-word;}.message-right {background-color: #dcf8c6;text-align: right;margin-left: auto;}.message-left {background-color: #f1f0f0;text-align: left;margin-right: auto;}#messageInput {width: 80%;padding: 12px;border-radius: 25px;border: 1px solid #ccc;margin-right: 10px;font-size: 16px;transition: border-color 0.3s ease;}#messageInput:focus {border-color: #007bff;outline: none;}button {padding: 12px 20px;border-radius: 25px;border: 1px solid #007bff;background-color: #007bff;color: white;cursor: pointer;font-size: 16px;transition: background-color 0.3s ease;}button:hover {background-color: #0056b3;}h3 {font-size: 18px;color: #333;margin-bottom: 20px;}#onlineUsers li.unread {font-weight: bold;color: red;}media (max-width: 768px) {#onlineUsersContainer {width: 100%;padding: 15px;}#chatBox {padding: 15px;}#messageInput {width: calc(100% - 100px);}button {width: 80px;}}/style
/head
body
div idonlineUsersContainerh3在线用户/h3ul idonlineUsers/ul
/div
div idchatBoxh3聊天窗口/h3div idmessages/divdiv styledisplay: flex;input idmessageInput typetext placeholder请输入消息button onclicksendMessage()发送/button/div
/divscriptlet websocket;const sid admin;let currentUserSid null; //当前聊天对象的sidlet chatHistory {}; //用于存储每个用户的聊天记录//页面加载时初始化window.onload () {connectWebSocket();getOnlineUsers(); //页面加载时刷新在线用户列表restoreSelectedUser(); //恢复选中的用户};function connectWebSocket() {websocket new WebSocket(ws://localhost:8080/api/websocket/ sid);websocket.onopen () {console.log(连接成功管理员ID sid);};websocket.onmessage (event) {try {let data;if (event.data.startsWith({) event.data.endsWith(})) {data JSON.parse(event.data); // 如果是有效的 JSON 格式进行解析} else {// 如果是无效的 JSON比如 conn_success 这样的字符串进行处理console.log(接收到非 JSON 消息:, event.data);return;}const { sourceSid, message } data;if (sourceSid) {//初始化聊天记录存储if (!chatHistory[sourceSid]) {chatHistory[sourceSid] [];}//存储对方的消息chatHistory[sourceSid].push({ sender: left, message });//如果消息来源是当前聊天对象更新聊天窗口if (sourceSid currentUserSid) {displayMessages();} else {//消息来源不是当前对象提示未读消息notifyUnreadMessage(sourceSid);}}} catch (e) {console.error(解析消息失败, e);}};websocket.onclose () {console.log(连接关闭);};websocket.onerror (error) {console.error(WebSocket发生错误, error);};}function notifyUnreadMessage(userSid) {const userListItems document.querySelectorAll(#onlineUsers li);userListItems.forEach(item {if (item.textContent userSid) {item.classList.add(unread); //添加未读消息样式}});}//清除未读消息提示function clearUnreadMessage(userSid) {const userListItems document.querySelectorAll(#onlineUsers li);userListItems.forEach(item {if (item.textContent userSid) {item.classList.remove(unread);}});}function sendMessage() {const message document.getElementById(messageInput).value;if (!currentUserSid) {alert(请选择一个用户进行聊天);return;}if (message.trim() ! ) {websocket.send(JSON.stringify({ targetSid: currentUserSid, message }));chatHistory[currentUserSid] chatHistory[currentUserSid] || [];chatHistory[currentUserSid].push({ sender: right, message });document.getElementById(messageInput).value ;displayMessages();}}//显示当前用户的聊天记录function displayMessages() {const messagesDiv document.getElementById(messages);messagesDiv.innerHTML ;if (currentUserSid chatHistory[currentUserSid]) {chatHistory[currentUserSid].forEach(msg {const messageDiv document.createElement(div);messageDiv.classList.add(message, msg.sender right ? message-right : message-left);messageDiv.textContent msg.message;messagesDiv.appendChild(messageDiv);});}scrollToBottom();}function scrollToBottom() {const messagesDiv document.getElementById(messages);messagesDiv.scrollTop messagesDiv.scrollHeight;}//获取在线用户列表不包括管理员function getOnlineUsers() {fetch(/api/chat/online-users).then(response response.json()).then(users {const userList document.getElementById(onlineUsers);userList.innerHTML ;users.forEach(user {if (user ! admin) {const li document.createElement(li);li.textContent user;li.onclick () selectUser(user, li);userList.appendChild(li);}});});}//选择用户进行聊天function selectUser(user, liElement) {//清除所有选中状态const userListItems document.querySelectorAll(#onlineUsers li);userListItems.forEach(item item.classList.remove(selected));//高亮显示当前选中的用户liElement.classList.add(selected);if (currentUserSid ! user) {currentUserSid user;//清除未读消息提示clearUnreadMessage(user);//显示与该用户的聊天记录displayMessages();//保存当前选中的用户localStorage.setItem(selectedUserSid, user);}scrollToBottom();}//恢复选中的用户function restoreSelectedUser() {const savedUserSid localStorage.getItem(selectedUserSid);if (savedUserSid) {currentUserSid savedUserSid;const userListItems document.querySelectorAll(#onlineUsers li);userListItems.forEach(item {if (item.textContent savedUserSid) {item.classList.add(selected);}});displayMessages();}}
/script
/body
/html5.user页面
!DOCTYPE html
html langen
headmeta charsetUTF-8title用户端 - 聊天窗口/titlestyle* {margin: 0;padding: 0;box-sizing: border-box;}body {font-family: Arial, sans-serif;background-color: #f0f4f8;display: flex;justify-content: center;align-items: center;min-height: 100vh;}#chatBox {position: fixed;bottom: 10px;right: 10px;width: 400px;height: 500px;background-color: #ffffff;border-radius: 8px;padding: 20px;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);background: linear-gradient(to top right, #f9f9f9, #e9eff7);display: flex;flex-direction: column;max-height: 80vh;}#chatBox h3 {font-size: 20px;margin-bottom: 15px;color: #333;text-align: center;}#messages {flex: 1;border: 1px solid #ddd;padding: 15px;overflow-y: auto;background-color: #f9f9f9;border-radius: 8px;margin-bottom: 15px;font-size: 14px;color: #333;line-height: 1.5;box-shadow: inset 0 0 8px rgba(0, 0, 0, 0.1);}.message {padding: 10px;margin: 5px 0;border-radius: 8px;max-width: 80%;word-wrap: break-word;}.message-right {background-color: #dcf8c6;text-align: right;margin-left: auto;}.message-left {background-color: #f1f0f0;text-align: left;margin-right: auto;}#inputWrapper {display: flex;width: 100%;}#messageInput {width: calc(100% - 80px);padding: 12px;border-radius: 25px;border: 1px solid #ccc;margin-right: 10px;font-size: 16px;transition: border-color 0.3s ease;}#messageInput:focus {border-color: #007bff;outline: none;}button {padding: 12px 20px;border-radius: 25px;border: 1px solid #007bff;background-color: #007bff;color: white;cursor: pointer;font-size: 16px;transition: background-color 0.3s ease;width: 60px;display: inline-flex;align-items: center;justify-content: center;}button:hover {background-color: #0056b3;}media (max-width: 768px) {#chatBox {width: 100%;bottom: 20px;padding: 15px;}#messageInput {width: calc(100% - 100px);}button {width: 70px;padding: 10px;}}/stylescriptlet websocket;const sid Math.random().toString(36).substring(2, 15); //用户端的sidconst isAdmin false; //这是用户端管理员标识为falsefunction connectWebSocket() {websocket new WebSocket(ws://localhost:8080/api/websocket/ sid);websocket.onopen () {console.log(连接成功用户ID sid);document.getElementById(messages).innerHTML div classmessage-left连接成功您的ID是${sid}/div;};websocket.onmessage (event) {try {let data;// 检查消息是否是有效的 JSONif (event.data event.data.startsWith({)) {data JSON.parse(event.data);const { targetSid, message, sourceSid } data;// 确保消息是发送给当前用户的if (sourceSid admin || targetSid sid) {document.getElementById(messages).innerHTML div classmessage-left${message}/div;scrollToBottom();}} else {// 如果不是 JSON 格式可以直接处理其他类型的消息document.getElementById(messages).innerHTML div classmessage-left${event.data}/div;scrollToBottom();}} catch (e) {console.error(解析消息失败, e);}};websocket.onclose () {console.log(连接关闭);};websocket.onerror (error) {console.error(WebSocket发生错误, error);};}function sendMessage() {const message document.getElementById(messageInput).value;const targetSid admin; //目标为管理员if (message.trim() ! ) {websocket.send(JSON.stringify({ targetSid, message }));document.getElementById(messages).innerHTML div classmessage-right${message}/div;document.getElementById(messageInput).value ;scrollToBottom();}}function scrollToBottom() {const messagesDiv document.getElementById(messages);messagesDiv.scrollTop messagesDiv.scrollHeight;}connectWebSocket();/script
/head
body
div idchatBoxh3用户聊天窗口/h3div idmessages/divdiv idinputWrapperinput idmessageInput typetext placeholder请输入消息button onclicksendMessage()发送/button/div
/div
/body
/html项目部署
1.准备云服务器
2.在服务器上安装jdk
1yum install -y java-1.8.0-openjdk-devel.x86_64
2输入java -version查看已安装的jdk版本 3.在服务器上安装tomcat
1sudo wget https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.75/bin/apache-tomcat-9.0.75.tar.gz 2解压后进入到文件目录启动 3.修改项目
1修改pom文件
添加打包方式 添加tomcat和websocket依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-tomcat/artifactIdscopeprovided/scope !-- 提示该依赖已由外部服务器提供 --/dependencydependencygroupIdjavax.websocket/groupIdartifactIdjavax.websocket-api/artifactIdversion1.1/version/dependency
添加插件 plugingroupIdorg.apache.maven.plugins/groupIdartifactIdmaven-war-plugin/artifactIdconfigurationfailOnMissingWebXmlfalse/failOnMissingWebXml/configuration/plugin
2修改启动类
package com.qcby.chatroom1117;import javafx.application.Application;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;SpringBootApplication
public class ChatRoom1117Application extends SpringBootServletInitializer {public static void main(String[] args) {SpringApplication.run(ChatRoom1117Application.class, args);}Override //这个表示使用外部的tomcat容器protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {// 注意这里要指向原先用main方法执行的启动类return builder.sources(ChatRoom1117Application.class);}}3修改前端代码 4.打包
先执行clean再执行install 5.上传war包到tomcat文件夹的webapp目录下 6.重新启动tomcat访问
用户端 - 聊天窗口http://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/user
管理员端 - 聊天窗口http://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/admin 至此部署完成