实现实时互动:用Spring Boot原生WebSocket打造你的专属聊天室

news2024/11/15 7:57:55
😊 @ 作者: 一恍过去
💖 @ 主页: https://blog.csdn.net/zhuocailing3390
🎊 @ 社区: Java技术栈交流
🎉 @ 主题: 实现实时互动:用Spring Boot原生WebSocket打造你的专属聊天室
⏱️ @ 创作时间: 2023年08月04日

在这里插入图片描述

目录

  • 前言
  • 1、pom文件
  • 2、静态工具类
  • 3、实现HandshakeInterceptor
  • 4、配置消息发送类
  • 5、配置实体类
  • 6、实现WebSocketHandler
  • 7、WebSocketConfig配置
  • 8、前端页面
  • 9、测试
    • 9.1、连接测试
    • 9.2、发送消息测试
    • 9.3、用户间消息测试
    • 9.4、消息群发测试
    • 9.5、服务端主动推送测试
  • 10、建议

前言

WebSocket实现聊天室的原理包括建立WebSocket连接的握手过程、保持连接状态以及通过WebSocket协议实现实时双向通信,从而实现用户之间的实时聊天功能,关键点如下:

  • WebSocket协议:
    • WebSocket是一种全双工通信协议,它在Web浏览器和服务器之间建立持久性的连接,允许实时的双向数据传输。相比传统的HTTP协议,WebSocket减少了通信的延迟,并且支持服务器主动向客户端推送数据。
  • 握手过程:
    • 客户端与服务器之间建立WebSocket连接的过程称为握手。握手过程开始于客户端发送一个HTTP请求,请求头中包含了一个特定的Upgrade字段,表明客户端希望升级为WebSocket协议。服务器收到请求后,验证并确认升级,然后返回一个特定的HTTP响应,表示握手成功。之后,双方就可以直接通过WebSocket协议进行实时数据传输。
  • 保持连接:
    • 一旦WebSocket连接建立,客户端和服务器之间的连接就会保持打开状态,直到其中一方主动关闭连接或发生异常。这使得WebSocket非常适合实时通信场景,如聊天室。
  • 实时通信:
    • 通过WebSocket连接,客户端和服务器可以直接发送消息和接收消息,而无需像传统HTTP请求那样每次都建立新的连接。这样,用户在聊天室中发送消息时,消息可以立即传递给其他在线用户,实现实时通信。

通过简陋的页面,实现多个用户之间的消息通信,为一个聊天室功能实现,只实现了聊天室的基本功能,没有实现聊天室的好看的页面;
在这里插入图片描述

1、pom文件

pom引入:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2、静态工具类

用于记录聊天室交互过程中的常量储存,代码如下:

import org.springframework.web.socket.WebSocketSession;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;


public class UserRecordParamManager {
    /**
     * 在线人数
     */
    public static AtomicInteger onlineNumber = new AtomicInteger(0);

    /**
     * 当前在线人员Id
     */
    public static List<String> onlineUser = Collections.synchronizedList(new ArrayList<>());

    /**
     * 用户和连接session映射
     */
    public static Map<String, WebSocketSession> userSession = new ConcurrentHashMap<>();
}

3、实现HandshakeInterceptor

通过实现HandshakeInterceptor接口的beforeHandshakeafterHandshake接口,作用如下:

  • beforeHandshake: 在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
  • afterHandshake: 在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头。
  • 代码如下:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

/**
 * @Description: 创建握手 此类用来获取登录用户信息并交由websocket管理
 **/
@Component
public class UserWebSocketInterceptor implements HandshakeInterceptor {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置WebSocketSession的属性
     *
     * @param request
     * @param response
     * @param webSocketHandler
     * @param attributes
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler webSocketHandler, Map<String, Object> attributes) throws Exception {
        logger.info("握手前请求连接URL:" + request.getURI());
        // 获取userId(token)
        String userId = ((ServletServerHttpRequest) request).getServletRequest().getParameter("userId");

        // 验证userId(token)是否有效
        //如果有效
        logger.info("用户:{},建立连接...", userId);

        // 加入到属性中
        attributes.put("userId", userId);
        return true;
    }

    /**
     * 在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头.
     */
    @Override
    public void afterHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse, WebSocketHandler webSocketHandler, Exception e) {
    }
}

