Spingboot 整合netty-socket.io

news2024/11/29 10:44:40

Spingboot 整合netty-socket.io

  1. 依赖

    注意版本号,不然client版本不对的话也是连不上的

    https://github.com/mrniko/netty-socketio

​ ``

<dependency>
    <groupId>com.corundumstudio.socketio</groupId>
    <artifactId>netty-socketio</artifactId>
</dependency>


<zyy-socket.version>2.0.2</zyy-socket.version>
  1. 结构

在这里插入图片描述

3 配置

package com.zyy.wss.config;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.corundumstudio.socketio.SocketConfig;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import com.zyy.framework.model.constant.SecurityConstants;
import com.zyy.wss.handler.NettySocketEventHandler;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;

/**
 * @author: liucw
 * @createDate: 2023/4/26
 * @description:
 */


@Configuration
@Slf4j
public class NettySocketConfig {
    @Value("${socket.port}")
    private Integer socketPort;
    @Value("${socket.upgradeTimeout}")
    private Integer upgradeTimeout;
    @Value("${socket.pingInterval}")
    private Integer pingInterval;
    @Value("${socket.pingTimeout}")
    private Integer pingTimeout;

    @Value("${socket.bossThreadCount}")
    private Integer bossThreadCount;

    @Value("${socket.workerThreadCount}")
    private Integer workerThreadCount;
    @Resource
    private NettySocketEventHandler nettySocketEventHandler;


    @Resource
    private TokenStore tokenStore;

    @Bean
    public SocketIOServer socketIOServer() {
        /*
         * 创建Socket,并设置监听端口
         */
        com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
        // 设置主机名,默认是0.0.0.0
        config.setHostname("0.0.0.0");
        // 设置监听端口
        config.setBossThreads(bossThreadCount);
        config.setWorkerThreads(workerThreadCount);
        config.setAllowCustomRequests(true);
        config.setPort(socketPort);
        // 协议升级超时时间(毫秒), 默认10000, HTTP握手升级为ws协议超时时间
        config.setUpgradeTimeout(upgradeTimeout);
        // Ping消息间隔(毫秒), 默认25000, 客户端向服务器发送一条心跳消息间隔
        config.setPingInterval(pingInterval);
        // Ping消息超时时间(毫秒), 默认60000, 这个时间间隔内没有接收到心跳消息就会发送超时事件
        config.setPingTimeout(pingTimeout);
        // 开放跨域 null 不是*
        config.setOrigin("*");
        SocketConfig socketConfig = new SocketConfig();
        socketConfig.setReuseAddress(true);
        socketConfig.setTcpNoDelay(true);
        socketConfig.setSoLinger(0);
        config.setSocketConfig(socketConfig);

        config.setAuthorizationListener(handshakeData -> {
            Map<String, List<String>> urlParams = handshakeData.getUrlParams();
            String accessToken = handshakeData.getSingleUrlParam("Authorization");
            log.info(" handshakeData  url  req :{} ,authorization:{}", JSONUtil.toJsonStr(urlParams), accessToken);
            if (StrUtil.isBlank(accessToken)) {
                return false;
            }
            OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(StrUtil.replaceIgnoreCase(accessToken, SecurityConstants.JWT_PREFIX, Strings.EMPTY));
            return !oAuth2AccessToken.isExpired();
        });
        SocketIOServer socketIOServer = new SocketIOServer(config);
        socketIOServer.addListeners(nettySocketEventHandler);
        return socketIOServer;
    }

    @Bean
    public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
        return new SpringAnnotationScanner(socketServer);
    }
}

4 .ClientCache

``

package com.zyy.wss.config;

import com.corundumstudio.socketio.SocketIOClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author litong
 * @date 2019/11/6 16:01
 */
@Component
@Slf4j
public class ClientCache {

    /**
     * 本地缓存
     */
    private static Map<String, HashMap<UUID, SocketIOClient>> concurrentHashMap = new ConcurrentHashMap<>();

