使用LVS+NGinx+Netty实现数据接入

news2024/9/22 15:41:31

数据接入

链接参考文档 LVS+Keepalived项目

车辆数据上收,TBox通过TCP协议连接到TSP平台 建立连接后进行数据上传。也可借由该连接实现远程控制等操作。

通过搭建 LV—NGinx—Netty实现高并发数据接入

  • LVS:四层负载均衡(位于内核层):根据请求报文中的目标地址和端口进行调度
  • NGinx:七层负载均衡(位于应用层):根据请求报文的内容进行调度,这种调度属于「代理」的方式
组件角色主机名称虚拟ip/端口
LVS+keepalivedactive-0007
LVS+keepalivedbackup-0006
Nginx负载-00058050
Nginx负载-00048050
Netty真实服务器-00038050
Netty真实服务器-00028050
Netty真实服务器-00038092
Netty真实服务器-00028092
Netty真实服务器-00018092
Netty真实服务器-00018092

使用华为云服务器 安装LVS 需要有VPC服务(免费),在控制台页面做虚拟ip绑定在这里插入图片描述

一、安装LVS 服务

使用的是 DR 模式

  • NET模式:LVS将数据请求包转发给真实服务器的时候,会修改成真实服务器的IP地址;在回复时真实服务器会把回复包发往LVS调度服务器 再发往客户端。
  • TUN隧道模式:将原始数据包封装并添加新的包头(内容包括新的源地址及端口、目标地址及端口),从而实现将一个目标为调度器的VIP地址的数据包封装,通过隧道转发给后端的真实服务器(RealServer)感觉很复杂。
  • DR模式:要求LVS调度服务器要和后端服务器在同一局域网下,为后端服务器添加lo回环地址为VIP(虚拟IP地址)这样回复给客户端 客户端会以为是连接的VIP进行回复的

DR模式不支持端口映射

#查看网卡 eth0
ifconfig
#执行 虚拟ip:172.25.94.187    广播地址(不变):172.25.94.191 子网掩码(不变):255.255.255.192 up:立即启用vip(虚拟ip)
ifconfig eth0:1 172.25.94.187 broadcast 172.25.94.191 netmask 255.255.255.192 up
#查看当前网卡信息
ip a
#安装keepalived
sudo yum install keepalived
#启动keepalived
systemctl start keepalived
#加入开机启动keepalived
systemctl enable keepalived
#重新启动keepalived
systemctl restart keepalived  
#查看keepalived状态
systemctl status keepalived

在这里插入图片描述

LVS 模块内嵌lvs模块,只需要ipvsadm和keepalived安装

#查看Linux 内核版本
uname -r
#查看内核是否集成lvs模块
find /lib/modules/$(uname -r)/ -iname "**.ko*" | cut -d/ -f5- 
#安装LVS管理工具:ipvsadm
yum install -y gcc gcc-c++ makepcre pcre-devel kernel-devel openssl-devel libnl-devel popt*  
yum -y install ipvsadm  
#启动ipvs
sudo ipvsadm
#查看是否支持lvs
sudo lsmod |grep ip_vs
#查看ipvsadm 版本
ipvsadm -v
#服务器添加路由规则
route add -host 172.25.94.187 dev ens33:0
route add -host 172.25.110.124 dev eth:0

#启用系统的包转发功能 #1:启用ip转发,0:禁止ip转发
echo "1" >/proc/sys/net/ipv4/ip_forward 
#清除原有转发规则
systemctl restart keepalived 
systemctl status ipvsadm
ipvsadm --clear
#添加虚拟ip规则 rr:负载均衡算法 轮询
 ipvsadm -A -t 172.25.94.187:8043 -s rr
 ipvsadm -a -t 172.25.94.187:8043 -r 172.25.94.151:8043 -g
 ipvsadm -a -t 172.25.94.187:8043 -r 172.25.94.152:8043 -g
 ipvsadm -l
 #配置tcp/tcpfin/udp超时时间
  ipvsadm --set 900 120 300
  #添加虚机IP规则也可以通过修改文件实现
  vim /etc/keepalived/keepalived.conf