4、配置消息发送类

自定义了两个方法用于实现,服务端将消息发送到相应的webSocket客户端。

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;

/**
 * @Description:
 **/
@Component
public class UserSendMessageManager {
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    public void sendMessageToUser(String userId, String message) {
        try {
            WebSocketSession session = UserRecordParamManager.userSession.get(userId);
            if (session != null && session.isOpen()) {
                TextMessage textMessage = new TextMessage(message.getBytes());
                session.sendMessage(textMessage);
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }


    public void sendMessageAll(String message) {
        // 遍历取出所有session进行发送消息
        try {
            for (WebSocketSession session : UserRecordParamManager.userSession.values()) {
                if (session.isOpen()) {
                    TextMessage textMessage = new TextMessage(message.getBytes());
                    session.sendMessage(textMessage);
                }
            }
        } catch (IOException e) {
            logger.error(e.getMessage());
        }
    }
}

5、配置实体类

该实体类用于解析客户端传递到服务端的json数据,代码如下:

public class RecevieUserMessage {

    /**
     * 消息类型:server、ping、user、all
     */
    private String type;

    /**
     * 内容
     */
    private String msg;

    /**接收人员Id(发给指定人时)
     * 
     */
    private String recevieUserId;
}

6、实现WebSocketHandler

通过实现WebSocketHandler接口的afterConnectionEstablishedhandleMessagehandleTransportErrorafterConnectionClosedsupportsPartialMessages方法,作用如下:

  • afterConnectionEstablished: 客户端成功连接后触发。
  • handleMessage: 接收到客户端消息时触发。
  • handleTransportError: 客户端连接失败触发。
  • afterConnectionClosed: 客户端连接失败后触发。

代码如下:

import com.alibaba.fastjson.JSON;
import com.lhz.socket.entity.RecevieUserMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

import javax.annotation.Resource;

/**
 * @Description: 消息处理器
 **/
@Component
public class UserWebSocketMessageHandler implements WebSocketHandler {

    @Resource
    private UserSendMessageManager userSendMessageManager;

    private final Logger logger = LoggerFactory.getLogger(this.getClass());


    /**
     * 用户进入系统监听
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 自加
        int userNum = UserRecordParamManager.onlineNumber.incrementAndGet();
        // 获取用户Id
        String userId = session.getAttributes().get("userId").toString();

        logger.info("有新连接加入! sessionId:{},", session.getId());
        logger.info("userId:{},在线人数{}", userId, userNum);

        // 加自己加入内存中
        UserRecordParamManager.onlineUser.add(userId);
        UserRecordParamManager.userSession.put(userId, session);
    }

    @Override
    public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage) throws Exception {
        try {
            String message = webSocketMessage.getPayload().toString();
            String userId = webSocketSession.getAttributes().get("userId").toString();

            // 解析内容为对象
            RecevieUserMessage userMessage = JSON.parseObject(message, RecevieUserMessage.class);
            String msg = userMessage.getMsg();
            String type = userMessage.getType();
            /**
             * 处理客户端接收的不同类型的消息
             * 消息类型:server、ping、user、all
             */
            switch (type) {
                case "server":
                    userSendMessageManager.sendMessageToUser(userId, "这里是服务端,已收到消息!");
                    break;
                case "ping":
                    userSendMessageManager.sendMessageToUser(userId, "收到心跳!");
                    break;
                case "user":
                    // 获取接收人Id
                    String recevieUserId = userMessage.getRecevieUserId();
                    // 验证对方是否在线
                    if (!UserRecordParamManager.onlineUser.contains(recevieUserId)) {
                        // 发送消息内容为错误码
                        userSendMessageManager.sendMessageToUser(userId, "201");
                    }
                    userSendMessageManager.sendMessageToUser(recevieUserId, msg);
                    break;
                case "all":
                    userSendMessageManager.sendMessageAll(msg);
                    break;
            }
        } catch (Exception e) {
            e.printStackTrace();
            logger.info("发生了错误了");
        }
    }