    /**
     * 存入本地缓存
     *
     * @param userId         用户ID
     * @param sessionId      页面sessionID
     * @param socketIOClient 页面对应的通道连接信息
     */
    public void saveClient(String userId, UUID sessionId, SocketIOClient socketIOClient) {
        HashMap<UUID, SocketIOClient> sessionIdClientCache = concurrentHashMap.get(userId);
        if (sessionIdClientCache == null) {
            sessionIdClientCache = new HashMap<>();
        }
        sessionIdClientCache.put(sessionId, socketIOClient);
        concurrentHashMap.put(userId, sessionIdClientCache);

        log.info("ClientCache :{}",concurrentHashMap.get(userId).size());

    }

    /**
     * 根据用户ID获取所有通道信息
     *
     * @param userId
     * @return
     */
    public HashMap<UUID, SocketIOClient> getUserClient(String userId) {
        return concurrentHashMap.get(userId);
    }

    /**
     * 根据用户ID及页面sessionID删除页面链接信息
     *
     * @param userId
     * @param sessionId
     */
    public void deleteSessionClient(String userId, UUID sessionId) {
        concurrentHashMap.get(userId).remove(sessionId);
    }
}

5 . 连接处理类

package com.zyy.wss.handler;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.zyy.admin.api.dto.common.NumberNotDownloadedDto;
import com.zyy.admin.api.enums.common.ExportEnums;
import com.zyy.common.api.feign.DownloadCenterFeign;
import com.zyy.framework.model.dto.result.data.DataResult;
import com.zyy.framework.model.enums.TenantEnums;
import com.zyy.framework.model.enums.base.EnumUtils;
import com.zyy.wss.config.ClientCache;
import com.zyy.wss.entity.DownLoadData;
import com.zyy.wss.entity.MessageInfoStructure;
import com.zyy.wss.entity.TokenInfo;
import com.zyy.wss.enums.MsgTypeEnum;
import com.zyy.wss.helper.JWTHelper;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.Objects;
import java.util.UUID;

/**
 * @author: liucw
 * @createDate: 2023/4/26
 * @description:
 */
@Component
@Slf4j
public class NettySocketEventHandler {
    @Resource
    private ClientCache clientCache;
    @Resource
    private JWTHelper jwtHelper;
    @Resource
    private DownloadCenterFeign downloadCenterFeign;

    /**
     * socket事件消息接收入口
     */
    @OnEvent(value = "message_event") //value值与前端自行商定
    public void onEvent(SocketIOClient client, AckRequest ackRequest, MessageInfoStructure data) {
        boolean ackRequested = ackRequest.isAckRequested();
        // 获取前端推送数据
        log.info("onEvent :{}", JSONUtil.toJsonStr(data));
        //根据msgType类别进行数据类型判断,
        if (Objects.nonNull(data.getMsgType()) && data.getMsgType().equals(MsgTypeEnum.EXPORT_POLLING.getValue())) {
            TokenInfo clientKey = jwtHelper.getClientKey(client);
            assert clientKey != null;
            DataResult<Integer> integerDataResult = null;
            if (data.getTenantId().equals(TenantEnums.ZMP_BACK.getId())) {
                integerDataResult = downloadCenterFeign.downloadableCount(NumberNotDownloadedDto.builder().userType(ExportEnums.ExportUserType.DAY_PIVOT.getUserType()).userId(clientKey.getUserId()).build());
            } else if (data.getTenantId().equals(TenantEnums.ZMP_BEND.getId())) {
                integerDataResult = downloadCenterFeign.downloadableCount(NumberNotDownloadedDto.builder().userType(ExportEnums.ExportUserType.CLOUD_CHAIN.getUserType()).userId(clientKey.getCustomerId()).build());
            }
            log.info("integerDataResult :{}", JSONUtil.toJsonStr(integerDataResult));
            if (StrUtil.isNotBlank(data.getMsgContent())) {
                DownLoadData downLoadData = (DownLoadData) JSONUtil.toBean(data.getMsgContent(), EnumUtils.of(MsgTypeEnum.class, MsgTypeEnum.EXPORT_POLLING.getValue()).getContentType());
                log.info("Json: " + downLoadData);
            }
            //数据类型标识
            //向前端发送接收数据成功标识
            //这里可填写接收数据后的相关业务逻辑代码
            client.sendEvent("message_event", integerDataResult);

        }

    }