global_defs {
   router_id chery_21
}
vrrp_instance VI_1 {
    state MASTER
    interface eth0
    virtual_router_id 51
    priority 100
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }

    virtual_ipaddress {
        172.25.110.124
    }
}
virtual_server 172.25.110.124 8050 {
    delay_loop 6
    lb_algo rr
    lb_kind DR
    persistence_timeout 50
    protocol TCP

    real_server 172.25.110.19 8050 {
        weight 1
        TCP_CHECK {
	connect_timeout 30
	delay_before_retry 3
    }
}
    real_server 172.25.110.18 8050 {
	weight 2
	TCP_CHECK {
	connect_timeout 30
	}
}
}

全局定义(global_defs)

  • router_id chery_21:定义了当前Keepalived实例的路由ID,这是唯一的标识符,用于在VRRP组中区分不同的Keepalived实例。

VRRP实例(vrrp_instance VI_1)

  • state MASTER:设置当前实例的初始状态为MASTER。在VRRP组中,MASTER负责处理对虚拟IP的流量。

  • interface eth0:指定VRRP通信使用的网络接口。

  • virtual_router_id 51:虚拟路由的ID,用于在VRRP组中标识不同的虚拟路由器。

  • priority 100:设置当前实例的优先级,优先级高的实例将成为MASTER。

  • advert_int 1:VRRP通告的间隔时间,单位为秒。MASTER每隔这个时间会向其他节点发送VRRP通告。

  • authentication
    

    :VRRP认证配置,确保只有授权的设备可以加入VRRP组。

    • auth_type PASS:使用密码认证。
    • auth_pass 1111:认证密码。
  • virtual_ipaddress:定义了虚拟IP地址,即VIP,客户端将访问此IP地址来访问服务。

虚拟服务器(virtual_server)

  • 172.25.110.124 8050:定义了虚拟服务器的IP地址和端口号,这里与VRRP的VIP相同,表明这个虚拟服务器是通过VIP来访问的。
  • delay_loop 6:健康检查的时间间隔,单位为秒。
  • lb_algo rr:负载均衡算法,这里使用的是轮询(rr)。
  • lb_kind DR:负载均衡类型,这里使用的是直接路由(DR),需要确保后端服务器(real_server)配置正确以支持DR模式。
  • persistence_timeout 50:会话保持时间,单位为秒。在指定时间内,来自同一客户端的请求将被转发到同一台后端服务器。
  • protocol TCP:使用TCP协议进行健康检查和负载均衡。

后端服务器(real_server)

  • 定义了多个后端服务器,每个服务器都配置了IP地址、端口号、权重和健康检查设置。

  • weight:权重,用于负载均衡时决定服务器的优先级。

  • TCP_CHECK
    

    :TCP健康检查配置。

    • connect_timeout 30:连接超时时间,单位为秒。
    • delay_before_retry 3:在重试之前等待的时间,单位为秒。

LVS负载均衡(LVS简介、三种工作模式、十种调度算法)

#列出当前LVS表中的所有配置,包括虚拟服务器和真实服务器的信息。
ipvsadm -Ln
#显示统计信息,包括已转发的连接数、入包个数、出包个数等。
ipvsadm -L --stats
#显示转发速率信息,包括每秒连接数、每秒入包个数、每秒出包个数等
ipvsadm -L --rate
#keepalived 日志
vim /var/log/message

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、安装nginx 服务

在nginx服务器和后端服务器 配置lo回环地址 否则回复将不成功

服务器上一般还需要修改lo网卡 配置成虚拟IP。华为云服务器使用的是Centos 8版本 没有 lo配置文件,通过 ifconfig lo:0 172.25.94.187 netmask 255.255.255.255 broadcast 172.25.94.187 up 华为云服务器不支持修改网卡,所以修改了 eth0网卡配置 ip addr add 172.25.94.187/24 dev eth0

在这里插入图片描述

