springboot+netty化身Udp服务端,go化身客户端模拟设备实现指令联动

news2024/10/7 16:23:20

🎏:你只管努力,剩下的交给时间

🏠 :小破站

springboot+netty化身Udp服务端,go化身客户端模拟设备实现指令联动

    • 🔗涉及链接
    • 前言
    • 异步通信的优势
      • 异步通信的优势:
      • 异步通信的应用场景:
    • 项目实现逻辑图
    • springboot与Netty结合
      • 1. 添加依赖
      • 2. 创建UDP服务端
      • 3. 创建UDP消息处理器
      • 4. 在Spring Boot中集成UDP服务端
      • 5.controller实现
    • Go语言模拟设备
    • 运行和测试
    • 性能优化与调优
      • 性能优化技巧:
      • 在高负载环境中调整UDP通信:
    • 安全性考量与加密通信
      • UDP通信的安全性问题:
      • 如何实现UDP通信的加密传输:

🔗涉及链接

🔗:探秘网络通信:UDP与TCP/IP的奥秘

🔗:CompletableFuture探秘:解锁Java并发编程的新境界

前言

在通信的大舞台上,UDP是一位默默贡献的明星。而当它与Spring Boot和Netty联手,再搭配Go语言的模拟设备,将掀起异步通信的新篇章。今天,我们将一同踏入这个奇妙的领域,揭开Spring Boot和Netty在UDP通信中的神秘面纱。

异步通信的优势

异步通信具有许多优势,特别是在处理大量连接、高并发和I/O密集型操作时。

异步通信的优势:

  1. 高并发处理: 异步通信使得系统可以在一个线程中处理多个请求,提高了系统的并发处理能力,特别适用于高并发的网络应用场景。
  2. 资源节约: 相比于同步阻塞模型,异步通信可以减少线程的创建和管理,节省系统资源,提高系统的性能和可伸缩性。
  3. 响应性: 异步通信允许系统在处理请求的同时继续接受新的请求,提高了系统的响应性,用户在得到响应之前不需要一直等待。
  4. 非阻塞I/O: 异步通信中,I/O操作是非阻塞的,一个线程可以处理多个I/O操作,避免了线程在等待I/O完成时的阻塞。

异步通信的应用场景:

  1. 网络服务: 适用于网络服务,特别是需要高并发和低延迟的场景,如实时通信、在线游戏等。
  2. 大规模连接: 适用于需要处理大量连接的场景,如聊天服务器、消息推送服务器等。
  3. I/O密集型任务: 适用于处理大量I/O密集型任务,如文件操作、数据库操作等。
  4. 事件驱动: 适用于事件驱动的应用,如消息队列、日志系统等。

项目实现逻辑图

在这里插入图片描述

springboot与Netty结合

将Spring Boot与Netty结合是为了利用Netty的高性能网络通信能力,而Spring Boot则提供了便捷的开发和集成环境。下面是详细介绍如何搭建一个高效的UDP服务端,使用Spring Boot和Netty实现。

1. 添加依赖

首先,在Spring Boot项目的pom.xml中添加Netty的依赖:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.69.Final</version> <!-- 替换为最新版本 -->
</dependency>

2. 创建UDP服务端

创建一个UDP服务端,使用Netty实现。下面是一个简单的示例:

package com.todoitbo.baseSpringbootDasmart.netty.server;

import com.todoitbo.baseSpringbootDasmart.netty.handler.UdpHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioDatagramChannel;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;

/**
 * @author todoitbo
 * @date 2023/11/29
 */
@Slf4j
public class NettyUdpServer {

    private final int nettyPort;

    public static Channel channel;

    public NettyUdpServer(int port) {
        this.nettyPort = port;
    }