    /**
     * socket添加@OnDisconnect事件,客户端断开连接时调用,刷新客户端信息
     */

    @OnDisconnect
    public void onDisconnect(SocketIOClient client) {
        log.info("--------------------客户端已断开连接--------------------");
        //client.disconnect();
        TokenInfo clientKey = jwtHelper.getClientKey(client);
        if (Objects.isNull(clientKey)) {
            log.error("Client key token error");
            return;
        }
        clientCache.deleteSessionClient(String.valueOf(clientKey.getUserId()), client.getSessionId());
    }

    /**
     * socket添加connect事件,当客户端发起连接时调用
     */
    @OnConnect
    public void onConnect(SocketIOClient client) {
        if (client != null) {
            TokenInfo clientKey = jwtHelper.getClientKey(client);
            if (Objects.isNull(clientKey)) {
                log.error("Client key token error");
                return;
            }
            //存储SocketIOClient,用于向不同客户端发送消息
            // socketIOClientMap.put(mac, client);
            UUID sessionId = client.getSessionId();
            clientCache.saveClient(String.valueOf(clientKey.getUserId()), sessionId, client);
            log.info("--------------------客户端连接成功---------------------");

            client.sendEvent("connected", "connected", sessionId);
        } else {
            log.error("客户端为空");
        }
    }

    /**
     * 广播消息 函数可在其他类中调用
     */
/*    public static void sendBroadcast(byte[] data) {
        for (SocketIOClient client : socketIOClientMap.values()) {
            //向已连接的所有客户端发送数据,map实现客户端的存储
            if (client.isChannelOpen()) {
                client.sendEvent("message_event", data);
            }
        }
    }*/
}

6.工具处理类

package com.zyy.wss.helper;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;

import com.corundumstudio.socketio.SocketIOClient;
import com.google.common.collect.Maps;
import com.nimbusds.jose.JWSObject;
import com.zyy.framework.model.constant.SecurityConstants;
import com.zyy.wss.entity.TokenInfo;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.text.ParseException;
import java.util.Map;

/**
 * @author: liucw
 * @createDate: 2023/4/28
 * @description:
 */

@Component
@Slf4j
public class JWTHelper {

    @Resource
    private TokenStore tokenStore;