    /**
     * 用户连接出错
     */
    @Override
    public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
        logger.info("服务端发生了错误:" + throwable.getMessage());
        closeConnection(webSocketSession);
    }

    /**
     * 用户退出后的处理
     */
    @Override
    public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) throws Exception {
        closeConnection(webSocketSession);
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }


    private void closeConnection(WebSocketSession webSocketSession) {
        String sessionId = webSocketSession.getId();
        String offUserId = webSocketSession.getAttributes().get("userId").toString();
        // 自减
        int userNum = UserRecordParamManager.onlineNumber.decrementAndGet();
        UserRecordParamManager.onlineUser.remove(offUserId);
        UserRecordParamManager.userSession.remove(offUserId);

        logger.info("有连接关闭! sessionId:{},", sessionId);
        logger.info("userId:{},在线人数{}", offUserId, userNum);
    }

}

7、WebSocketConfig配置

配置WebSocket,配置socket的连接地址,配置HandlerInterceptors

import com.lhz.socket.websocket.user.UserWebSocketMessageHandler;
import com.lhz.socket.websocket.user.UserWebSocketInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

import javax.annotation.Resource;


@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Resource
    private UserWebSocketMessageHandler userWebSocketMessageHandler;

    @Resource
    private UserWebSocketInterceptor userWebSocketInterceptor;


    /**
     * 注册WebSocket处理类
     *
     * @param registry
     */
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 加载连接地址及拦截器
        registry.addHandler(userWebSocketMessageHandler, "/user")
                .addInterceptors(userWebSocketInterceptor)
                .setAllowedOrigins("*");
        //.withSockJS(); //通过socketJS方式连接

        //不同的地址配置不同的拦截器及处理器
    }

    /**
     * 支持websocket
     */
    @Bean
    public ServerEndpointExporter createServerEndExporter() {
        return new ServerEndpointExporter();
    }
}

8、前端页面

样式如下:
在这里插入图片描述

代码如下:
代码中,需要根据实际情况对连接地址做修改,代码中的地址是ws://localhost:9009/user?userId={userId},需要根据自己的环境,修改地址中的ip值以及port值

<!DOCTYPE HTML>
<html>
<head>
    <title>Test My WebSocket</title>
</head>
 
 
<body>
<p style="text-align: center"}>Socket测试<p/>
<input style="width: 300px" id="address" type="text" value="ws://localhost:9009/user?userId={userId}" />
<button id="conection" onclick="conection()">连接</button>
<button id="close" onclick="closeWebSocket()">断开</button>

<p/>
<input style="width: 300px" id="server-text" type="text" value="发送测试消息到服务器!" /> 
<button id="send-server" onclick="sendServer('server')">发送至服务</button>

<p/>

<input style="width: 80px" id="user-id" type="text" value="userId" /> 
<input style="width: 300px" id="user-text" type="text" value="发送给指定人员!" /> 
<button id="send-user" onclick="sendUser('user')">发送给用户</button>

<p/>
<input style="width: 300px" id="all-text" type="text" value="发送给所有人!" /> 
<button id="send-all" onclick="sendAll('all')">发送所有人</button>

<div id="message"></div>
</body>
 