yum -y nginx
#检查是否有 stream
nginx -V 2>&1 | grep --color -o with-stream
#如果没有stream需要对nginx源码安装进行二次编译 
tar -zxvf nginx-*.tar.gz  
cd nginx-*  
./configure --prefix=/usr/local/nginx --with-http_ssl_module --with-stream  
make  
sudo make install
#重新加载Nginx配置文件
nginx -s reload 
#强制停止Nginx服务
nginx -s stop
#重启nginx
nginx -s reopen
#修改配置文件
vim /etc/nginx/nginx.conf

netstat -anput | grep nginx

nginx -c nginx.conf

在这里插入图片描述

三、Netty服务

**1、在Linux上部署启用了 epoll **

epoll:是Linux内核为处理大批量文件描述符而作的改进的poll,是Linux下多路复用IO接口select/poll的增强版本
应用于Linux系统下的应用程序,特别是需要处理大量并发连接的高性能网络服务器。

BIO:同步阻塞IO,也就是传统阻塞型的IO,服务器实现模式是一个连接对应一个线程。客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销。

NIO:同步非阻塞IO,服务器实现模式是一个线程处理多个请求,客户端发送的链接请求都会注册到多路复用器上,多路复用器轮询到链接有IO请求就进行处理。

AIO:异步非阻塞,AIO引入了异步通道的概念,采用了Proactor模式,简化了程序编写,有效的请求才启动线程,他的特点是先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且链接时间较长的应用。

public class NettyServer {
    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    @Resource
    private NettyServerInitializer nettyServerInitializer;

    ServerBootstrap serverBootstrap = new ServerBootstrap();

    EventLoopGroup boss = null;

    EventLoopGroup worker = null;

    ChannelFuture future = null;

    ChannelFuture future2 = null;

    //厂商编码
    Integer factoryCode = null;

    @Value("${netty.server.use-epoll}")
    boolean epoll = false;

    @Value("${netty.server.port1}")
    private int port = 8030;

    @Value("${netty.server.port2}")
    private int port2 = 8050;

    @PreDestroy
    public void stop() {
        if (future != null) {
            future.channel().close().addListener(ChannelFutureListener.CLOSE);
            future.awaitUninterruptibly();
            boss.shutdownGracefully();
            worker.shutdownGracefully();
            future = null;
            logger.info(" 服务关闭 ");
        }
    }

    public void start() {
        logger.info(" nettyServer 正在启动");

        if (epoll) {
            logger.info(" nettyServer 使用epoll模式");
            boss = new EpollEventLoopGroup(4);
            //指定线程32
            worker = new EpollEventLoopGroup(32);
        } else {
            logger.info(" nettyServer 使用nio模式");
            boss = new NioEventLoopGroup(4);
            worker = new NioEventLoopGroup(32);
        }

        logger.info("netty服务器在[" + this.port + "]端口启动监听");
        logger.info("netty服务器在[" + this.port2 + "]端口启动监听");

        serverBootstrap.group(boss, worker)
        // tcp缓冲区:将不能处理的客户端连接请求放到队列里等待
                .option(ChannelOption.SO_BACKLOG, 10240)
                //多个进程或者线程绑定到同一端口,提高服务器程序的性能
                .option(EpollChannelOption.SO_REUSEPORT, true)
                //打印info级别的日志
//                .handler(new LoggingHandler(LogLevel.INFO))
                // 将网络数据积累到一定的数量后,服务器端才发送出去,会造成一定的延迟。希望服务是低延迟的,建议将TCP_NODELAY设置为true
                .childOption(ChannelOption.TCP_NODELAY, true)
                // 可以确保连接在因网络问题中断时能够被及时检测并处理。
                .childOption(ChannelOption.SO_KEEPALIVE, false)
                // 配置ByteBuf内存分配器
                .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                // 配置 编码器、解码器、业务处理
                .childHandler(nettyServerInitializer);

        if (epoll) {
            serverBootstrap.channel(EpollServerSocketChannel.class);
        } else {
            serverBootstrap.channel(NioServerSocketChannel.class);
        }


        try {
            future = serverBootstrap.bind(port).sync();
            future2 = serverBootstrap.bind(port2).sync();
            future.channel().closeFuture().addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {       //通过回调只关闭自己监听的channel
                    future.channel().close();
                }
            });