    public static Boolean verifyZyToken(String token) {

        token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY);
        String payload;
        try {
            payload = StrUtil.toString(JWSObject.parse(token).getPayload());
        } catch (ParseException e) {
            log.error("验证token失败", e);
            return false;
        }
        TokenInfo tokenInfo = JSONUtil.toBean(payload, TokenInfo.class);
        log.info("token: {}", tokenInfo);
        return tokenInfo.isAuth();
    }

    public static TokenInfo parseZyToken(String token) {

        token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY);
        String payload;
        try {
            payload = StrUtil.toString(JWSObject.parse(token).getPayload());
        } catch (ParseException e) {
            log.error("验证token失败", e);
            return null;
        }
        return JSONUtil.toBean(payload, TokenInfo.class);

    }

    public TokenInfo parseZyTokenTwo(OAuth2AccessToken token) {
        Map<String, Object> source = token.getAdditionalInformation();
        Map<String, String> mapping = Maps.newHashMap();
        mapping.put("username", "username");
        mapping.put("customerId", "customerId");
        mapping.put("userId", "userId");
        return BeanUtil.mapToBean(source, TokenInfo.class, true, CopyOptions.create().setFieldMapping(mapping));
    }


    public TokenInfo getClientKey(SocketIOClient client) {

        String authorization = client.getHandshakeData().getSingleUrlParam("Authorization");
        if (StrUtil.isBlank(authorization)) {
            return null;
        }
        authorization = StrUtil.replaceIgnoreCase(authorization, SecurityConstants.JWT_PREFIX, Strings.EMPTY);
        // TokenStore tokenStore = SpringContextHelper.getBean("tokenStore", TokenStore.class);
        return parseZyTokenTwo(tokenStore.readAccessToken(authorization));

    }

  /*  public static TokenInfo getClientKey(SocketIOClient client) {


        String authorization = client.getHandshakeData().getSingleUrlParam("Authorization");
        if (StrUtil.isBlank(authorization)) {
            return null;
        }
        return parseZyToken(authorization);

    }*/


    public static void main(String[] args) throws ParseException {

        String token = "bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJMaWFvMSIsImlwIjoiMTEzLjExMC4yMjEuNSIsImF1dGhlbnRpY2F0aW9uSWRlbnRpdHkiOiJ1c2VybmFtZSIsImF2YXRhciI6bnVsbCwidXNlcklkIjoxNjUxNDEwMDc3MTQ5Njc5NjE4LCJhdXRob3JpdGllcyI6WyJCRU5EOklvdC9NaWZp55So5oi36L-Q6JClIl0sImNsaWVudF9pZCI6InptcC1iZW5kIiwic2NvcGUiOlsiYWxsIl0sImZpcnN0TG9naW5TdGF0dXMiOjAsInRlbmFudElkIjoxMSwiY3VzdG9tZXJJZCI6MTY1MTQxMDA3Njg1MTg1NTM2MSwiZXhwIjoxNjgyNjg4NTU5LCJqdGkiOiI2MTY5ODRiOC1jZDU3LTRmYmItOTMxMS05YzNhNTdlZjVmOTciLCJ1c2VybmFtZSI6IkxpYW8xIn0.jHwvZc3vCwDzTfPJ26-7_wfKOehmfRCxEiKe3l79up5gBaYDSIWb3lTSsdORfhmCiRBRJPvhts19DJIwa3xbieBTDUoN8eEENUBGVblHzTMCgdprms88WCtLue6-BpZ6YnEiBXK1vNLStrdi7zf7JohKwdeXb7OojgKN-5PX2yLuaPZSQMiJmCDQ597q7Lx7DkmR5MdcAqEU2bUuTrFTtqgQDcyfARAqYxra2JFVreVOjnVJoZqtIkYE16lSt5_7IEQYqgzdcccq6hnuZmD7eqcg0vh3KDO94DBZncWEu08CJZ427cpFZBL0n5rTplFFnxNHy8LtX-MiZSZkJHFKjA";
        token = StrUtil.replaceIgnoreCase(token, SecurityConstants.JWT_PREFIX, Strings.EMPTY);

        String payload = StrUtil.toString(JWSObject.parse(token).getPayload());
        TokenInfo tokenInfo1 = JSONUtil.toBean(payload, TokenInfo.class);

        boolean auth = tokenInfo1.isAuth();
        //  TokenInfo tokenInfo = verifyToken(token);
        System.out.println(JSONUtil.toJsonStr(auth));


    }

}

7.NettySocketRunnable

package com.zyy.wss.runner;

import com.corundumstudio.socketio.SocketIOServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author: liucw
 * @createDate: 2023/4/26
 * @description:
 */

@Component
@Order(value = 1)
@Slf4j
public class NettySocketRunnable implements CommandLineRunner {

    private final SocketIOServer server;

    @Autowired
    public NettySocketRunnable(SocketIOServer server) {
        this.server = server;
    }

    @Override
    public void run(String... args) {
        log.info("--------------------SocketIOServer socket.io通信启动成功!---------------------");
        server.start();
    }
}

yaml 配置

socket:
  port: 9090
  upgradeTimeout: 10000
  pingInterval: 60000
  pingTimeout: 180000
  bossThreadCount : 1
  workerThreadCount : 100

8:nginx 配置 协议升级 wss(443) 或ws(80)

map $http_upgrade $connection_upgrade {
        default upgrade;
        '' close;
    }
   upstream socket {
        server 127.0.0.1:9090 ;
                 keepalive 256;
    }
 location /socket.io/ {
          proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
          proxy_set_header Host $http_host;
          proxy_pass http://socket;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection "upgrade";
          tcp_nodelay on;
    }

