netty使用redis发布订阅实现消息推送

news2024/11/25 2:49:41

netty使用redis发布订阅实现消息推送

场景

项目中需要给用户推送消息:

在这里插入图片描述

接口

@RestController
public class PushApi {

    @Autowired
    private PushService pushService;

    /**
     * 消息推送
     * @param query
     * @return
     */
    @PostMapping("/push/message")
    public String push(@RequestBody MessagePushConfigDto query){
        pushService.push(query);
        return "success";
    }


}

@Component
@Slf4j
public class PushService {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private MessageService messageService;

    public void push(MessagePushConfigDto query) {
        String messageNo = UUID.randomUUID().toString();
        if (query.getType()== Constants.MSG_TYPE_ALL){
            doPushGroup(query, messageNo);
        }else {
            doPushToUser(query, messageNo);
        }
    }

    private void doPushGroup(MessagePushConfigDto query, String messageNo) {
        MessageDto dto = new MessageDto();
        dto.setModule(query.getModule());
        dto.setType(query.getType());
        dto.setMessageNo(messageNo);
        dto.setContent(query.getContent());
        //转发至其他节点
        redisTemplate.convertAndSend(Constants.TOPIC_MODULE, JSON.toJSONString(dto));
    }

    private void doPushToUser(MessagePushConfigDto query, String messageNo) {
        for (String identityNo : query.getIdentityList()) {
            MessageDto dto = new MessageDto();
            dto.setModule(query.getModule());
            dto.setType(query.getType());
            dto.setMessageNo(messageNo);
            dto.setContent(query.getContent());
            dto.setIdentityNo(identityNo);

            String key = MessageFormat.format(Constants.USER_KEY, query.getModule(),identityNo);
            String nodeIp = redisTemplate.opsForValue().get(key);
            if (StrUtil.isBlank(nodeIp)){
                log.info("no user found: {}-{}",identityNo, key);
                return;
            }
            if (NodeConfig.node.equals(nodeIp)){
                log.info("send from local: {}", identityNo);
                messageService.sendToUser(dto.getMessageNo(),dto.getModule(),dto.getIdentityNo(),dto.getContent());
            }else {
                //转发至其他节点
                redisTemplate.convertAndSend(Constants.TOPIC_USER, JSON.toJSONString(dto));
            }
        }
    }
}

实体

//发送的消息
@Data
public class MessageDto {

    private String module;
    /**
     * 1、指定用户
     * 2、全部
     */
    private Integer type;
    private String messageNo;
    private String content;
    private String identityNo;

}

//消息配置
@Data
public class MessagePushConfigDto {

    private String module;
    /**
     * 1、指定用户
     * 2、全部
     */
    private Integer type;
    private String content;
    private List<String> identityList;

}

//常量
public interface Constants {

    int MSG_TYPE_ALL = 1;
    int MSG_TYPE_SINGLE = 0;

    String TOPIC_MODULE = "topic:module";
    String TOPIC_USER = "topic:module:user";


    String USER_KEY = "socket:module:{0}:userId:{1}";
}
MessageService 发送消息接口
public interface MessageService {

    /**
     * 发送组
     * @param messageNo
     * @param module
     * @param content
     */
    void sendToGroup(String messageNo, String module, String content);

    /**
     * 单用户发送
     * @param messageNo
     * @param module
     * @param identityNo
     * @param content
     */
    void sendToUser(String messageNo, String module, String identityNo, String content);
}

public class MessageServiceImpl implements MessageService {

    private SessionRegistry sessionRegistry;

    public MessageServiceImpl(SessionRegistry sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }

    @Override
    public void sendToGroup(String messageNo, String module, String content) {
        SessionGroup sessionGroup = sessionRegistry.retrieveGroup(module);
        if (!Objects.isNull(sessionGroup)){
            sessionGroup.sendGroup(content);
        }
    }

    @Override
    public void sendToUser(String messageNo, String module, String identityNo, String content) {
        WssSession wssSession = sessionRegistry.retrieveSession(module, identityNo);
        if (!Objects.isNull(wssSession)){
            wssSession.send(content);
        }
    }
}
SessionService

操作 session 服务,并设置 用户到redis

public interface SessionService<WS extends WssSession<C>,C> {

    /**
     * 添加session
     * @param session
     */
    void addSession(WS session);