            future2.channel().closeFuture().addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    future.channel().close();
                }
            });

            // 等待服务端监听端口关闭
            // future.channel().closeFuture().sync();
        } catch (Exception e) {
            logger.info("nettyServer 启动时发生异常---------------{}", e);
            logger.info(e.getMessage());
        } finally {
            //这里一定要注释,因为上面没有阻塞了,不注释的话,这里会直接关闭的
            //boss.shutdownGracefully();
            //worker.shutdownGracefully();
        }
    }

2、超时配置

    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        // readerIdleTimeSeconds 读超时;writerIdleTimeSeconds 写超时;allIdaleTimes 读写全超时 300 秒;断开连接
        pipeline.addLast(new IdleStateHandler(0, 0, 300, TimeUnit.SECONDS));
        pipeline.addLast(new LengthFieldBasedFrameDecoder(64 * 1024, 22, 2, 1, 0));
       //根据端口动态的选择解码器
        Integer localPort = socketChannel.localAddress().getPort();
        if (localPort == 8050 || localPort == 8055) {
            pipeline.addLast("authHandler", authHandler);
            pipeline.addLast("messageHandler", messageHandler);
        } else if (localPort == 8030 || localPort == 8035) {
                pipeline.addLast("authHandler", authHandler2);
                pipeline.addLast("messageHandler", messageHandler2);
        }
    }

在处理器中的应用


    /**
     * 用户事件触发,发现读超时会调用 根据心跳检测状态去关闭连接
     */

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {

        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            String clientId = ChannelStore.getClientId(ctx);
            Attribute<Integer> timesAttr = ctx.channel().attr(AttributeKey.valueOf("times"));
            Integer timeInt = timesAttr.get();

            if (timeInt == null) {
                timeInt = 0;
            }
            String eventDesc = null;
            switch (event.state()) {
                case READER_IDLE:
                    eventDesc = "读空闲";
                    break;
                case WRITER_IDLE:
                    eventDesc = "写空闲";
                    break;
                case ALL_IDLE:
                    eventDesc = "读写空闲";
                    break;
            }
            //获取ip地址信息
            InetAddress ip = InetAddress.getLocalHost();
            String hostAddress = ip.getHostAddress();

            log.info(clientId + " 地址:" + hostAddress + "发生超时事件--" + eventDesc);
            timeInt++;
            timesAttr.set(timeInt);

            if (timeInt > 1) {
                //删除ip地址信息
                String redisIpAddress = redisTemplateNew.get(clientId + "_IP");
                boolean hostBoolean = hostAddress.equals(redisIpAddress);
                log.info(hostBoolean + "check :" + clientId + " redisTemplateNewDelete:" + hostAddress + "redisIP:" + redisIpAddress);
                if (redisIpAddress != null && hostBoolean) {
                    redisTemplateNew.delete(clientId + "_IP");
                }
                log.info(clientId + " 地址:" + hostAddress + ":" + ctx.channel().remoteAddress() + "空闲次数为" + timeInt + "次 关闭连接 " + clientId);
                ctx.channel().close();
            }
        }
    }

3、下行API

public class SendApi {

    @Resource
    private RedisTemplateNew redisTemplateNew;

    @Resource
    private KafkaTemplate<String, String> kafkaTemplate;

    @GetMapping(value = "/userinfo")
    public UserDto gerUserInfo() {
        UserDto user = new UserDto();
        user.setUserId("888888");
        user.setUserName("holmium");
        user.setSex("1");
        return user;
    }
}

}
}
}


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

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

相关文章

Django定时任务框架django-apscheduler的使用

1.安装库 pip install django-apscheduler 2.添加 install_app django_apscheduler 3.在app下添加一个task.py文件&#xff0c;用来实现具体的定时任务 task.pydef my_scheduled_job():print("这个任务每3秒执行一次", time.time()) 4.在app下创建一个manag…

macpdf转图片 macpdf导出为图片 mac如何将pdf存为jpg