1: 注意server的版本和client版本是对应上的

2: 上线后注意线上是否有cdn 注意 如果有需要开启cdn支持websocket

1: 注意server的版本和client版本是对应上的

2: 上线后注意线上是否有cdn 注意 如果有需要开启cdn支持websocket

支持多个节点 清集群

public interface SocketIOService {


    void pushMessageToUser(String eventName, String userId, Object msgContent);

    void sendToAll(String eventName, Object msgContent);

    void sendMessage(String eventName, String userId, Object msgContent);
}
package com.zyy.wss.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.corundumstudio.socketio.SocketIOClient;
import com.zyy.wss.config.ClientCache;
import com.zyy.wss.config.properties.SocketIoServerProperties;
import com.zyy.wss.entity.MessageInfoStructure;
import com.zyy.wss.service.SocketIOService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RTopic;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * @author: liucw
 * @createDate: 2023/5/23
 * @description:
 */

@Service
@Slf4j
public class SocketIOServiceImpl implements SocketIOService {


    @Resource
    private ClientCache clientCache;

    @Resource
    private RedissonClient redissonClient;

    @Resource
    private SocketIoServerProperties serverProperties;
    /**
     * 广播(群发)前缀
     */
    private static final String MASS_PREFIX = "/mass";
    /**
     * socketio
     */
    private static final String TOPIC_SOCKETIO_SINGLE = "socketio:single";

    private static final String TOPIC_SOCKETIO_TOALL = "socketio:toAll";


    @Override
    public void pushMessageToUser(String eventName, String userId, Object msgContent) {

        HashMap<UUID, SocketIOClient> userClient = clientCache.getUserClient(userId);
        if (userClient == null) {
            log.debug("没有在线的用户");
            return;
        }
        userClient.forEach((uuid, socketIOClient) -> {
            //向客户端推送消息
            socketIOClient.sendEvent(eventName, msgContent);
        });


    }

    @Override
    public void sendToAll(String topic, Object msgContent) {
        if (StrUtil.isBlank(topic)) {
            topic = MASS_PREFIX + "/toAll";
        }
        clientCache.sendBroadcast(topic, msgContent);
        //socketIOServer.getBroadcastOperations().sendEvent(topic, msgContent);

    }

    @Override
    public void sendMessage(String eventName, String userId, Object msgContent) {
        MessageInfoStructure socketIOMessageDTO = MessageInfoStructure.builder().topic(eventName).userId(userId).msgContent(JSONUtil.toJsonStr(msgContent)).build();

        if (StrUtil.isNotBlank(userId)) {
            if (Objects.nonNull(serverProperties) && serverProperties.isCluster()) {
                RTopic rTopic = redissonClient.getTopic(TOPIC_SOCKETIO_SINGLE);
                rTopic.publish(socketIOMessageDTO);
            } else {
                pushMessageToUser(eventName, socketIOMessageDTO.getUserId(), msgContent);
            }
        } else {
            RTopic rTopic = redissonClient.getTopic(TOPIC_SOCKETIO_TOALL);
            rTopic.publish(socketIOMessageDTO);
            //sendToAll(eventName, msgContent);
        }
    }

    @PostConstruct
    public void init() {
        if (redissonClient == null) {
            return;
        }
        RTopic topic = redissonClient.getTopic(TOPIC_SOCKETIO_SINGLE);
        topic.addListener(MessageInfoStructure.class, (channel, msg) -> {

            if (StrUtil.isNotBlank(msg.getUserId())) {
                pushMessageToUser(msg.getTopic(), msg.getUserId(), JSONUtil.toBean(msg.getMsgContent(), Map.class));
                log.info(" {} {} {} {} {}", serverProperties.getPort(), channel.toString(), msg.getTopic(), msg.getUserId(), msg.getMsgContent());
            }
        });

        RTopic broadcast = redissonClient.getTopic(TOPIC_SOCKETIO_TOALL);
        broadcast.addListener(MessageInfoStructure.class, (channel, msg) -> {
            sendToAll(msg.getTopic(), JSONUtil.toBean(msg.getMsgContent(), Map.class));
            log.info(" sendToAll  {} {} {} {} {}", serverProperties.getPort(), channel.toString(), msg.getTopic(), msg.getUserId(), msg.getMsgContent());
        });
    }

}

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

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

