云服务器部署WebSocket项目

news2024/11/27 11:03:28

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 {
    @Bean
    public 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 List<String> getOnlineUsers() {
        List<String> 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: 客户端连接时执行的操作,维护连接集合并记录用户的 sid
  • @OnClose: 客户端断开时从集合中移除,更新在线用户数
  • @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 CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();

    //用户信息
    private Session session;

    //当前用户的 sid
    private String sid = "";

    //JSON解析工具
    private static final ObjectMapper objectMapper = new ObjectMapper();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("sid") String sid) {
        this.session = session;
        this.sid = sid;
        webSocketSet.add(this); //加入集合
        addOnlineCount(); //在线数加1
        try {
            sendMessage("conn_success");
            log.info("有新窗口开始监听: " + sid + ", 当前在线人数为: " + getOnlineCount());
        } catch (IOException e) {
            log.error("WebSocket IO Exception", e);
        }
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this); //从集合中删除
        subOnlineCount(); //在线数减1
        log.info("释放的 sid 为:" + sid);
        log.info("有一连接关闭!当前在线人数为 " + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("收到来自窗口 " + sid + " 的信息: " + message);

        //解析消息中的 targetSid
        String targetSid;
        String msgContent;
        try {
            Map<String, String> messageMap = objectMapper.readValue(message, Map.class);
            targetSid = messageMap.get("targetSid");
            msgContent = messageMap.get("message");
        } catch (IOException e) {
            log.error("消息解析失败", e);
            return;
        }

        //构造消息
        Map<String, 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);
    }

    /**
     * 发生错误时调用的方法
     */
    @OnError
    public 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 CopyOnWriteArraySet<WebSocketServer> getWebSocketSet() {
        return webSocketSet;
    }

    public String getSid() {
        return this.sid;
    }
}

 4.admin页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>管理员端 - 聊天窗口</title>
    <style>
        * {
            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 id="onlineUsersContainer">
    <h3>在线用户</h3>
    <ul id="onlineUsers"></ul>
</div>
<div id="chatBox">
    <h3>聊天窗口</h3>
    <div id="messages"></div>
    <div style="display: flex;">
        <input id="messageInput" type="text" placeholder="请输入消息">
        <button onclick="sendMessage()">发送</button>
    </div>
</div>

<script>
    let websocket;
    const sid = "admin";
    let currentUserSid = null; //当前聊天对象的sid
    let 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>
</html>

5.user页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用户端 - 聊天窗口</title>
    <style>
        * {
            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;
            }
        }

    </style>
    <script>
        let websocket;
        const sid = Math.random().toString(36).substring(2, 15); //用户端的sid
        const isAdmin = false; //这是用户端,管理员标识为false

        function connectWebSocket() {
            websocket = new WebSocket("ws://localhost:8080/api/websocket/" + sid);

            websocket.onopen = () => {
                console.log("连接成功,用户ID:" + sid);
                document.getElementById("messages").innerHTML += `<div class="message-left">连接成功,您的ID是:${sid}</div>`;
            };

            websocket.onmessage = (event) => {
                try {
                    let data;
                    // 检查消息是否是有效的 JSON
                    if (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 class="message-left">${message}</div>`;
                            scrollToBottom();
                        }
                    } else {
                        // 如果不是 JSON 格式,可以直接处理其他类型的消息
                        document.getElementById("messages").innerHTML += `<div class="message-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 class="message-right">${message}</div>`;
                document.getElementById("messageInput").value = '';
                scrollToBottom();
            }
        }


        function scrollToBottom() {
            const messagesDiv = document.getElementById("messages");
            messagesDiv.scrollTop = messagesDiv.scrollHeight;
        }

        connectWebSocket();
    </script>
</head>
<body>
<div id="chatBox">
    <h3>用户聊天窗口</h3>
    <div id="messages"></div>
    <div id="inputWrapper">
        <input id="messageInput" type="text" placeholder="请输入消息">
        <button onclick="sendMessage()">发送</button>
    </div>
</div>
</body>
</html>

项目部署

1.准备云服务器

2.在服务器上安装jdk

(1)yum install -y java-1.8.0-openjdk-devel.x86_64

(2)输入java -version查看已安装的jdk版本

3.在服务器上安装tomcat

(1)sudo 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依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
            <scope>provided</scope> <!-- 提示该依赖已由外部服务器提供 -->
        </dependency>
        <dependency>
            <groupId>javax.websocket</groupId>
            <artifactId>javax.websocket-api</artifactId>
            <version>1.1</version>
        </dependency>

添加插件:

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <configuration>
                    <failOnMissingWebXml>false</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,访问

用户端 - 聊天窗口icon-default.png?t=O83Ahttp://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/user

管理员端 - 聊天窗口icon-default.png?t=O83Ahttp://47.96.252.224:8080/chatroom1117-0.0.1-SNAPSHOT/admin

 

至此,部署完成

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2248367.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

计算机网络八股整理(一)

计算机网络八股文整理 一&#xff1a;网络模型 1&#xff1a;网络osi模型和tcp/ip模型分别介绍一下 osi模型是国际标准的网络模型&#xff0c;它由七层组成&#xff0c;从上到下分别是&#xff1a;应用层&#xff0c;表示层&#xff0c;会话层&#xff0c;传输层&#xff0c;…

【Qt】控件7

1.QTextEdit的简单使用 使用简单的QTextEdit,获取到的内容显示到标签上 使用textChanged信号 在槽函数中需要获取QTextEdit的内容&#xff0c;对应操作是&#xff1a; QString curorui->textEdit->toPlainText();然后显示到标签上&#xff0c;对应操作是&#xff1a; …

【博主推荐】C#的winfrom应用中datagridview常见问题及解决方案汇总

文章目录 1.datagridview绘制出现鼠标悬浮数据变空白2.datagridview在每列前动态添加序号2.1 加载数据集完成后绘制序号2.2 RowPostPaint事件绘制 3.datagridview改变行样式4.datagridview后台修改指定列数据5.datagridview固定某个列宽6.datagridview某个列的显示隐藏7.datagr…

AI智能体崛起:从“工具”到“助手”的进化之路

目录 AI智能体的崛起 AI智能体的定义与决策模型 AI智能体的特点与优势 AI智能体的应用与类型 面临的挑战 未来展望 近年来&#xff0c;人工智能领域的焦点正从传统的聊天机器人&#xff08;Chat Bot&#xff09;快速转向更具潜力的AI智能体&#xff08;AI Agent&#xff…

【计网】自定义协议与序列化(一) —— Socket封装于服务器端改写

&#x1f30e; 应用层自定义协议与序列化 文章目录&#xff1a; Tcp协议Socket编程 应用层简介 序列化和反序列化       重新理解read/write/recv/send及tcp的全双工       Socket封装       服务器端改写 &#x1f680;应用层简介 我们程序员写的一个个解决…

鸿蒙动画开发07——粒子动画

1、概 述 粒子动画是在一定范围内随机生成的大量粒子产生运动而组成的动画。 动画元素是一个个粒子&#xff0c;这些粒子可以是圆点、图片。我们可以通过对粒子在颜色、透明度、大小、速度、加速度、自旋角度等维度变化做动画&#xff0c;来营造一种氛围感&#xff0c;比如下…

C语言学习 12(指针学习1)

一.内存和地址 1.内存 在讲内存和地址之前&#xff0c;我们想有个⽣活中的案例&#xff1a; 假设有⼀栋宿舍楼&#xff0c;把你放在楼⾥&#xff0c;楼上有100个房间&#xff0c;但是房间没有编号&#xff0c;你的⼀个朋友来找你玩&#xff0c;如果想找到你&#xff0c;就得挨…

【pyspark学习从入门到精通19】机器学习库_2

目录 估计器 分类 回归 聚类 管道 估计器 估计器可以被看作是需要估算的统计模型&#xff0c;以便对您的观测值进行预测或分类。 如果从抽象的 Estimator 类派生&#xff0c;新模型必须实现 .fit(...) 方法&#xff0c;该方法根据在 DataFrame 中找到的数据以及一些默认或…

结构方程模型(SEM)入门到精通:lavaan VS piecewiseSEM、全局估计/局域估计;潜变量分析、复合变量分析、贝叶斯SEM在生态学领域应用

目录 第一章 夯实基础 R/Rstudio简介及入门 第二章 结构方程模型&#xff08;SEM&#xff09;介绍 第三章 R语言SEM分析入门&#xff1a;lavaan VS piecewiseSEM 第四章 SEM全局估计&#xff08;lavaan&#xff09;在生态学领域高阶应用 第五章 SEM潜变量分析在生态学领域…

JQuery -- 第九课

文章目录 前言一、JQuery是什么&#xff1f;二、JQuery的使用步骤1.引入2.书写位置3. 表示方法 三、JQuery选择器1.层级选择器2. 筛选选择器3. 排他思想4. 精品展示 四、jQuery样式操作1. 修改样式2.类操作1. 添加2. 移除3. 切换 五、jQuery动画1. 显示和隐藏2. 滑动1. slide2.…

无人机探测:光电侦测核心技术算法详解!

核心技术 双光谱探测跟踪&#xff1a; 可见光成像技术&#xff1a;利用无人机表面反射的自然光或主动光源照射下的反射光&#xff0c;通过高灵敏度相机捕捉图像。该技术适用于日间晴朗天气下的无人机探测&#xff0c;具有直观、易于识别目标的特点。 红外成像技术&#xff1…

Java使用replaceAll替换时不使用正则表达式

前言 public String replaceAll(String regex, String replacement) {return Pattern.compile(regex).matcher(this).replaceAll(replacement);}在使用String.replaceAll() 方法时&#xff0c;由于入参时regex &#xff0c;而入参刚好是正则表达式的字符该怎么办&#xff1f;我…

计算机毕业设计Hadoop+Spark音乐推荐系统 音乐预测系统 音乐可视化大屏 音乐爬虫 HDFS hive数据仓库 机器学习 深度学习 大数据毕业设计

温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 温馨提示&#xff1a;文末有 CSDN 平台官方提供的学长联系方式的名片&#xff01; 作者简介&#xff1a;Java领…

途普科技企业知识中台完成华为昇思MindSpore技术认证

近日&#xff0c;北京途普科技有限公司&#xff08;以下简称“途普科技”&#xff09;作为华为昇腾大模型方向的应用软件伙伴&#xff0c;核心产品企业知识中台已成功与华为AI框架昇思MindSpore完成相互兼容性认证。这一成就标志着途普科技在AI领域与华为的合作进一步加深&…

自由学习记录(25)

只要有修改&#xff0c;子表就不用元表的参数了&#xff0c;用自己的参数&#xff08;只不过和元表里的那个同名&#xff09; 子表用__index“继承”了父表的值&#xff0c;此时子表仍然是空表 一定是创建这样一个同名的变量在原本空空的子表里&#xff0c; 传参要传具体的变…

【Nginx】核心概念与安装配置解释

文章目录 1. 概述2. 核心概念2.1.Http服务器2.2.反向代理2.3. 负载均衡 3. 安装与配置3.1.安装3.2.配置文件解释3.2.1.全局配置块3.2.2.HTTP 配置块3.2.3.Server 块3.2.4.Location 块3.2.5.upstream3.2.6. mine.type文件 3.3.多虚拟主机配置 4. 总结 1. 概述 Nginx是我们常用的…

AIGC-----AIGC在虚拟现实中的应用前景

AIGC在虚拟现实中的应用前景 引言 随着人工智能生成内容&#xff08;AIGC&#xff09;的快速发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术的应用也迎来了新的契机。AIGC与VR的结合为创造沉浸式体验带来了全新的可能性&#xff0c;这种组合不仅极大地降低了VR内容的…

学习笔记035——MySQL索引

数据库索引 索引是为了提高数据的查询速度&#xff0c;相当于给数据进行编号&#xff0c;在查找数据的时候就可以通过编号快速找到对应的数据。 索引内部数据结构&#xff1a;B Tree 主键自带索引。 如&#xff1a; insert into user (id, name) values (1,f); insert int…

C语言数据结构-链表

C语言数据结构-链表 1.单链表1.1概念与结构1.2结点3.2 链表性质1.3链表的打印1.4实现单链表1.4.1 插入1.4.2删除1.4.3查找1.4.4在指定位置之前插入或删除1.4.5在指定位置之后插入或删除1.4.6删除指定位置1.4.7销毁链表 2.链表的分类3.双向链表3.1实现双向链表3.1.1尾插3.1.2头插…

计算机网络 网络安全基础——针对实习面试

目录 网络安全基础你了解被动攻击吗&#xff1f;你了解主动攻击吗&#xff1f;你了解病毒吗&#xff1f;说说基本的防护措施和安全策略&#xff1f; 网络安全基础 网络安全威胁是指任何可能对网络系统造成损害的行为或事件。这些威胁可以是被动的&#xff0c;也可以是主动的。…