    /**
     * 删除session
     * @param session
     */
    void removeSession(WS session);

}
public abstract class AbstractSessionService<SR extends SessionRegistry<WS, C>, WS extends WssSession<C>, C>
        implements SessionService<WS, C> {

    @Getter
    private SR sessionRegistry;

    public AbstractSessionService(SR sessionRegistry) {
        this.sessionRegistry = sessionRegistry;
    }
}

public class SessionServiceImpl<SR extends SessionRegistry<WS, C>, WS extends WssSession<C>, C>
        extends AbstractSessionService<SR, WS, C> {

    private StringRedisTemplate redisTemplate;

    public SessionServiceImpl(SR sessionRegistry, StringRedisTemplate redisTemplate) {
        super(sessionRegistry);
        this.redisTemplate = redisTemplate;
    }

    @Override
    public void addSession(WS session) {
        getSessionRegistry().addSession(session);
        String key = MessageFormat.format(Constants.USER_KEY, session.getModule(), session.getIdentityNo());
        redisTemplate.opsForValue().set(key, NodeConfig.node);
    }

    @Override
    public void removeSession(WS session) {
        getSessionRegistry().removeSession(session);
        String key = MessageFormat.format(Constants.USER_KEY, session.getModule(), session.getIdentityNo());
        redisTemplate.delete(key);
    }

}

websocket 实现

定义session接口相关

public interface WssSession<C> {

    /**
     * 模块
     * @return
     */
    String getModule();

    /**
     * 用户唯一标识
     * @return
     */
    String getIdentityNo();

    /**
     * 通信渠道
     * @return
     */
    C getChannel();

    /**
     * 发送消息
     * @param message
     */
    void send(String message);

}

public interface SessionGroup <T extends WssSession<C>, C>{

    /**
     * add session
     * @param session
     */
    void addSession(T session);

    /**
     * remove session
     * @param session
     */
    void removeSession(T session);

    /**
     * 发送组数据
     * @param message
     */
    void sendGroup(String message);

    /**
     * 根据唯一标识查询session
     * @param identityNo
     * @return
     */

    T getSession(String identityNo);

}
public interface SessionRegistry<T extends WssSession<C>, C> {

    /**
     * 添加 session
     *
     * @param session
     */
    void addSession(T session);

    /**
     * 移除 session
     *
     * @param session
     */
    void removeSession(T session);

    /**
     * 查询 SessionGroup
     * @param module
     * @return
     */
    SessionGroup<T, C> retrieveGroup(String module);

    /**
     * 查询 session
     * @param module
     * @param identityNo
     * @return
     */
    T retrieveSession(String module, String identityNo);

}

public abstract class AbstractSession<C> implements WssSession<C>{

    private String module;
    private String identityNo;
    private C channel;


    public AbstractSession(String module, String identityNo, C channel) {
        this.module = module;
        this.identityNo = identityNo;
        this.channel = channel;
    }

    @Override
    public String getModule() {
        return module;
    }

    @Override
    public String getIdentityNo() {
        return identityNo;
    }

    @Override
    public C getChannel() {
        return channel;
    }
}

public abstract class AbstractSessionRegistry<T extends WssSession<C>, C> implements SessionRegistry<T, C> {

    private Map<String, SessionGroup<T, C>> map = new ConcurrentHashMap<>();

    @Override
    public void addSession(T session) {
        SessionGroup<T, C> sessionGroup = map.computeIfAbsent(session.getModule(), key -> newSessionGroup());
        sessionGroup.addSession(session);
    }

    protected abstract SessionGroup<T, C> newSessionGroup();

    @Override
    public void removeSession(T session) {
        SessionGroup<T, C> sessionGroup = map.get(session.getModule());
        sessionGroup.removeSession(session);
    }

    @Override
    public SessionGroup<T, C> retrieveGroup(String module) {
        return map.get(module);
    }

    @Override
    public T retrieveSession(String module, String identityNo) {
        SessionGroup<T, C> sessionGroup = map.get(module);
        if (sessionGroup != null) {
            return (T) sessionGroup.getSession(identityNo);
        }
        return null;
    }
}

使用 netty 容器

@Slf4j
@Component
public class NettyServer {

    private NioEventLoopGroup boss;
    private NioEventLoopGroup worker;


    @Value("${namespace:/ns}")
    private String namespace;