<script type="text/javascript">

	//避免重复连接
	var lockReconnect = false;
	
	// socket对象
    var websocket = null;	
	
	//建立连接
    function conection(){
		createWebSocket(true);
	}
		
	function createWebSocket(tag) {
		var address = document.getElementById("address").value;
        console.log("createWebSocket...");
        if (tag) {//true表示正常连接
            count = 1;
        }
        try {
            //判断当前浏览器是否支持WebSocket
            if ('WebSocket' in window) {
                websocket = new WebSocket(address);
            } else {
                alert('当前浏览器不支持\n请更换浏览器');
            }
            init();
        } catch (e) {
            console.log('catch' + e);
            reconnect();
        }
    }
	
	function init() {
		
		//连接发生错误的回调方法
		websocket.onerror = function(){
			setMessageInnerHTML("连接失败...");
		};
	 
	 
		//连接成功建立的回调方法
		websocket.onopen = function(event){
			setMessageInnerHTML("连接成功...");
			//心跳检测重置
            heartCheck.start();
		}
	 
	 
		//接收到消息的回调方法
		websocket.onmessage = function(event){
			var data = event.data;
            if (data === '201') {
                data = "对方不在线,无法发送!"
            }
			if(data != '收到心跳!'){
				setMessageInnerHTML(data);
			}
			
			//心跳检测重置
            heartCheck.start();
		}
	 
	 
		//连接关闭的回调方法
		websocket.onclose = function(){
			setMessageInnerHTML("close");
		}
	 
	 
		//监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
		window.onbeforeunload = function(){
			websocket.close();
		}
    }

	function reconnect() {
        if (lockReconnect) {
            return;
        }
        //最多重新连接五次
        if (count >= 5) {
            alert("无法连接到服务,请稍后刷新重试!");
            return;
        }
        console.log("第" + count + "次尝试连接中...");
        count++;
        lockReconnect = true;
        //没连接上会一直重连,设置延迟避免请求过多
        tt && clearTimeout(tt);
        var tt = setTimeout(function () {
            createWebSocket(false);
            lockReconnect = false;
        }, 1000);
    }

    //心跳检测
    var heartCheck = {
        timeout: 5000, //30000,
        timeoutObj: null,
        serverTimeoutObj: null,
        start: function () {
            console.log('开启心跳检测');
            this.timeoutObj && clearTimeout(this.timeoutObj);
            this.serverTimeoutObj && clearTimeout(this.serverTimeoutObj);
            this.timeoutObj = setTimeout(function () {
                //这里发送一个心跳,后端收到后,返回一个心跳消息,
                //onmessage拿到返回的心跳就说明连接正常
				console.log("ping...");
                //心跳识别名称叫做 heartCheck
                sendHeartCheck();
            }, this.timeout)
        }
    };
 
    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML){
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }
 
    //关闭连接
    function closeWebSocket(){
        websocket.close();
    }
 
    //发送消息
    function sendServer(type) {
		var text = document.getElementById("server-text").value;
		var msgData = {};
        msgData['type'] = type;
        msgData['msg'] = text;

        console.log(msgData);
        websocket.send(JSON.stringify(msgData));
    }
	
	 function sendUser(type) {
	    var recevieUserId = document.getElementById("user-id").value;
		var text = document.getElementById("user-text").value;
		var msgData = {};
        msgData['type'] = type;
        msgData['msg'] = text;
		msgData['recevieUserId'] = recevieUserId;
		
        console.log(msgData);
        websocket.send(JSON.stringify(msgData));
    }
	
	 function sendAll(type) {
		var text = document.getElementById("all-text").value;
		var msgData = {};
        msgData['type'] = type;
        msgData['msg'] = text;

        console.log(msgData);
        websocket.send(JSON.stringify(msgData));
    }
	
	function sendHeartCheck(){
		var msgData = {};
        msgData['type'] = 'ping';
        msgData['msg'] = 'ping';

        console.log(msgData);
        websocket.send(JSON.stringify(msgData));
	}
</script>
</html>

9、测试

将上述《8、前端页面》中的前端代码,复制到html文件中,打开两个html页面,页面效果如下:
在这里插入图片描述

9.1、连接测试

将两个html页面中的{userId}分别修改为1、2,并且点击连接按钮;
在这里插入图片描述
服务端控制台信息如下:
在这里插入图片描述

9.2、发送消息测试

客户端发送信息到服务端,服务端收到消息后,给与一个回馈。
在这里插入图片描述
对应前端代码如下:
在这里插入图片描述
对应后端代码如下:
类:UserWebSocketMessageHandler
在这里插入图片描述

9.3、用户间消息测试

我们通过用户1客户端给用户2客户端发送一个消息;
在这里插入图片描述

对应前端代码如下:
在这里插入图片描述

对应后端代码如下:
类:UserWebSocketMessageHandler
在这里插入图片描述

9.4、消息群发测试

客户端用户2中发送群消息,效果如下:
在这里插入图片描述
对应前端代码如下:
在这里插入图片描述

对应后端代码如下:
类:UserWebSocketMessageHandler
在这里插入图片描述

9.5、服务端主动推送测试

为了测试服务器主动推送消息到socket客户端,我们实现一个controller,通过http接口的方式触发消息推送条件。
controller:

import com.lhz.socket.websocket.user.UserSendMessageManager;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;

/**
 * 通过http请求接口进行触发,由服务器主动发送内容
 */
@RestController
public class UserController {

    @Resource
    private UserSendMessageManager userSendMessageManager;

    @GetMapping("/send/{userId}")
    public void sendToUser(@PathVariable("userId") String userId) {
        userSendMessageManager.sendMessageToUser(userId, userId + "——这里是服务端主动推送!");
    }