    /**
     * 启动服务
     *
     * @throws InterruptedException
     */
    public void start() throws InterruptedException {
        // 连接管理线程池
        EventLoopGroup mainGroup = new NioEventLoopGroup(2);
        EventLoopGroup workGroup = new NioEventLoopGroup(8);

        try {
            // 工作线程池
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(mainGroup)
                    // 指定 nio 通道,支持 UDP
                    .channel(NioDatagramChannel.class)
                    // 广播模式
                    .option(ChannelOption.SO_BROADCAST, true)
                    // 设置读取缓冲区大小为 10M
                    .option(ChannelOption.SO_RCVBUF, 1024 * 1024 * 10)
                    // 设置发送缓冲区大小为 10M
                    .option(ChannelOption.SO_SNDBUF, 1024 * 1024 * 10)
                    // 线程池复用缓冲区
                    .option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT)
                    // 指定 socket 地址和端口
                    .localAddress(new InetSocketAddress(nettyPort))
                    // 添加通道 handler
                    .handler(new ChannelInitializer<NioDatagramChannel>() {
                        @Override
                        protected void initChannel(NioDatagramChannel nioDatagramChannel) throws Exception {
                            nioDatagramChannel.pipeline()
                                    // 指定工作线程,提高并发性能
                                    .addLast(workGroup,new UdpHandler());
                        }
                    });

            // 异步绑定服务器,调用sync()方法阻塞等待直到绑定完成
            ChannelFuture sync = bootstrap.bind().sync();
            channel = sync.channel();
            log.info("---------- [init] UDP netty server start ----------");

            // 阻塞等待服务器关闭
            channel.closeFuture().sync();
        } finally {
            // 释放资源
            mainGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
        }
    }
}

3. 创建UDP消息处理器

创建一个简单的UDP消息处理器,用于处理接收到的消息,且使用CompletableFuture来实现异步收发

package com.todoitbo.baseSpringbootDasmart.netty.handler;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;

import java.net.InetSocketAddress;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static com.todoitbo.baseSpringbootDasmart.controller.SysUploadController.socketAddressMap;

/**
 * @author todoitbo
 * @date 2023/11/29
 */
public class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {

    // 使用 CompletableFuture 用于异步获取客户端的响应
    public static CompletableFuture<String> responseFuture = new CompletableFuture<>();

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
        // 从DatagramPacket中获取数据和发送者信息
        byte[] data;
        int len = packet.content().readableBytes();

        if (packet.content().hasArray()) {
            data = packet.content().array();
        } else {
            data = new byte[len];
            packet.content().getBytes(packet.content().readerIndex(), data);
        }
        String senderAddress = packet.sender().getAddress().getHostAddress();
        int senderPort = packet.sender().getPort();

        // 处理接收到的数据
        String message = new String(data);
        System.out.println("Received message from " + senderAddress + ":" + senderPort + " - " + message);

        if (message.contains("test")) {
            responseFuture.complete(message);
        }

        // 构建响应消息
        String response = "Hello, client!";
        byte[] responseData = response.getBytes();

        // 创建响应的DatagramPacket并发送给发送者
        InetSocketAddress senderSocketAddress = new InetSocketAddress(senderAddress, senderPort);
        socketAddressMap.put("test", senderSocketAddress);
        DatagramPacket responsePacket = new DatagramPacket(Unpooled.copiedBuffer(responseData), senderSocketAddress);
        ctx.writeAndFlush(responsePacket);
    }

    /*@Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 处理异常情况
        cause.printStackTrace();
        ctx.close();
    }*/
    // 在接口调用后等待客户端响应的方法
    public static String waitForClientResponse() {
        try {
            // 使用 CompletableFuture 的 get 方法来阻塞等待客户端的响应
            String s = responseFuture.get(500, TimeUnit.MILLISECONDS);
            responseFuture = new CompletableFuture<>();
            return s; // 等待时间为 1 秒
        } catch (Exception e) {
            // 发生超时或其他异常,可以根据实际情况处理
            return "456"; // 超时返回默认值 "456"
        }
    }
}

⚠️:注意

在某些上下文中,将 CompletableFuture 声明为 public static 可行,但请注意这并不总是一个最佳实践。做出这个决定时需要考虑以下几点:
线程安全性 - CompletableFuture 是线程安全的,但是如果你在多个线程中设置其结果,你可能会遇到异常,因为 CompletableFuture 的结果只能被设置一次。
共享状态 - 任何可以访问这个 public static 变量的代码都可以改变其状态。这可能会导致你的代码难于理解和维护。
生命周期 - 这个 CompletableFuture 的生命周期与应用程序的生命周期一致,除非显式地设置为 null。 这可能在某些情况下会导致内存泄漏。
如果是为了协调或表示一个跨类或跨方法的异步操作的结果,使用 public static CompletableFuture 是可以接受的。但你需要意识到在静态上下文中共享的状态可能会导致的问题,并以适当的同步机制处理它们。

4. 在Spring Boot中集成UDP服务端

创建一个Spring Boot应用,并在应用启动时启动UDP服务端:

import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;