    @Autowired
    private SessionService sessionService;

    @PostConstruct
    public void start() {
        try {
            boss = new NioEventLoopGroup(1);
            worker = new NioEventLoopGroup();

            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new IdleStateHandler(0, 0, 60));
                    pipeline.addLast(new HeartBeatInboundHandler());
                    pipeline.addLast(new HttpServerCodec());
                    pipeline.addLast(new HttpObjectAggregator(64 * 1024));
                    pipeline.addLast(new ChunkedWriteHandler());
                    pipeline.addLast(new HttpRequestInboundHandler(namespace));
                    pipeline.addLast(new WebSocketServerProtocolHandler(namespace, true));
                    pipeline.addLast(new WebSocketHandShakeHandler(sessionService));
                }
            });
            int port = 9999;
            serverBootstrap.bind(port).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    log.info("server start at port successfully: {}", port);
                } else {
                    log.info("server start at port error: {}", port);
                }
            }).sync();
        } catch (InterruptedException e) {
            log.error("start error", e);
            close();
        }
    }

    @PreDestroy
    public void destroy() {
        close();
    }

    private void close() {
        log.info("websocket server close..");
        if (boss != null) {
            boss.shutdownGracefully();
        }
        if (worker != null) {
            worker.shutdownGracefully();
        }
    }


}

public class NettySessionGroup implements SessionGroup<NWssSession,Channel> {

    private ChannelGroup group = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    //Map<identityNo,channel>
    private Map<String, NWssSession> map = new ConcurrentHashMap<>();
    @Override
    public void addSession(NWssSession session) {
        group.add(session.getChannel());
        map.put(session.getIdentityNo(), session);
    }

    @Override
    public void removeSession(NWssSession session) {
        group.remove(session.getChannel());
        map.remove(session.getIdentityNo());
    }

    @Override
    public void sendGroup(String message){
        group.writeAndFlush(new TextWebSocketFrame(message));
    }

    @Override
    public NWssSession getSession(String identityNo) {
        return map.get(identityNo);
    }


}


public class NettySessionRegistry extends AbstractSessionRegistry<NWssSession, Channel> {

    @Override
    protected SessionGroup<NWssSession, Channel> newSessionGroup() {
        return new NettySessionGroup();
    }
}

public class NWssSession extends AbstractSession<Channel> {

    public NWssSession(String module, String identityNo, Channel channel) {
        super(module, identityNo, channel);
    }

    @Override
    public void send(String message) {
        getChannel().writeAndFlush(new TextWebSocketFrame(message));
    }
}

public class NettyUtil {

    //参数-module<->user-code
    public static AttributeKey<String> G_U = AttributeKey.valueOf("GU");
    //参数-uri
    public static AttributeKey<String> P = AttributeKey.valueOf("P");

    /**
     * 设置上下文参数
     *
     * @param channel
     * @param attributeKey
     * @param data
     * @param <T>
     */
    public static <T> void setAttr(Channel channel, AttributeKey<T> attributeKey, T data) {
        Attribute<T> attr = channel.attr(attributeKey);
        if (attr != null) {
            attr.set(data);
        }
    }

    /**
     * 获取上下文参数
     *
     * @param channel
     * @param attributeKey
     * @param <T>
     * @return
     */
    public static <T> T getAttr(Channel channel, AttributeKey<T> attributeKey) {
        return channel.attr(attributeKey).get();
    }

    /**
     * 根据 渠道获取 session
     *
     * @param channel
     * @return
     */
    public static NWssSession getSession(Channel channel) {
        String attr = channel.attr(G_U).get();
        if (StrUtil.isNotBlank(attr)) {
            String[] split = attr.split(",");
            String groupId = split[0];
            String username = split[1];
            return new NWssSession(groupId, username, channel);
        }
        return null;
    }


    public static void writeForbiddenRepose(ChannelHandlerContext ctx) {
        String res = "FORBIDDEN";
        FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.FORBIDDEN, Unpooled.wrappedBuffer(res.getBytes(StandardCharsets.UTF_8)));
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
        ctx.writeAndFlush(response);
        ctx.close();
    }


}

public interface WebSocketListener {

    void handShakeSuccessful(ChannelHandlerContext ctx, String uri);

    void handShakeFailed(ChannelHandlerContext ctx,String uri);
}