相关文章

使用TensorFlow训练深度学习模型实战(下)

大家好&#xff0c;本文接TensorFlow训练深度学习模型的上半部分继续进行讲述&#xff0c;下面将介绍有关定义深度学习模型、训练模型和评估模型的内容。 定义深度学习模型 数据准备完成后&#xff0c;下一步是使用TensorFlow搭建神经网络模型&#xff0c;搭建模型有两个选项…

年轻小伙爆肝ARST

关于 ARTS 的释义 —— 每周完成一个 ARTS&#xff1a; ⭐️● Algorithm: 每周至少做一个 LeetCode 的算法题 ⭐️● Review: 阅读并点评至少一篇技术文章 ⭐️● Tips: 学习至少一个技术技巧 ⭐️● Share: 分享一篇有观点和思考的技术文章 希望通过此次活动能聚集一波热爱技…

GitLab 删除项目

1.点击头像 2.点击Profile 3.选择要删除的项目点进去 4.settings-general-Advances-expand 5.然后在弹出框中输入你要删除的项目名称即可

【WEB开发】Java获取高德POI(关键词搜索法)实现数据展示

前言 该篇文章是关键词搜索法获取高德poi&#xff0c;但鉴于无法突破200条记录的上限&#xff0c;所以采用了本方法进行区/县循环检索。开始之前我们首先需要明白一些常识 poi是兴趣点&#xff0c;它本身除了经纬度&#xff0c;还记录了一些信息&#xff0c;如名称、地址、联…

mirror功能

实现方式 mirror逻辑的工作阶段&#xff1a; ngx在log phase之后&#xff08;在ngx_http_free_request处调用&#xff09;已完成向client端返回response&#xff0c;在log phase之后完成close connection&#xff08;短链接&#xff09;&#xff0c;在该阶段处理mirror逻辑不…

【Redis】高级篇: 一篇文章讲清楚Redis的单线程和多线程

目录 面试题 Redis到底是多线程还是单线程&#xff1f; 简单回答 详解 Redis的“单线程” Redis为什么选择单线程&#xff1f; 后来Redis为什么又逐渐加入了多线程特性&#xff1f; Redis为什么快&#xff1f; 回答 IO多路复用 Unix网络编程的5种IO模型 主线程和IO…

温湿度传感器的工作原理及应用领域你了解多少呢?

传感器是一种将物理量转换为电信号的装置&#xff0c;用于检测温度、湿度、压力、光强、震动等物理量。它能够将检测到的物理量转换为电信号&#xff0c;并输送到计算机、单片机等设备进行分析和处理。生产生活中&#xff0c;不同的场所和环境对温湿度有着特殊要求&#xff0c;…

看完即会,抓取微信小程序数据包教程

在给VIP学员答疑的时候&#xff0c;有很多小伙伴问到能不能抓取到微信小程序数据呢&#xff1f;答案当然是肯定的&#xff0c;通过Fiddler或者Charles这些主流的抓包工具都可以抓得到&#xff0c;在IOS平台抓取微信小程序和https请求都是一样的设置&#xff0c;接下来给大家通过…

【代码随想录day20】验证二叉搜索树

题目 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义如下&#xff1a; 节点的左子树只包含 小于 当前节点的数。 节点的右子树只包含 大于 当前节点的数。 所有左子树和右子树自身必须也是二叉搜索树。 思路 最开始想简单…

Linux 学习记录58(ARM篇)

Linux 学习记录58(ARM篇) 本文目录 Linux 学习记录58(ARM篇)一、GIC相关寄存器1. 系统框图2. 中断号对应关系 二、GICD寄存器1. GICD_CTLR2. GICD_ISENABLERx3. GICD_IPRIORITYRx4. GICD_ITARGETSRx5. GICD_ICPENDRx 三、GICC寄存器1. GICC_PMR2. GICC_CTLR3. GICC_IAR4. GICC_…