@SpringBootApplication
public class BaseSpringbootDasmartApplication {
   /* static {
        AspectLogEnhance.enhance();}//进行日志增强,自动判断日志框架*/
    public static void main(String[] args) {
        // System.setProperty("log4j2.isThreadContextMapInheritable", Boolean.TRUE.toString());
        SpringApplication.run(BaseSpringbootDasmartApplication.class, args);
        try {
            // new NettyWebsocketServer(13025).run();
            new NettyUdpServer(13026).start();
        } catch (Exception e) {
            throw new BusinessException("-----启动失败-----", e.getMessage()).setCause(e).setLog();
        }
    }
}

5.controller实现

@GetMapping("/login/{message}")
public String login(@PathVariable String message) throws NacosException, InterruptedException {

    byte[] responseData = message.getBytes();

    // 创建响应的DatagramPacket并发送给发送者
    DatagramPacket responsePacket = new DatagramPacket(Unpooled.copiedBuffer(responseData), socketAddressMap.get("test"));
    NettyUdpServer.channel.writeAndFlush(responsePacket);
    // 客户端是否响应,响应返回传入值,否则返回456,响应时间不超过0.5s,如果10.5s还未响应,则返回456
    return UdpHandler.waitForClientResponse();
}

Go语言模拟设备

下面是一个简单的Go语言程序,用于模拟UDP客户端,发送和接收指令。在这个例子中,我们使用Go的net包来处理UDP通信。下面的代码可以直接放到main中

// @Author todoitbo 2023/11/29 14:26:00
package utils

import (
    "context"
    "fmt"
    "net"
    "strings"
    "sync"
)

// UDPClient 是一个简单的 UDP 客户端
type UDPClient struct {
    conn *net.UDPConn
    mu   sync.Mutex
}

// NewUDPClient 创建一个新的 UDP 客户端
func NewUDPClient(serverAddr string) (*UDPClient, error) {
    client := &UDPClient{}

    addr, err := net.ResolveUDPAddr("udp", serverAddr)
    if err != nil {
       return nil, err
    }

    conn, err := net.DialUDP("udp", nil, addr)
    if err != nil {
       return nil, err
    }

    client.conn = conn
    message := []byte("你好")
    _, err = conn.Write(message)
    return client, nil
}

// Close 关闭 UDP 客户端连接
func (c *UDPClient) Close() {
    c.mu.Lock()
    defer c.mu.Unlock()
    if c.conn != nil {
       c.conn.Close()
    }
}

// ListenForMessages 启动 Goroutine 监听服务端的实时消息
func (c *UDPClient) ListenForMessages(ctx context.Context, wg *sync.WaitGroup) {
    defer wg.Done() // 在 Goroutine 结束时通知 WaitGroup

    buffer := make([]byte, 1024)
    for {
       select {
       case <-ctx.Done():
          // 收到关闭信号,结束 Goroutine
          return
       default:
          c.mu.Lock()
          conn := c.conn
          c.mu.Unlock()

          if conn == nil {
             // 客户端连接已关闭
             return
          }

          n, _, err := conn.ReadFromUDP(buffer)
          if err != nil {
             fmt.Println("Error reading from server:", err)
             return
          }

          // 处理收到的消息,可以根据实际需求进行逻辑处理
          message := string(buffer[:n])
          if strings.Contains(message, "test") {
             c.SendMessage(message)
          }

          // hexString := hex.EncodeToString(message)
          // 将 3600 转换为字符串
          /*expectedValue := "3600"
          if hexString == expectedValue {
             c.SendMessage("test")
          }else {
             c.SendMessage(message)
          }*/
          fmt.Println("Received message from server: ", message)
       }
    }
}

// SendMessage 向服务端发送消息
func (c *UDPClient) SendMessage(message string) error {
    c.mu.Lock()
    defer c.mu.Unlock()

    if c.conn == nil {
       return fmt.Errorf("client connection is closed")
    }

    _, err := c.conn.Write([]byte(message))
    return err
}

func InitUDPClient(ctx context.Context, wg *sync.WaitGroup, serverAddr string) (*UDPClient, error) {
    client, err := NewUDPClient(serverAddr)
    if err != nil {
       return nil, err
    }

    // 启动 Goroutine 监听服务端的实时消息
    wg.Add(1)
    go client.ListenForMessages(ctx, wg)

    return client, nil
}

func init() {
    InitUDPClient(context.Background(), &sync.WaitGroup{}, "127.0.0.1:13026")
}