//解析 request uri参数
@Slf4j
public class DefaultWebSocketListener implements WebSocketListener {
    private static final String G = "module";
    private static final String U = "userCode";

    @Override
    public void handShakeSuccessful(ChannelHandlerContext ctx, String uri) {
        QueryStringDecoder decoderQuery = new QueryStringDecoder(uri);
        Map<String, List<String>> params = decoderQuery.parameters();
        String groupId = getParameter(G, params);
        String userCode = getParameter(U, params);
        if (StrUtil.isBlank(groupId) || StrUtil.isBlank(userCode)) {
            log.info("module or userCode is null: {}", uri);
            NettyUtil.writeForbiddenRepose(ctx);
            return;
        }
        //传递参数
        NettyUtil.setAttr(ctx.channel(), NettyUtil.G_U, groupId.concat(",").concat(userCode));
    }

    @Override
    public void handShakeFailed(ChannelHandlerContext ctx, String uri) {
        log.info("handShakeFailed failed,close channel");
        ctx.close();
    }

    private String getParameter(String key, Map<String, List<String>> params) {
        if (CollectionUtils.isEmpty(params)) {
            return null;
        }
        List<String> value = params.get(key);
        if (CollectionUtils.isEmpty(value)) {
            return null;
        }
        return value.get(0);
    }

}

netty handler
//心跳
@Slf4j
public class HeartBeatInboundHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent ise){
            if (ise.state()== IdleState.ALL_IDLE){
                //关闭连接
                log.info("HeartBeatInboundHandler heart beat close");
                ctx.channel().close();
                return;
            }
        }
        super.userEventTriggered(ctx,evt);
    }


}

/**
 * @Date: 2024/7/17 13:06
 * 处理 http 协议 的请求参数并传递
 */
@Slf4j
public class HttpRequestInboundHandler extends ChannelInboundHandlerAdapter {
    private String namespace;

    public HttpRequestInboundHandler(String namespace) {
        this.namespace = namespace;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        if (msg instanceof FullHttpRequest request) {
            //ws://localhost:8080/n/ws?groupId=xx&username=tom
            String requestUri = request.uri();
            String decode = URLDecoder.decode(requestUri, StandardCharsets.UTF_8);
            log.info("raw request url: {}", decode);
            URI uri = new URI(requestUri);
            if (!uri.getPath().startsWith(namespace)) {
                NettyUtil.writeForbiddenRepose(ctx);
                return;
            }
            // TODO: 2024/7/17 校验token
            // 比如从 header中获取token

            // 构建自定义WebSocket握手处理器, 也可以使用 netty自带 WebSocketServerProtocolHandler
            //shakeHandsIfNecessary(ctx, request, requestUri);
            //去掉参数 ===>  ws://localhost:8080/n/ws
            //传递参数
            NettyUtil.setAttr(ctx.channel(), NettyUtil.P, requestUri);
            request.setUri(namespace);
            ctx.pipeline().remove(this);
            ctx.fireChannelRead(request);
        }
    }
/*

    private void shakeHandsIfNecessary(ChannelHandlerContext ctx, FullHttpRequest request, String requestUri) {
        WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
                getWebSocketLocation(request), null, true);
        WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) {
            // 如果不支持WebSocket版本,返回HTTP 405错误
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
        } else {
            ChannelPipeline pipeline = ctx.channel().pipeline();
            handshaker.handshake(ctx.channel(), request).addListener((ChannelFutureListener) future -> {
                if (future.isSuccess()) {
                    //握手成功 WebSocketListener listener
                    listener.handShakeSuccessful(ctx, requestUri);
                } else {
                    //握手失败
                    listener.handShakeFailed(ctx, requestUri);
                }

            });
        }
    }

    private String getWebSocketLocation(FullHttpRequest req) {
        return "ws://" + req.headers().get(HttpHeaderNames.HOST) + prefix;
    }

*/
}