在数字化办公的今天&#xff0c;pdf文件因其良好的文档保存和分享特性&#xff0c;已成为工作生活中不可或缺的一部分。然而&#xff0c;在某些场景下&#xff0c;我们需要将pdf文件转换为图片格式&#xff0c;以便于分享或展示。本文将向您介绍多种pdf转图片的方法&#xff0c…

github上的工程如何下载子模块.gitmodules如何下载指定的模块download submodules开源项目子模块下载externals

github上的工程如何下载子模块.gitmodules如何下载指定的模块download submodules 说明(废话)解决方案无法执行下载子模块无法下载子项目 说明(废话) 今天在编译一个开源库时&#xff0c;该开源库依赖其他项目&#xff0c;并且项目还挺多的&#xff0c;所以有此解决方案 在编…

MQ - RabbitMQ - 消息的可靠性 --学习笔记

消息的可靠性 RabbitMQ 提供了一系列的特性和机制来确保消息的可靠性&#xff0c;即确保消息不丢失、按需到达目的地。要实现在 RabbitMQ 中消息的可靠性&#xff0c;可通过以下几个方面进行操作&#xff1a; 一、发送者的可靠性 1、生产者重试机制 什么是生产者重试机制&a…

Elasticsearch:如何选择向量数据库?

作者&#xff1a;来自 Elastic Elastic Platform Team 向量数据库领域是一个快速发展的领域&#xff0c;它正在改变我们管理和搜索数据的方式。与传统数据库不同&#xff0c;向量数据库以向量的形式存储和管理数据。这种独特的方法可以实现更精确、更相关的搜索&#xff0c;并允…

Spire.PDF for .NET【文档操作】演示:如何在 C# 中切换 PDF 层的可见性

我们已经演示了如何使用 Spire.PDF在 C# 中向 PDF 文件添加多个图层以及在 PDF 中删除图层。我们还可以在 Spire.PDF 的帮助下在创建新页面图层时切换 PDF 图层的可见性。在本节中&#xff0c;我们将演示如何在 C# 中切换新 PDF 文档中图层的可见性。 Spire.PDF for .NET 是一…

Web开发:ASP.NET CORE前后端交互之AJAX(含基础Demo)

目录 一、后端 二、前端 三、代码位置 四、实现效果 五、关键的点 1.后端传输给前端&#xff1a; 2.前端传输给后端 一、后端 using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Rendering; using WebAppl…

24暑假算法刷题 | Day16 | LeetCode 513. 找树左下角的值,112. 路径总合,106. 从中序和后序遍历序列构造二叉树

目录 513. 找树左下角的值题目描述题解 112. 路径总合题目描述题解 106. 从中序和后序遍历序列构造二叉树题目描述题解 513. 找树左下角的值 点此跳转题目链接 题目描述 给定一个二叉树的 根节点 root&#xff0c;请找出该二叉树的 最底层 最左边 节点的值。 假设二叉树中至…

React@16.x(62)Redux@4.x(11)- 中间件2 - redux-thunk

目录 1&#xff0c;介绍举例 2&#xff0c;原理和实现实现 3&#xff0c;注意点 1&#xff0c;介绍 一般情况下&#xff0c;action 是一个平面对象&#xff0c;并会通过纯函数来创建。 export const createAddUserAction (user) > ({type: ADD_USER,payload: user, });这…

网络安全----防御----防火墙双机热备

实验要求&#xff1a; 1&#xff0c;对现有网络进行改造升级&#xff0c;将当个防火墙组网改成双机热备的组网形式&#xff0c;做负载分担模式&#xff0c;游客区和DMZ区走FW4&#xff0c;生产区和办公区的流量走FW1 2&#xff0c;办公区上网用户限制流量不超过100M&#xff0…

记录一下在Hyper-v中动态磁盘在Ubuntu中不完全用到的问题(扩展根目录)