⚠️:上面代码需要注意的地方

1️⃣:通用udp客户端建立

2️⃣:ListenForMessages 启动 Goroutine 监听服务端的实时消息

3️⃣:消息的处理,这里我使用的是字符串来接收,真正的设备应该是接收16进制的指令。

运行和测试

1️⃣:运行Spring Boot应用,UDP服务端将会在13026端口启动。你可以使用UDP客户端发送消息到该端口,然后在控制台看到服务端输出的消息。

2️⃣:运行go程序,可以在springboot控制台看到打印如下

在这里插入图片描述

3️⃣:调用接口,可以同时看到go程序,与springboot打印数据如下

在这里插入图片描述

这只是一个简单的示例,实际应用中可能需要根据具体需求进行更复杂的处理和逻辑。 Netty提供了强大的异步事件模型,适用于构建高性能、可伸缩的网络应用程序,而Spring Boot则为我们提供了更便捷的开发体验和集成环境。通过整合Spring Boot和Netty,你可以在网络通信方面获得更好的性能和灵活性。

性能优化与调优

性能优化和调优在高负载环境中是至关重要的,特别是在UDP通信这种无连接、不可靠的场景中。以下是一些性能优化的技巧和在高负载环境中调整UDP通信以获得最佳性能的建议:

性能优化技巧:

  1. 使用连接池: 对于UDP通信中的连接,考虑使用连接池来减少连接的创建和销毁开销,提高资源利用率。

  2. 调整缓冲区大小: 根据实际情况调整UDP通信中的缓冲区大小,以优化数据传输效率。

  3. 合并和拆分消息: 对于小消息,可以考虑合并多个小消息为一个大消息发送,减少网络开销。相反,对于大消息,可以考虑拆分为多个小消息发送,避免一次传输过大数据。

  4. 异步处理: 使用异步编程模型,将耗时的操作放在异步任务中处理,避免阻塞主线程。

  5. 压缩数据: 在需要传输大量数据时,可以考虑对数据进行压缩,减少数据传输的大小。

  6. 避免频繁GC: 减少对象的创建,特别是在高频率的UDP通信中,频繁的垃圾回收会对性能产生不利影响。

在高负载环境中调整UDP通信:

  1. 调整线程池大小: 在高负载环境中,适当调整线程池的大小,确保有足够的线程处理并发请求,避免线程池饱和。

  2. 优化消息处理逻辑: 对消息的处理逻辑进行优化,确保处理时间短,避免阻塞,提高处理能力。

  3. 调整超时设置: 对于需要等待响应的场景,调整超时设置,以适应高负载的情况,避免长时间的等待。

  4. 流量控制: 在高负载环境中,考虑实施流量控制,限制每个连接的最大流量,防止过多的数据堆积。

  5. 网络拓扑优化: 对于涉及多台服务器的场景,优化网络拓扑,减少数据传输的跳数,提高数据传输效率。

  6. 监控和调优: 使用性能监控工具对UDP通信进行监控,识别潜在的性能瓶颈,并进行相应的调优。

  7. 负载均衡: 对于UDP通信的负载均衡,确保负载均衡器能够合理地分发请求,避免某个节点过载。

在进行性能优化和调优时,需要根据具体的应用场景和性能测试结果进行调整。优化的效果可能因应用的特性而异,因此在实施之前最好进行充分的性能测试。

安全性考量与加密通信

UDP通信的主要特性是无连接和不可靠,相对于TCP,它缺乏内建的安全性机制,因此在UDP通信中需要额外关注安全性问题。以下是一些UDP通信中的安全性问题和如何实现UDP通信的加密传输的建议:

UDP通信的安全性问题:

  1. 数据完整性: UDP不提供数据完整性验证,因此数据在传输过程中可能会被篡改。攻击者可以修改、删除或注入数据。

  2. 数据机密性: UDP通信默认是明文传输的,攻击者可以轻松截取和查看通信中的数据,这对于敏感信息是一种风险。

  3. 重放攻击: 由于UDP通信不具备连接的概念,攻击者可以通过重放已经捕获的UDP数据包来模拟合法的通信。