    @GetMapping("/sendAll")
    public void sendAll() {
        userSendMessageManager.sendMessageAll("ALL——这里是服务端主动推送!");
    }
}

使用:
通过http请求接口即可

  • sendToUser接口:
    浏览器访问接口:http://localhost:9009/send/1,服务端主动给客户端用户1发送消息,效果如下:
    在这里插入图片描述

  • sendAll接口:
    浏览器访问接口:http://localhost:9009/sendAll,服务端主动给所有的客户端发送消息,效果如下:
    在这里插入图片描述

10、建议

在阅读文章时,可以直接先按步骤把代码建立好,然后再根据测试步骤进行代码的梳理。

在这里插入图片描述

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

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

相关文章

详解Nodejs中的Process对象

在Nodejs中&#xff0c;process是一个全局对象&#xff0c;它提供了与当前进程和运行时环境交互的方法和属性。通过process对象&#xff0c;我们可以访问进程的信息、控制流程和进行进程间通信&#xff0c;这些都是服务端语言应该具备的能力。本文将全面介绍process对象的使用场…

【雕爷学编程】 MicroPython动手做(35)——体验小游戏2

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

从感知到理解-融合语言模型的多模态大模型研究

©PaperWeekly 原创 作者 | 张燚钧 单位 | 中国移动云能力中心 研究方向 | 预训练大模型 引言 近年来&#xff0c;大语言模型&#xff08;Large language model, LLM&#xff09;取得了显著进展。以 ChatGPT 为代表的 LLM 在自然语言任务上展现出惊人的智能涌现能力。尽管…

TPU-NNTC 编译部署LPRNet 车牌识别算法

TPU-NNTC 编译部署LPRNet 车牌识别算法 注意&#xff1a; 由于SOPHGO SE5微服务器的CPU是基于ARM架构&#xff0c;以下步骤将在基于x86架构CPU的开发环境中完成 初始化开发环境(基于x86架构CPU的开发环境中完成)模型转换 (基于x86架构CPU的开发环境中完成) 处理后的LPRNet 项…

sql入门4--函数

字符串函数 # -----字符串函数----- # concat(s1,s2,....)拼接 select concat(Hello ,Mysql); #str转换为小写 select lower(HELLO); # str转换为大写 select upper(mysql); # 向左侧添加 str 位数 要添加的元素 select lpad(1, 3 ,-); # 向右侧添加 str 位数 要添加的元…

【单调栈part01】| 739.每日温度、496.下一个更大元素

&#x1f388;LeetCode739. 每日温度 链接&#xff1a;739.每日温度 给定一个整数数组 temperatures &#xff0c;表示每天的温度&#xff0c;返回一个数组 answer &#xff0c;其中 answer[i] 是指对于第 i 天&#xff0c;下一个更高温度出现在几天后。如果气温在这之后都不…

rv1109/1126 rknn 模型部署过程

rv1109/1126是瑞芯微出的嵌入式AI芯片&#xff0c;带有npu, 可以用于嵌入式人工智能应用。算法工程师训练出的算法要部署到芯片上&#xff0c;需要经过模型转换和量化&#xff0c;下面记录一下整个过程。 量化环境 模型量化需要安装rk的工具包&#xff1a; rockchip-linux/rk…

weblogic XML反序列化分析——CVE-2017-10271

环境 https://vulhub.org/#/environments/weblogic/CVE-2017-10271/ 启动环境 docker-compose up -d代码审计 传入参数 中间跟进函数 最后的出口 没有限制&#xff0c;直接包参数传入xmlDecoder public String readLine() throws IOException {return (String)this.xml…

Class Central-全球在线课程搜索引擎和学习平台

Class Central&#xff08;课程中央网站&#xff09;是一个全球在线课程搜索引擎和学习平台&#xff0c;全球知名的慕课资源导航社区&#xff0c;汇集了来自Coursera&#xff08;斯坦佛大学&#xff09;、edX&#xff08;麻省理工学院&#xff09;、Futurelearn&#xff08;英国…

如何使用vue ui创建一个项目?

首先打开cmd 输入vue ui 等待浏览器打开一个窗口&#xff0c;按照下图操作 在"功能页面"中&#xff0c;各个插件代表以下意思&#xff1a; Babel&#xff1a;Babel是一个JavaScript编译器&#xff0c;用于将ES6代码转换为向后兼容的JavaScript版本&#xff0c;以确保…