在之前给hyper虚拟机的Ubuntu分配磁盘有20G&#xff1b; 后来在Ubuntu中查看磁盘发现有一个分区没用到&#xff1a; 贴的图片是完成扩展后的 之前这里是10G&#xff0c;然后有个dev/sda4的分区&#xff0c;也是10G&#xff0c;Type是Microsoft Basic Data&#xff1b; …

健康问题查询找搜索引擎还是大模型

随着自然语言处理&#xff08;NLP&#xff09;的最新进展&#xff0c;大型语言模型&#xff08;LLMs&#xff09;已经成为众多信息获取任务中的主要参与者。然而&#xff0c;传统网络搜索引擎&#xff08;SEs&#xff09;在回答用户提交的查询中的作用远未被取代。例如&#xf…

云计算实训室的核心功能有哪些?

在当今数字化转型浪潮中&#xff0c;云计算技术作为推动行业变革的关键力量&#xff0c;其重要性不言而喻。唯众&#xff0c;作为教育实训解决方案的领先者&#xff0c;深刻洞察到市场对云计算技能人才的迫切需求&#xff0c;精心打造了云计算实训室。这一实训平台不仅集成了先…

基于电鸿(电力鸿蒙)的边缘计算网关,支持定制

1 产品信息 边缘计算网关基于平头哥 TH1520 芯片&#xff0c;支持 OpenHarmony 小型系统&#xff0c;是 连接物联网设备和云平台的重要枢纽&#xff0c;可应用于城市基础设施&#xff0c;智能工厂&#xff0c;智能建筑&#xff0c;营业网点&#xff0c;运营 服务中心相关场…

PostgreSQL 中如何解决因大量并发读取导致的缓存命中率下降?

&#x1f345;关注博主&#x1f397;️ 带你畅游技术世界&#xff0c;不错过每一次成长机会&#xff01;&#x1f4da;领书&#xff1a;PostgreSQL 入门到精通.pdf 文章目录 PostgreSQL 中如何解决因大量并发读取导致的缓存命中率下降一、了解 PostgreSQL 缓存机制二、分析缓存…

人工智能导论-神经网络

神经网络 概述 本章主要介绍人工神经网络的基本概念&#xff0c;以及几种重要模型&#xff0c;包括“单层感知机、两层感知机、多层感知机”等。 在此基础上&#xff0c;介绍两种重要的基础神经网络“Hopfield神经网络、BP神经网络”。 最后&#xff0c;着重介绍了深度学习…

Java跨平台的原理是什么?JDK,JRE,JVM三者的作用和区别?xxx.java和xxx.class有什么区别?看这一篇就够了

目录 1. Java跨平台相关问题 1.1 什么是跨平台(平台无关性)&#xff1f; 1.2 跨平台(平台无关性)的好处&#xff1f; 1.3 编译原理基础&#xff08;Java程序编译过程&#xff09; 1.4Java跨平台的是实现原理&#xff1f; 1.4.1 JVM(Java虚拟机) 1.4.2 Class文件 1.4.3 …

是德keysight N9020B(原Agilent) N9020A信号频谱分析仪

Agilent N9020B N9020B信号分析仪手持信号分析仪 N9020B MXA 信号分析仪&#xff0c;10 Hz 至 26.5 GHz 主要特性和功能快速适应无线器件不断演进的测试要求通过硬件加速功率测量缩短测试时间&#xff0c;显示更新速率快&#xff0c;并且具有游标峰值搜索和快速扫描功能X 系列…

el-select选择器修改背景颜色

<!--* FilePath: topSearch.vue* Author: 是十九呐* Date: 2024-07-18 09:46:03* LastEditTime: 2024-07-18 10:42:03 --> <template><div class"topSearch-container"><div class"search-item"><div class"item-name&quo…

ROS2从入门到精通2-3:机器人3D物理仿真Gazebo与案例分析

目录 0 专栏介绍1 什么是Gazebo&#xff1f;2 Gazebo架构2.1 Gazebo前后端2.2 Gazebo文件格式2.3 Gazebo环境变量 3 Gazebo安装与基本界面4 搭建自己的地图4.1 编辑地图4.2 保存地图4.3 加载地图 5 常见问题 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底…