JAVA面试总结-Redis篇章(五)——持久化

Java面试总结-Redis篇章&#xff08;五&#xff09;——持久化 1.RDBRDB全称Redis Database Backup file (Redis数据备份文件)&#xff0c;也被叫做Redis数据快照。简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后&#xff0c;从磁盘读取快照文件&#x…

持续贡献开源力量,棱镜七彩加入openKylin

近日&#xff0c;棱镜七彩签署 openKylin 社区 CLA&#xff08;Contributor License Agreement 贡献者许可协议&#xff09;&#xff0c;正式加入openKylin 开源社区。 棱镜七彩成立于2016年&#xff0c;是一家专注于开源安全、软件供应链安全的创新型科技企业。自成立以来&…

短视频账号矩阵系统源码开发部署路径

一、短视频批量剪辑的开发逻辑算法 1.视频剪辑之开发算法 自己研发视频剪辑是指通过对视频素材进行剪切、调整、合并等操作&#xff0c;利用后台计算机算法&#xff0c;进行抽帧抽组抽序进行排列以达到对视频内容进行修改和优化的目的。自己研发的视频剪辑工具可以通过后台码…

系统集成项目管理工程师挣值分析笔记大全

系统集成项目管理工程师挣值分析笔记大全 挣值分析是一种项目管理技术&#xff0c;用于量化和监控项目绩效。它通过比较计划值&#xff08;PV&#xff09;、实际成本&#xff08;AC&#xff09;和挣值&#xff08;EV&#xff09;三个参数来评估项目的进展情况和成本绩效。 挣值…

系统架构设计师-软件架构设计(3)

目录 一、软件架构风格&#xff08;其它分类&#xff09; 1、闭环控制结构&#xff08;过程控制&#xff09; 2、C2风格 3、MDA&#xff08;模型驱动架构 Model Driven Architecture&#xff09; 4、特定领域软件架构&#xff08;DSSA&#xff09; 4.1 DSSA基本活动及产出物…

《CUDA C++ Programming Guide》第一章 CUDA介绍

第一章 CUDA介绍 1.1 使用GPUs的好处 在相同的价格和功耗范围内&#xff0c;图形处理器 GPU 比 CPU 提供了更高的指令吞吐量和内存带宽, 许多应用程序利用这些更高的功能在 GPU 上比在 CPU 上运行得更快。相比较其他计算设备&#xff0c;如 FPGA&#xff0c;也是非常节能的&a…

协议和模型

1 规则 1.1 通信基础知识 不同网络的规模、形状和功能都存在很大差异。它们可以复杂到通过互联网来连接设备&#xff0c;也可以简单到直接将两台计算机用一根电缆连接&#xff0c;或者是介于这两种之间。然而&#xff0c;只是完成终端设备之间的有线或无线物理连接并不足以实…

MATLAB基础知识回顾

目录 1.帮助命令 2.数据类型 3.元胞数组和结构体 4.矩阵操作 4.1 矩阵的定义与构造 4.2 矩阵的四则运算 4.3 矩阵的下标 5.程序结构 5.1 for循环结构 5.2 分支结构 7.基本绘图操作 7.1.二维平面绘图 6.2 三维立体绘图 7.图形的保存与导出 8.补充 语句后⾯加;的作…

Kaggle图表内容识别大赛TOP方案汇总

赛题名称&#xff1a;Benetech - Making Graphs Accessible 赛题链接&#xff1a;https://www.kaggle.com/competitions/benetech-making-graphs-accessible 赛题背景 数以百万计的学生有学习、身体或视力障碍&#xff0c;导致人们无法阅读传统印刷品。这些学生无法访问科学…

人机合一Linux

未来云系统成为主流&#xff0c;维护电脑这种充满时代特性的技术&#xff0c;完全不重要了。 无论是学习还是工作&#xff0c;电脑都是IT人必不可少的重要武器&#xff0c;一台好电脑除了自身配置要经得起考验&#xff0c;后期主人对它的维护也是决定它寿命的重要因素&#xff…