ORB-SLAM2学习笔记6之D435i双目IR相机运行ROS版ORB-SLAM2并发布位姿pose的rostopic

文章目录 0 引言1 D435i相机配置2 新增发布双目位姿功能2.1 新增d435i_stereo.cc代码2.2 修改CMakeLists.txt2.3 新增配置文件D435i.yaml 3 编译运行和结果3.1 编译运行3.2 结果3.3 可能出现的问题 0 引言 ORB-SLAM2学习笔记1已成功编译安装ROS版本ORB-SLAM2到本地&#xff0c…

C++入门篇6 C++的内存管理

在学习C的内存管理之前&#xff0c;我们先来回顾一下C语言中动态内存 int main() {int* p1 (int*)malloc(sizeof(int));free(p1);// 1.malloc/calloc/realloc的区别是什么&#xff1f;int* p2 (int*)calloc(4, sizeof(int));//calloc 可以初始化空间为0int* p3 (int*)reall…

渗透-01:DNS原理和HTML字符编码-HTML实体编码

一、DNS概念 DNS (Domain Name System 的缩写)就是根据域名查出IP地址(常用) DNS分类&#xff1a; 正向解析&#xff1a;已知域名解析IP反向解析&#xff1a;已知IP解析对应的域名 二、查询过程 工具软件dig可以显示整个查询过程 [rootnode01 ~]# dig baidu.com; <<>&…

pytorch学习——卷积神经网络——以LeNet为例

目录 一.什么是卷积&#xff1f; 二.卷积神经网络的组成 三.卷积网络基本元素介绍 3.1卷积 3.2填充和步幅 3.2.1填充&#xff08;Padding&#xff09; 填充是指在输入数据周围添加额外的边界值&#xff08;通常是零&#xff09;&#xff0c;以扩展输入的尺寸。填充可以在卷…

重磅特性 - SpreadJS推出新插件甘特图,预览版下载体验中

摘要&#xff1a;本文由葡萄城技术团队于CSDN原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 甘特图对于业务场景中的工程项目管理、预算执行、生产计划等都能将原有的表格数据&…

【数据分析】numpy (二)

numpy作为数据分析&#xff0c;深度学习常用的库&#xff0c;本篇博客我们来介绍numpy的一些进阶用法&#xff1a; 一&#xff0c;numpy的常用简单内置函数&#xff1a; 1.1求和&#xff1a; a np.array([[1, 2],[3, 4]]) np.sum(a)10 1.2求平均值&#xff1a; np.mean(a…

《向量数据库》——怎么安装向量检索库Faiss?

装 Faiss 以下教程将展示如何在 Linux 系统上安装 Faiss: 1. 安装 Conda。 在安装 Faiss 之前,先在系统上安装 Conda。Conda 是一个开源软件包和环境管理系统,可在 Windows、macOS 和 Linux 操作系统上运行。根据以下步骤在 Linux 系统上安装 Conda。 2. 从官网…

[模拟电路]集成运算放大器

目录 一.前言二.集成运放的介绍及特性分析1.集成运算放大器2.集成运放由四个部分组成3.集成运放的特性 三.集成运放的线性应用&#xff08;引入负反馈&#xff09;1.两个基本运算电路——反相/同相比例运算电路2.同相比例运算电路的特例——电压跟随器3.反相加法运算电路4.同相…

Android组件化入门:一步步搭建组件化架构

1、前言 最近因为业务需求变更&#xff0c;有考虑采用组件化架构进行开发&#xff0c;这方面我之前没有接触过。关于组件化的文章很多&#xff0c;各方大神更是提出了各种的组件化方案&#xff0c;我也看了很多相关文章。但是学习新东西看的再多&#xff0c;不如动手做一次&am…

CAD产品设计逆向软件 FARO RevEng Crack

CAD产品设计逆向软件 FARO RevEng 软件平台能为用户带来全面的数字设计体验。该反向工程软件有助于利用三维点云创建和编辑高质量的网格和 CAD 表面&#xff0c;以实现反向工程工作流程。然后&#xff0c;工业设计师可以利用这些网格模型进行进一步设计或三维打印。 RevEng 的商…