如何实现UDP通信的加密传输:

  1. 使用加密算法: 选择合适的加密算法,如AES、DES等,对通信中的数据进行加密。确保使用足够强度的加密算法,并定期更新密钥。

  2. 消息认证码(MAC): 使用消息认证码对消息进行签名,以验证消息的完整性和真实性。HMAC(基于散列的消息认证码)是一个常见的选择。

  3. 密钥交换: 定期更换加密密钥,可以通过安全的密钥交换协议,如Diffie-Hellman密钥交换,来确保密钥的安全性。

  4. 防重放攻击: 使用时间戳或一次性令牌(One-Time Token)等机制防止重放攻击。在通信中引入时序元素,可以有效地防止攻击者重放过期的数据包。

  5. 数字签名: 对通信中的重要信息进行数字签名,确保数据的真实性和完整性。公钥基础设施(PKI)可以用于验证数字签名。

  6. 实现安全通信协议: 考虑使用已有的安全通信协议,如DTLS(Datagram Transport Layer Security),它是基于UDP的TLS版本,提供了加密和认证。

  7. 使用VPN或隧道: 在通信的底层使用安全的VPN(Virtual Private Network)或隧道技术,将UDP数据包进行封装,提供额外的安全性保障。

  8. 防止拒绝服务攻击: 在UDP通信中,由于缺少连接状态,可能容易受到拒绝服务攻击。采用流量限制、频率控制等手段来减缓拒绝服务攻击的影响。

实现UDP通信的加密传输需要综合考虑数据的机密性、完整性和身份验证等因素。选择合适的安全机制和协议取决于具体的应用场景和安全需求。

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

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

相关文章

腾讯云双11活动最后一天,错过再等一年!

腾讯云双11活动已经进入尾声&#xff0c;距离活动结束仅剩最后一天&#xff0c;记得抓住这次上云好时机&#xff0c;错过这次&#xff0c;就要等到下一年才能享受到这样的优惠力度了&#xff01; 活动地址&#xff1a; 点此直达腾讯云双11活动主会场 活动详情&#xff1a; 1…

Retrofit中的注解

一、Retrofit中的注解有那些&#xff1f; 方法注解&#xff1a;GET ,POST,PUT,DELETE,PATH,HEAD,OPTIONS,HTTP标记注解&#xff1a;FormUrlEncoded&#xff0c;Multpart&#xff0c;Streaming参数注解&#xff1a;Query&#xff0c;QueryMap&#xff0c;Body&#xff0c;Field…

vue+el-tooltip 封装提示框组件,只有溢出才提示

效果 封装思路 通过控制el-tooltip的disabled属性控制是否提示通过在内容上绑定mouseenter事件监听内容宽度和可视宽度&#xff0c;判断内容是否溢出 封装代码 <template><div style"display: flex" class"column-overflow"><el-tooltip…

XUbuntu22.04之OBS强大录屏工具(一百九十五)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【Linux】ln命令使用

ln命令 ln是linux中又一个非常重要命令&#xff0c;请大家一定要熟悉。它的功能是为某一个文件在另外一个位置建立一个同步的链接&#xff0c;这个命令最常用的参数是-s&#xff0c;具体用法是&#xff1a;ln –s 源文件 目标文件。 当我们需要在不同的目录&#xff0c;用到相…

第三方检测机构实验室信息管理系统LIMS全套源码

​LIMS实验室信息管理系统源码&#xff0c;支持二次开发 LIMS实验室信息管理系统是一种软件类型&#xff0c;旨在通过跟踪与样品、实验、实验室工作流程和仪器相关的数据&#xff0c;提高实验室产能和效率。覆盖实验室从合同审批、委托下单、样品管理、生产调度、检测记录、报告…

Talk | UCSB博士生许闻达:细粒度可解释评估初探

本期为TechBeat人工智能社区第551期线上Talk。 北京时间11月29日(周三)20:00&#xff0c;UC Santa Barbara博士生—许闻达的Talk将准时在TechBeat人工智能社区开播&#xff01; 他与大家分享的主题是: “细粒度可解释评估初探”&#xff0c;分享了他们团队在具备解释性的细粒度…

彻底删除VsCode配置和安装过的插件与缓存

前言 当你准备对 Visual Studio Code&#xff08;VSCode&#xff09;进行重新安装时&#xff0c;可能遇到一个常见问题&#xff1a;重新安装后&#xff0c;新的安装似乎仍然保留了旧的配置信息&#xff0c;这可能会导致一些麻烦。这种情况通常是由于卸载不彻底所致&#xff0c…

MySQL官网推荐书籍