@Slf4j
public class WebSocketBizHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    private SessionService sessionService;

    public WebSocketBizHandler(SessionService sessionService){
        this.sessionService = sessionService;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded");
        NWssSession session = NettyUtil.getSession(ctx.channel());
        if (session == null) {
            log.info("session is null: {}", ctx.channel().id());
            NettyUtil.writeForbiddenRepose(ctx);
            return;
        }
        sessionService.addSession(session);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerRemoved");
        NWssSession session = NettyUtil.getSession(ctx.channel());
        if (session == null) {
            log.info("session is null: {}", ctx.channel().id());
            return;
        }
        sessionService.removeSession(session);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception {
        if (msg instanceof TextWebSocketFrame) {

        } else if (msg instanceof BinaryWebSocketFrame) {

        } else if (msg instanceof PingWebSocketFrame) {

        } else if (msg instanceof PongWebSocketFrame) {

        } else if (msg instanceof CloseWebSocketFrame) {
            if (ctx.channel().isActive()) {
                ctx.close();
            }
        }
        ctx.writeAndFlush(new TextWebSocketFrame("默认回复"));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        //处理最后的业务异常
        log.info("WebSocketBizHandler error: ", cause);
    }
}

//处理websocket协议握手
@Slf4j
public class WebSocketHandShakeHandler extends ChannelInboundHandlerAdapter {

    private SessionService sessionService;
    private WebSocketListener webSocketListener = new DefaultWebSocketListener();

    public WebSocketHandShakeHandler(SessionService sessionService) {
        this.sessionService = sessionService;
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete) {
            log.info("WebSocketHandShakeHandler shake-hands success");
            // 在此处获取URL、Headers等信息并做校验,通过throw异常来中断链接。
            String uri = NettyUtil.getAttr(ctx.channel(), NettyUtil.P);
            if (StrUtil.isBlank(uri)) {
                log.info("request uri is null");
                NettyUtil.writeForbiddenRepose(ctx);
                return;
            }
            webSocketListener.handShakeSuccessful(ctx, uri);
            ChannelPipeline pipeline = ctx.channel().pipeline();
            pipeline.addLast(new WebSocketBizHandler(sessionService));
            pipeline.remove(this);
            return;
        }
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        if (cause instanceof WebSocketHandshakeException) {
            //只处理 websocket 握手相关异常
            FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.BAD_REQUEST,
                    Unpooled.wrappedBuffer(cause.getMessage().getBytes()));
            ctx.channel().writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
            return;
        }
        super.exceptionCaught(ctx,cause);
    }

}


配置

@Component
public class NodeConfig {

    public static String node;

    @PostConstruct
    public void init() {
        String localhostStr = NetUtil.getLocalhostStr();
        NodeConfig.node = localhostStr;
        Assert.notNull(NodeConfig.node, "local ip is null");
    }
}

@Slf4j
@Configuration
public class RedisPublishConfig {

    @Bean
    public RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, MessageListener messageListener) {
        RedisMessageListenerContainer container = new RedisMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        List<PatternTopic> topicList = new ArrayList<>();
        topicList.add(new PatternTopic(Constants.TOPIC_USER));
        topicList.add(new PatternTopic(Constants.TOPIC_MODULE));
        container.addMessageListener(messageListener, topicList);
        log.info("RedisMessageListenerContainer listen topic: {}", Constants.TOPIC_USER);
        return container;
    }

}

@Slf4j
@Component
public class RedisPublisherListener implements MessageListener {

    @Autowired
    private RedisPublisherConsumer messageService;

    @Override
    public void onMessage(Message message, byte[] pattern) {
        try {
            String topic = new String(pattern);
            String msg = new String(message.getBody(), "utf-8");
            log.info("recv topic:{}, msg: {}", topic, msg);
            messageService.consume(topic, msg);
        } catch (UnsupportedEncodingException e) {
            log.error("recv msg error: {}", new String(pattern), e);
        }
    }
}

@Configuration
public class WebSocketConfig {


    @Bean
    public NettySessionRegistry sessionRegistry() {
        return new NettySessionRegistry();
    }

    @Bean
    public SessionService<NWssSession, Channel> sessionService(StringRedisTemplate redisTemplate) {
        return new SessionServiceImpl<>(sessionRegistry(), redisTemplate);
    }

    @Bean
    public MessageService messageService() {
        return new MessageServiceImpl(sessionRegistry());
    }

}

e, byte[] pattern) {
try {
String topic = new String(pattern);
String msg = new String(message.getBody(), “utf-8”);
log.info(“recv topic:{}, msg: {}”, topic, msg);
messageService.consume(topic, msg);
} catch (UnsupportedEncodingException e) {
log.error(“recv msg error: {}”, new String(pattern), e);
}
}
}