MySQL官网推荐书籍 图片有防盗链csdn转存失败。有图版传送门MySQL官网推荐书籍 高效的MySQL性能&#xff1a;Daniel Nichter的最佳实践和技术 Daniel Nichter 向您展示了如何应用直接影响 MySQL 性能的最佳实践和技术。您将学习如何通过分析查询执行、为常见 SQL 子句和表联接…

一套后台管理系统的入门级的增删改查(vue3组合式api+elemment-plus)

一、页面示意&#xff1a; 图一 图二 二、组件结构 列表组件 &#xff1a;index.vue,对应图一添加组件&#xff1a;add.vue&#xff0c;对应图二&#xff0c;用抽屉效果编辑组件&#xff1a;edit.vue&#xff0c;和添加组件的效果一个。 三、代码 1、列表组件: index.vue …

Ubuntu使用Nginx部署前端项目——记录

安装nginx 依次执行以下两条命令进行安装&#xff1a; sudo apt-get update sudo apt-get install nginx通过查看版本号查看是否安装成功&#xff1a; nginx -v补充卸载操作&#xff1a; sudo apt-get remove nginx nginx-common sudo apt-get purge nginx nginx-common su…

为计算机设计一个完美的思维模型,帮找bug和漏洞,一起来做渗透测试吧 最赚钱的10种思维模型

芒格 如果我不能淘汰自己一年前的思维模型&#xff0c;这一年我就白过了。&#xff08;终身学习&#xff0c;不断迭代自己。&#xff09; 思维模型是什么&#xff0c;有哪些&#xff1f; 思维模型是用来简化和理解复杂现实世界的概念框架。它们是一种思考和解决问题的工具&a…

8个最流行的Revit插件【2023-2024】

NSDT工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 Revit 已取代 AutoCAD 成为全球行业标准软件。 随着设计复杂性的增加&#xff0c;近年来插件变得更加必要。 热…

【腾讯地图】【微信小程序】地图选点

【相关文章】 【腾讯地图】【微信小程序】地图选点 【腾讯地图】【微信小程序】路线规划 【腾讯地图】【微信小程序】城市记录&#xff08;基于地图选点入门版&#xff09; 【效果展示】 【官方文档】 微信小程序插件-地图选点插件 【完善流程】 当前操作和官方文档操作有部…

06 # 枚举类型

一个角色判断例子 function initByRole(role) {if (role 1 || role 2) {// do sth} else if (role 3 || role 4) {// do sth} else if (role 5) {// do sth} else {// do sth} }上面的代码存在的问题&#xff1a; 可读性差&#xff1a;很难记住数字的含义可维护性差&…

前端笔记:React的form表单全部置空或者某个操作框置空的做法

原创/朱季谦 在React框架前端开发中&#xff0c;经常会有弹出框的开发&#xff0c;涉及到弹出框&#xff0c;难免就会有表单。一般在关闭弹出框或者对表单联动时&#xff0c;往往都需要考虑对表单进行置空操作了。 我以前在工作就遇到过这类问题&#xff0c;正好顺便对表单置空…

Linux RN6752 驱动编写

一、概述 关于 RN6752V1 这个芯片这里就不做介绍了&#xff0c;看到这篇笔记的小伙伴应该都明白&#xff0c;虽然说 RN6752V1 芯片是 AHD 信号的解码芯片&#xff0c;但是也可以把芯片当做是一个 YUV 信号的 MIPI 摄像头&#xff0c;所以驱动的编写和 MIPI 摄像头无太大的区别。…

Docker篇之docker部署harbor仓库

一、首先需要安装docker step1&#xff1a;安装docker #1、安装yun源 yum install -y yum-utils #2、配置yum源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 如果上面源不稳定的话&#xff0c;更换为下列的aliyun源 yu…

爬虫学习 逆向爬虫(六)

多任务异步协程 协程:更高效的利用CPU import timedef func():print("黎明")time.sleep(3)print("还是黎明")func() 等待时机长 sleep时CPU不再工作 IO操作(费时不费力)->阻塞 线程运行阻塞后 移出主线程 移动到下一个 4个任务一个线程 …

首次部署Linux系统的经历

我是一名电子信息工程专业的学生&#xff0c;有次在图书馆上自习的时候无意间看到其他同学的电脑屏幕&#xff0c;黑色的屏幕上显示着一行一行的代码&#xff0c;勾起了我无限的好奇&#xff0c;经过询问得知他是用的Linux操作系统&#xff0c;是和Windows完全不同的系统&#…