@Configuration
public class WebSocketConfig {

@Bean
public NettySessionRegistry sessionRegistry() {
    return new NettySessionRegistry();
}

@Bean
public SessionService<NWssSession, Channel> sessionService(StringRedisTemplate redisTemplate) {
    return new SessionServiceImpl<>(sessionRegistry(), redisTemplate);
}

@Bean
public MessageService messageService() {
    return new MessageServiceImpl(sessionRegistry());
}

}


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

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

相关文章

华为Ascend C算子开发(中级)考试

华为Ascend C算子开发&#xff08;中级&#xff09;考试题 提示&#xff1a;这个是河北廊坊Ascend C算子开发考试题和答案&#xff0c;仅供参考&#xff0c;因为不确定其他城市的考试题是否也是一样 文章目录 华为Ascend C算子开发&#xff08;中级&#xff09;考试题一、op_ho…

音视频入门基础:H.264专题(16)——FFmpeg源码中,判断某文件是否为H.264裸流文件的实现

一、引言 通过FFmpeg命令可以判断出某个文件是否为AnnexB格式的H.264裸流&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为AnnexB格式的H.264裸流呢&#xff1f;它内部其实是通过h264_probe函数来判断的。从文章《FFmpeg源码&#xff1a;av_probe_input_format3函数分析》中…

VSCode STM32嵌入式开发插件记录

要卸载之前搭建的VSCode嵌入式开发环境了&#xff0c;记录一下用的插件。 1.Cortex-Debug https://github.com/Marus/cortex-debug 2.Embedded IDE https://github.com/github0null/eide 3.Keil uVision Assistant https://github.com/jacksonjim/keil-assistant/ 4.RTO…

python-爬虫实例(4):获取b站的章若楠的视频

目录 前言 道路千万条&#xff0c;安全第一条 爬虫不谨慎&#xff0c;亲人两行泪 获取b站的章若楠的视频 一、话不多说&#xff0c;先上代码 二、爬虫四步走 1.UA伪装 2.获取url 3.发送请求 4.获取响应数据进行解析并保存 总结 前言 道路千万条&#xff0c;安全第一条 爬…

剧本杀小程序搭建,互联网下的游戏新体验,实现新增收!

近几年&#xff0c;桌游备受大众青睐&#xff0c;剧本杀行业更是瞬间曝火&#xff01;拥有强大社交体验与沉浸式游戏体验的剧本杀成为了众多年轻人的新宠&#xff0c;无论是外出游玩还是好友聚会&#xff0c;剧本杀游戏都成为了首选方式。 随着互联网的发展&#xff0c;线上小…

【防火墙】防火墙NAT、智能选路综合实验

实验拓扑 实验要求 7&#xff0c;办公区设备可以通过电信链路和移动链路上网(多对多的NAT&#xff0c;并且需要保留一个公网IP不能用来转换) 8&#xff0c;分公司设备可以通过总公司的移动链路和电信链路访问到Dmz区的http服务器 9&#xff0c;多出口环境基于带宽比例进行选路…

IO多路复用-select的使用详解【C语言】

1.多进程/线程并发和IO多路复用的对比 IO多路转接也称为IO多路复用&#xff0c;它是一种网络通信的手段&#xff08;机制&#xff09;&#xff0c;通过这种方式可以同时监测多个文件描述符并且这个过程是阻塞的&#xff0c;一旦检测到有文件描述符就绪&#xff08; 可以读数据…

【Java】中的List集合

目录 一、什么是List集合二、List的常用方法List的初始化元素操作1.添加元素2.删除元素3.修改元素4.查询元素 三、List集合的遍历1.for循环遍历2.增强for循环3.迭代器遍历 一、什么是List集合 List集合是最常用的一种数据结构之一。它具有动态扩容、元素添加、删除和查询等基础…

MySQL--索引(2)

InnoDB 1.索引类型 主键索引(Primary Key) 数据表的主键列使用的就是主键索引。 一张数据表有只能有一个主键&#xff0c;并且主键不能为 null&#xff0c;不能重复。 在 mysql 的 InnoDB 的表中&#xff0c;当没有显示的指定表的主键时&#xff0c;InnoDB 会自动先检查表中是…

IC秋招RTL代码合集

一 全加器和半加器 全加器 module full_adder1(input Ai, Bi, Ci,output So, Co);assign So Ai ^ Bi ^ Ci ;assign Co (Ai & Bi) | (Ci & (Ai | Bi)); endmodule module full_adder1(input Ai, Bi, Cioutput So, Co);assign {Co, So} Ai Bi Ci ; endm…

Wi-SUN无线通信技术 — 大规模分散式物联网应用首选

引言 在数字化浪潮的推动下&#xff0c;物联网&#xff08;IoT&#xff09;正逐渐渗透到我们生活的方方面面。Wi-SUN技术以其卓越的性能和广泛的应用前景&#xff0c;成为了大规模分散式物联网应用的首选。本文将深入探讨Wi-SUN技术的市场现状、核心优势、实际应用中的案例以及…

AndroidStudio 编辑xml布局文件卡死问题解决

之前项目编写的都是正常&#xff0c;升级AndroidStudio后编辑布局文件就卡死&#xff0c;还以为是AndroidStudio文件。 其实不然&#xff0c;我给整个项目增加了版权声明。所以全部跟新后&#xff0c;布局文件也增加了版权声明。估计AndroidStudio在 解析布局文件时候因为有版…

信号【Linux】

文章目录 信号处理方式&#xff08;信号递达&#xff09;前后台进程 终端按键产生信号kill系统调用接口向进程发信号阻塞信号sigset_tsigprocmasksigpending内核态与用户态&#xff1a;内核空间与用户空间内核如何实现信号的捕捉 1、信号就算没有产生&#xff0c;进程也必须识别…

Django—admin后台管理

Django官网 https://www.djangoproject.com/ 如果已经有了Django跳过这步 安装Django&#xff1a; 如果你还没有安装Django&#xff0c;可以通过Python的包管理器pip来安装&#xff1a; pip install django 创建项目&#xff1a; 使用Django创建一个新的项目&#xff1a; …

敲详细的springboot中使用RabbitMQ的源码解析

这里介绍的源码主要是涉及springboot框架下的rabbitmq客户端代码&#xff08;具体在springframework.amqp.rabbit包下&#xff0c;区分一下不由springboot直接接管的spring-rabbit的内容&#xff09;&#xff0c;springboot基于RabbitMQ的Java客户端建立了简便易用的框架。 sp…

jmeter实战(1)- Mac环境安装

一、安装 JDK 这个就不介绍了&#xff0c;本地自行安装 JDK 并且配置好环境变量 二、安装 Jmeter 1. 下载地址 —> 下载链接点击这里 2. 选择合适的版本下载 3. 解压到本地目录 解压后&#xff0c;会得到下面的目录文件&#xff1a; 输入cd bin&#xff0c;进入到bin…

OpenCV 直方图概念,直方图均衡化原理详解

文章目录 直方图相关概念颜色灰度级作用应用场景 C 使用OpenCV绘制直方图单通道直方图关键代码分析&#xff1a;calcHist函数分析使用OpenCV API来绘制直方图 效果图&#xff1a; 彩色三通道直方图效果图&#xff1a; 直方图均衡化概念均衡化作用均衡化效果均衡化数学原理步骤数…

项目实战二 HIS项目

目标&#xff1a; 项目的操作流程&#xff1a; 开发体系 前端开发&#xff1a;负责页面的编写 HTML CSS JavaScript 后端开发&#xff1a;看不到 摸不着的功能 常用开发语言 PHP JAVA Python 框架 &#xff1a; 半成品 做好的功能模块 版本控制 Git 分布式版本控…

vxe-table——实现切换页码时排序状态的回显问题(ant-design+elementUi中table排序不同时回显的bug)——js技能提升

之前写的后台管理系统&#xff0c;都是用的antdelement&#xff0c;table组件中的【排序】问题是有一定的缺陷的。 想要实现的效果&#xff1a; antv——table组件一次只支持一个参数的排序 如下图&#xff1a; 就算是可以自行将排序字段拼接到列表接口的入参中&#xff0c…

【中项】系统集成项目管理工程师-第4章 信息系统架构-4.3应用架构

前言&#xff1a;系统集成项目管理工程师专业&#xff0c;现分享一些教材知识点。觉得文章还不错的喜欢点赞收藏的同时帮忙点点关注。 软考同样是国家人社部和工信部组织的国家级考试&#xff0c;全称为“全国计算机与软件专业技术资格&#xff08;水平&#xff09;考试”&…