springBoot使用webSocket的几种方式以及在高并发出现的问题及解决

一、第一种方式-原生注解(tomcat内嵌)

1.1、引入依赖

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

1.2、配置文件

package cn.jt.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月06日
 */
@Configuration
public class WebSocketConfig {

    /**
     * 初始化Bean,它会自动注册使用了 @ServerEndpoint 注解声明的 WebSocket endpoint
     *
     * @return
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

1.3、构建安全的WebSocket抽象层

1、该类可以作为一个基础的安全抽象层,后续项目中如果需要做认证的操作,都可以继承该抽象类

ClientUserInfoService 大家可以看作一个 UserService 就是一张用户表的service类

这里认证采用的是 jwt的方式,大家可以换成自己的

2、大家这里注意,我们使用的是 javax.websocket.Session; 这个是tomcat下的
在这里插入图片描述

package cn.jt.websocket;

import cn.jt.client.entity.ClientUserInfo;
import cn.jt.client.service.ClientUserInfoService;
import cn.jt.jwt.JwtUtils;
import cn.jt.utils.SpringContextUtils;
import lombok.extern.slf4j.Slf4j;

import javax.websocket.Session;
import java.io.IOException;
import java.util.Date;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月06日
 */
@Slf4j
public abstract class SecureWebSocket {
    private static final ClientUserInfoService clientUserInfoService;

    static {
        clientUserInfoService = SpringContextUtils.getBean(ClientUserInfoService.class);
    }

    protected Session session;

    protected String token;

    protected Long tokenExpiresAt;

    protected ClientUserInfo clientUserInfo;

    /**
     * 验证token是否有效(包含有效期)
     *
     * @param token  token
     * @param isInit 是否对token和userInfo进行初始化赋值
     * @return boolean
     */
    protected boolean isTokenValid(String token, boolean isInit) {
        ClientUserInfo clientUserInfo;
        try {
            clientUserInfo = JwtUtils.getClientUserInfo(token);
        } catch (Exception e) {
            log.error("ws 认证失败", e);
            return false;
        }
        if (isInit) {
            this.clientUserInfo = clientUserInfo;
            this.tokenExpiresAt = JwtUtils.getDecodedJWT(token).getExpiresAt().getTime();
            this.token = token;
        }
        return true;
    }

    /**
     * 认证失败,断开连接
     *
     * @param session session
     */
    protected void sendAuthFailed(Session session) {
        try {
            session.getBasicRemote().sendText("认证失败");
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

1.4、构建基础的WebSocket

1、代码很简单,大家一看就知道逻辑了,这里就解释一下各个注解的含义

  • @ServerEndpoint:将目前的类定义成一个websocket服务器端,注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
  • @OnOpen:当WebSocket建立连接成功后会触发这个注解修饰的方法。
  • @OnClose:当WebSocket建立的连接断开后会触发这个注解修饰的方法。
  • @OnMessage:当客户端发送消息到服务端时,会触发这个注解修改的方法。
  • @OnError:当WebSocket建立连接时出现异常会触发这个注解修饰的方法。

2、大家这里注意,我们使用的是 javax.websocket.Session; 这个是tomcat下的
在这里插入图片描述

package cn.jt.websocket;

import com.alibaba.fastjson.JSON;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.*;

/**
 * @author GXM
 * @version 1.0.0
 * @Description 
 * @createTime 2023年07月06日
 */
@Slf4j
@ServerEndpoint("/globalWs/{token}")
@Component
public class GlobalWebsocket extends SecureWebSocket {

 
    /**
     * key: userKye
     * value: GlobalWebsocket  这里你直接存储 session 也是可以的
     */
    private static final Map<String, GlobalWebsocket> CLIENTS = new ConcurrentHashMap<>();

    /**
     * // 如果允许 一个账号 多人登录的话  就 加上  "-" + tokenTime,因为每次登录的token过期时间都是不一样的
     * clientUserInfo.getId() + "-" + clientUserInfo.getAccount() ;
     */
    private String userKye;

    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        if (!isTokenValid(token, true)) {
            sendAuthFailed(session);
            return;
        }

        this.session = session;
        this.userKye = clientUserInfo.getId() + "-" + clientUserInfo.getAccount() + "-" + super.tokenExpiresAt;
        CLIENTS.put(userKye, this);
        log.info("当前在线用户:{}", CLIENTS.keySet());

        try {
            session.getBasicRemote().sendText("连接成功!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnMessage
    public String onMessage(Session session, String message) {
        // 先判断当前token 是否已经到期了
        if (!isTokenValid(token, false)) {
            sendAuthFailed(session);
            return null;
        }

        try {
            session.getBasicRemote().sendText("received");
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
//        log.error("ws session 发生错误,session key is {}",throwable);
        log.error("ws session 发生错误:{}", throwable.getMessage());
    }

    @OnClose
    public void onClose(Session session) {
        CLIENTS.remove(userKye);
        log.info("ws 用户 userKey {} 已下线,当前在线用户:{}", userKye, CLIENTS.keySet());
    }

    /**
     * 发送消息
     *
     * @param messageVo
     */
    public void sendMessage(MessageVo messageVo) {
        try {
            this.session.getBasicRemote().sendText(JSON.toJSONString(messageVo));
        } catch (IOException e) {
            log.error("发送消息异常", e);
        }
    }

    /**
     * 向user精确用户发送消息
     *
     * @param userKey   由 account + "-" + refreshToken的签发时间组成,例:"admin-1635830649000"
     * @param messageVo 消息内容
     */
    public static void sendToUser(String userKey, MessageVo messageVo) {

        GlobalWebsocket globalWebsocket = CLIENTS.get(userKey);
        if (null != globalWebsocket) {
            globalWebsocket.sendMessage(messageVo);
            return;
        }
        log.error("发送消息到指定用户,但是用户不存在,userKey is {},message is {}", userKey, JSON.toJSONString(messageVo));
    }

    /**
     * 全体组播消息
     *
     * @param
     */
    public static void broadcast(MessageVo messageVo) {
        CLIENTS.values().forEach(c -> {
                    Session curSession = c.session;
                    if (curSession.isOpen()) {
                            try {
                                curSession.getBasicRemote().sendText(JSON.toJSONString(messageVo));
                            } catch (IOException e) {
                                log.error("发送ws数据错误:{}", e.getMessage());
                            }
                    }
                }
        );
    }
}

1.5、SpringBoot 开启 WebSocket

@EnableWebSocket

在这里插入图片描述

1.6、高并发时候的问题

1、这里要说明一下在高并发下的问题,如果你同时向在线的 3 个webSocket 在线客户端发送消息,即广播所有在线用户(目前是3个),每个用户每秒10条,那就是说,你每秒要发送 30 条数据,我们调用上述的广播函数 broadcast(),有时候会出现

java.lang.IllegalStateException: 远程 endpoint 处于 [xxxxxx] 状态,如:
The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for calle

这是因为在高并发的情况下,出现了session抢占的问题,导致session,的状态不一致,所以,这里可以去尝试加锁操作,如下


 public static final ExecutorService WEBSOCKET_POOL_EXECUTOR = new ThreadPoolExecutor(
            20, 20,
            Integer.MAX_VALUE, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(),
            new ThreadFactoryBuilder()
                    .setNameFormat("GlobalWebsocket-executor-" + "%d")
                    .setUncaughtExceptionHandler((thread, throwable) -> log.error("ThreadPool {} got exception", thread, throwable)).build(),
            new ThreadPoolExecutor.AbortPolicy());
            
    /**
     * 全体组播消息
     *
     * @param
     */
    public static void broadcast(MessageVo messageVo) {
        CLIENTS.values().forEach(c -> {    
                    Session curSession = c.session;
                    if (curSession.isOpen()) {
                        // 建议单个session 一个线程,避免  一个session会话网络不好,会出现超时异常,当前线程会因此中断。
                        // 导致后面的session没有进行发送操作。使用单个线程,单个session情况下避免session之间的相互影响。
                        WEBSOCKET_POOL_EXECUTOR.execute(() -> {
                            synchronized (curSession) {
                                // 双重锁检查,外边的 isOpen 第一遍过滤,里面枷加锁之后,第二遍过滤
                                if (curSession.isOpen()) {
                                    try {
                                        curSession.getBasicRemote().sendText(JSON.toJSONString(messageVo));
                                    } catch (IOException e) {
                                        log.error("发送ws数据错误:{}", e.getMessage());
                                    }
                                }
                            }
                        });
                    }
                }
        );
    }

其中增加了,双重锁检查,以及线程池操作,当然加上锁之后,性能是肯定会有所下降的

建议单个session 一个线程,避免 一个session会话网络不好,会出现超时异常,当前线程会因此中断

2、按照上述的代码,我这边测试12个webSocket 链接,每秒每个客户端都发送10条数据,相当于每秒发送120条数据,目前看来,速度还是不错的,但是当客户端重连后,偶尔会出现错误信息 远程主机已经关闭了一个链接,类似于这种错误,这条错误日志是在广播代码的如下位置打印的,这是因为当准备发送消息的时候,远程客户端还是关闭了。

 try {
                                        curSession.getBasicRemote().sendText(JSON.toJSONString(messageVo));
                                    } catch (IOException e) {
                                        log.error("发送ws数据错误:{}", e.getMessage());
                                    }

二、第二种方式-Spring封装

2.1、引入依赖

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

2.2、自己的webSocket处理service

1、WebSocketService 处理器类如下

类似于 UserService 等等,主要是抽出一部分的业务逻辑

package cn.jt.websocket.spring;

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月19日
 */
public interface WebSocketService {
    /**
     * 会话开始回调
     *
     * @param session 会话
     */
    void handleOpen(WebSocketSession session);

    /**
     * 会话结束回调
     *
     * @param session 会话
     */
    void handleClose(WebSocketSession session);

    /**
     * 处理消息
     *
     * @param session 会话
     * @param message 接收的消息
     */
    void handleMessage(WebSocketSession session, String message);

    /**
     * 发送消息
     *
     * @param session 当前会话
     * @param message 要发送的消息
     * @throws IOException 发送io异常
     */
    void sendMessage(WebSocketSession session, String message) throws IOException;

    /**
     * 发送消息
     *
     * @param userId  用户id
     * @param message 要发送的消息
     * @throws IOException 发送io异常
     */
    void sendMessage(Integer userId, TextMessage message) throws IOException;

    /**
     * 发送消息
     *
     * @param userId  用户id
     * @param message 要发送的消息
     * @throws IOException 发送io异常
     */
    void sendMessage(Integer userId, String message) throws IOException;

    /**
     * 发送消息
     *
     * @param session 当前会话
     * @param message 要发送的消息
     * @throws IOException 发送io异常
     */
    void sendMessage(WebSocketSession session, TextMessage message) throws IOException;

    /**
     * 广播
     *
     * @param message 字符串消息
     * @throws IOException 异常
     */
    void broadCast(String message) throws IOException;

    /**
     * 广播
     *
     * @param message 文本消息
     * @throws IOException 异常
     */
    void broadCast(TextMessage message) throws IOException;

    /**
     * 处理会话异常
     *
     * @param session 会话
     * @param error   异常
     */
    void handleError(WebSocketSession session, Throwable error);
}

2、WebSocketServiceImpl 实现类如下

类似于 UserServiceImpl 等等,主要是抽出一部分的业务逻辑


package cn.jt.websocket.spring;

import cn.jt.client.entity.ClientUserInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;

import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月19日
 */
@Slf4j
public class WebSocketServiceImpl implements WebSocketService {

    private final Map<Integer, WebSocketSession> clients = new ConcurrentHashMap<>();

    @Override
    public void handleOpen(WebSocketSession session) {
        // 这个时候就需要在建立 webSocket 时存储的 用户信息了
        Map<String, Object> attributes = session.getAttributes();
        ClientUserInfo clientUserInfo = (ClientUserInfo) attributes.get("clientUserInfo");
        clients.put(clientUserInfo.getId(), session);

        log.info("a new connection opened,current online count:{}", clients.size());
    }

    @Override
    public void handleClose(WebSocketSession session) {
        // 这个时候就需要在建立 webSocket 时存储的 用户信息了
        Map<String, Object> attributes = session.getAttributes();
        ClientUserInfo clientUserInfo = (ClientUserInfo) attributes.get("clientUserInfo");
        clients.remove(clientUserInfo.getId());
        log.info("a new connection closed,current online count:{}", clients.size());
    }

    @Override
    public void handleMessage(WebSocketSession session, String message) {
        // 只处理前端传来的文本消息,并且直接丢弃了客户端传来的消息
        log.info("received a message:{}", message);
    }

    @Override
    public void sendMessage(WebSocketSession session, String message) throws IOException {
        this.sendMessage(session, new TextMessage(message));
    }

    @Override
    public void sendMessage(Integer userId, TextMessage message) throws IOException {
        WebSocketSession webSocketSession = clients.get(userId);

        if (webSocketSession.isOpen()) {
            webSocketSession.sendMessage(message);
        }
    }

    @Override
    public void sendMessage(Integer userId, String message) throws IOException {
        this.sendMessage(userId, new TextMessage(message));
    }

    @Override
    public void sendMessage(WebSocketSession session, TextMessage message) throws IOException {
        session.sendMessage(message);
    }

    @Override
    public void broadCast(String message) throws IOException {
        clients.values().forEach(session -> {
            if (session.isOpen()) {
                try {
                    session.sendMessage(new TextMessage(message));
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    @Override
    public void broadCast(TextMessage message) throws IOException {
        clients.values().forEach(session -> {
            if (session.isOpen()) {
                try {
                    session.sendMessage(message);
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        });
    }

    @Override
    public void handleError(WebSocketSession session, Throwable error) {
        log.error("websocket error:{},session id:{}", error.getMessage(), session.getId());
        log.error("", error);
    }

}

2.3、实现spring框架的WebSocket处理器

1、注意这里的 webSocketSession 就是 spring 包下的了,不再是 tomcat包下的了

在这里插入图片描述

这里其实就和我们之前使用原生注解(tomcat)的那个一样了,都是几个特定的函数

我们在特定的方法下,调用我们自己的 service去单独处理,解耦合

package cn.jt.websocket.spring;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.socket.*;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月19日
 */
public class DefaultWebSocketHandler implements WebSocketHandler {

    @Autowired
    private WebSocketService webSocketService;
    /**
     * 建立连接
     *
     * @param session Session
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        webSocketService.handleOpen(session);
    }

    /**
     * 接收消息
     *
     * @param session Session
     * @param message 消息
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
        if (message instanceof TextMessage) {
            TextMessage textMessage = (TextMessage) message;
            webSocketService.handleMessage(session, textMessage.getPayload());
        }
    }

    /**
     * 发生错误
     *
     * @param session   Session
     * @param exception 异常
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        webSocketService.handleError(session, exception);
    }

    /**
     * 关闭连接
     *
     * @param session     Session
     * @param closeStatus 关闭状态
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
        webSocketService.handleClose(session);
    }

    /**
     * 是否支持发送部分消息
     *
     * @return false
     */
    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

2.4、自定义拦截器

这里,我们可以设置拦截器,在做请求参数,或者权限认证的时候,不用在建立链接的函数afterConnectionEstablished里面去处理

可以理解为 springMvc 每次请求前的拦截器

package cn.jt.websocket.spring;

import cn.jt.client.entity.ClientUserInfo;
import cn.jt.jwt.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;

import java.util.Map;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月19日
 */
@Slf4j
public class WebSocketInterceptor implements HandshakeInterceptor {

    /**
     * 建立请求之前,可以用来做权限判断
     *
     * @param request    the current request
     * @param response   the current response
     * @param wsHandler  the target WebSocket handler
     * @param attributes the attributes from the HTTP handshake to associate with the WebSocket
     *                   session; the provided attributes are copied, the original map is not used.
     * @return
     * @throws Exception
     */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request,
                                   ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) throws Exception {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletServerHttpRequest = (ServletServerHttpRequest) request;
            // 模拟用户(通常利用JWT令牌解析用户信息)
            String token = servletServerHttpRequest.getServletRequest().getParameter("token");
            try {
                ClientUserInfo clientUserInfo = JwtUtils.getClientUserInfo(token);
                // 设置当前这个session的属性,后续我们在发送消息时,可以通过 session.getAttributes().get("clientUserInfo")可以取出 clientUserInfo参数
                attributes.put("clientUserInfo", clientUserInfo);
            } catch (Exception e) {
                log.error("webSocket 认证失败 ", e);
                return false;
            }
            return true;
        }
        return false;
    }

    /**
     * 建立请求之后
     *
     * @param request   the current request
     * @param response  the current response
     * @param wsHandler the target WebSocket handler
     * @param exception an exception raised during the handshake, or {@code null} if none
     */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {

    }
}


2.5、WebSocket配置

将自定义处理器、拦截器以及WebSocket操作类依次注入到IOC容器中。

  • @EnableWebSocket:开启WebSocket功能
  • addHandler:添加处理器
  • addInterceptors:添加拦截器
  • setAllowedOrigins:设置允许跨域(允许所有请求来源)
package cn.jt.websocket.spring;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

/**
 * @author GXM
 * @version 1.0.0
 * @Description TODO
 * @createTime 2023年07月19日
 */
@Configuration
public class WebSocketConfiguration implements WebSocketConfigurer {
    @Bean
    public DefaultWebSocketHandler defaultWebSocketHandler() {
        return new DefaultWebSocketHandler();
    }

    @Bean
    public WebSocketService webSocket() {
        return new WebSocketServiceImpl();
    }

    @Bean
    public WebSocketInterceptor webSocketInterceptor() {
        return new WebSocketInterceptor();
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 链接方式如下 ws://127.0.0.1:9085/globalWs/message?token=qwncjncwqqdnjncz.adqwascsvcgrgb.cbrtbfvb
        // 如果你设置了springboot的 contentPath 那就需要在:9085端口后面 加上contentPath 的值,在拼接上  globalWs/message?token=qwncjncwqqdnjncz.adqwascsvcgrgb.cbrtbfvb
        registry.addHandler(defaultWebSocketHandler(), "/globalWs/message")
                .addInterceptors(webSocketInterceptor())
                .setAllowedOrigins("*");
    }
}

2.6、SpringBoot 开启 WebSocket

@EnableWebSocket

在这里插入图片描述

2.7、链接

1、其中 thermal-api 是我的项目名称

在这里插入图片描述

2、链接路径如下

ws://127.0.0.1:9085/thermal-api/globalWs/message?token=qwncjncwqqdnjncz.adqwascsvcgrgb.cbrtbfvb

2.8、高并发时候的问题

1、如果在广播的时候,客户端很多,发送的消息也是很多,还是会出现和之前 第一种方式-原生注解(tomcat内嵌)相同的问题,出现类似如下报错

The remote endpoint was in state [xxxx] which is an invalid state for calle

2、错误分析可以看 踩坑笔记 Spring websocket并发发送消息异常,写的很清楚。

2.8.1、解决方案一

1、和之前一样,加锁

@Override
    public void broadCast(String message) throws IOException {
        clients.values().forEach(session -> {
            if (session.isOpen()) {
                synchronized (session){
                    try {
                        session.sendMessage(new TextMessage(message));
                    } catch (IOException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
    }

2.8.2、解决方案二

1、使用 spring 的,Spring 的解决方案是把原来的 WebSocketSession 封了一层,即 org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator

3、代码稍微改一下,如下

@Override
    public void handleOpen(WebSocketSession session) {
        // 这个时候就需要在建立 webSocket 时存储的 用户信息了
        Map<String, Object> attributes = session.getAttributes();
        ClientUserInfo clientUserInfo = (ClientUserInfo) attributes.get("clientUserInfo");
        
        clients.put(clientUserInfo.getId(), new ConcurrentWebSocketSessionDecorator(session, 10 * 1000, 64000));
        log.info("a new connection opened,current online count:{}", clients.size());
    }

第三种方式-TIO

1、请上网了解,用的比较少,不做过多说明

第四种方式-STOMP

1、请上网了解,用的比较少,不做过多说明

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

如何动态修改 spring aop 切面信息?让自动日志输出框架更好用

业务背景 很久以前开源了一款 auto-log 自动日志打印框架。 其中对于 spring 项目&#xff0c;默认实现了基于 aop 切面的日志输出。 但是发现一个问题&#xff0c;如果切面定义为全切范围过大&#xff0c;于是 v0.2 版本就是基于注解 AutoLog 实现的。 只有指定注解的类或…

数据结构之BinaryTree(二叉树)的实现

BinaryTree要实现的方法 总结 remove不在BinNode里&#xff0c;而是BinTree里 递归的两种写法 从上往下&#xff1a;同一对象的递归&#xff08;参数多一个&#xff0c;判空用一句话&#xff09;&#xff0c;子对象的递归&#xff08;参数void&#xff0c;判空用两句话&#…

对于awd

最近我们老师直接说要我准备awd&#xff0c;大概率要我上场我就顺便整理一下awd的资料&#xff08;准备写很多所以建议大家收藏一下&#xff09; 攻防指北 先来一个思维导图 Awd竞赛 AWD(Attack With Defense&#xff0c;攻防兼备)是一个非常有意思的模式&#xff0c;你需要…

Nginx配置整合:基本概念、命令、反向代理、负载均衡、动静分离、高可用

一、基本概念 1.什么是Nginx Nginx是一个高性能的HTTP和反向代理服务器&#xff0c;也是一个IMAP/POP3/SMTP代理server。其特点是占有内存少。并发能力强&#xff0c;其并发能力确实在同类型的网页server中表现较好。 http服务器 Web服务器是指驻留于因特网上某种类型计算机的程…

IT技术岗的面试技巧分享

我们在找工作时,需要结合自己的现状,针对意向企业做好充分准备。作为程序员,你有哪些面试IT技术岗的技巧?你可以从一下几个方向谈谈你的想法和观点。 方向一:分享你面试IT公司的小技巧 1、事先和邀约人了解公司的基本情况,比如公司的行业,规模,研发人员占比等 2、事先和…

cmder 使用简介

文章目录 1. cmder 简介2. 下载地址3. 安装4. 配置环境变量5. 添加 cmder 到右键菜单6. 解决中文乱码问题 1. cmder 简介 cmder 是一个增强型命令行工具&#xff0c;不仅可以使用 windows 下的所有命令&#xff0c;更爽的是可以使用 linux的命令, shell 命令。 2. 下载地址 …

【C#】MVC页面常见的重定向方式和场景

本篇文章主要简单讲讲&#xff0c;C# MVC 页面常见跳转或者重定向的方式和场景。 在实际项目开发中&#xff0c;在一些特定场景肯定会用到重定向&#xff0c;比如&#xff1a;不同角色跳转到不同视图地址 目录 一、种常见重定向方式1.1、RedirectToAction1.2、RedirectToRoute1…

【算法 -- LeetCode】(025) K 个一组翻转链表

1、题目 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点…

记录一次抓取WiFi驱动日志以及sniffer日志

起因 路由器桥接一个WiFi&#xff0c;然后设备连接这个路由器的WiFi&#xff0c;发现网络不可用&#xff0c;而手机或者电脑连接就没问题&#xff0c;与供应商沟通问题&#xff0c;需要抓取日志&#xff0c;记录一下 抓取WLAN DRIVER WLAN FW3日志 进入开发者模式打开启动WL…

MQTT 与 Kafka|物联网消息与流数据集成实践

MQTT 如何与 Kafka 一起使用&#xff1f; MQTT (Message Queuing Telemetry Transport) 是一种轻量级的消息传输协议&#xff0c;专为受限网络环境下的设备通信而设计。Apache Kafka 是一个分布式流处理平台&#xff0c;旨在处理大规模的实时数据流。 Kafka 和 MQTT 是实现物…

数据结构和算法——快速排序(算法概述、选主元、子集划分、小规模数据的处理、算法实现)

目录 算法概述 图示 伪代码 选主元 子集划分 小规模数据的处理 算法实现 算法概述 图示 快速排序和归并排序有一些相似&#xff0c;都是用到了分而治之的思想&#xff1a; 伪代码 通过初步的认识&#xff0c;我们能够知道快速排序算法最好的情况应该是&#xff1a; 每…

keil5编辑器主题配色美化使用(附六款暗黑主题)

一、通过配置文件修改主题 1、在软件安装目下备份以下三个文件&#xff0c;更换主题只需要替换global.prop arm.propglobal.propglobal.prop.def 2、替换配置文件 将已经准备好的配色文件复制到\UV4下替换 https://download.csdn.net/download/qq_43445867/88064961 Theme1…

【湍流介质的三维传播模拟器】全衍射3-D传播模拟器,用于在具有随机和背景结构的介质中传播无线电和光传播(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码实现 &#x1f4a5;1 概述 全衍射3-D传播模拟器是一种用于模拟在具有随机和背景结构的介质中传播无线电和光的工具。它可以帮助研究人员和工程师理解和预测无线电波和光波…

【数据可视化】基于Python和Echarts的中国经济发展与人口变化可视化大屏

1.题目要求 本次课程设计要求使用Python和ECharts实现数据可视化大屏。要求每个人的数据集不同&#xff0c;用ECharts制作Dashboard&#xff08;总共至少4图&#xff09;&#xff0c;要求输入查询项&#xff08;地点和时间&#xff09;可查询数据&#xff0c;查询的数据的地理…

IDEA+SpringBoot +ssm+ Mybatis+easyui+Mysql求职招聘管理系统网站

IDEASpringBoot ssm MybatiseasyuiMysql求职招聘管理系统网站 一、系统介绍1.环境配置 二、系统展示1. 登录2.注册3.首页4.公司5.关于我们6.我的简历7.我投递的简历8.修改密码9. 管理员登录10.我的信息11.用户信息12.职位类别13. 职位列表14. 公司列表15. 日志列表 三、部分代码…

【高阶数据结构】跳表

文章目录 一、什么是跳表二、跳表的效率如何保证&#xff1f;三、skiplist的实现四、skiplist跟平衡搜索树和哈希表的对比 一、什么是跳表 skiplist本质上也是一种查找结构&#xff0c;用于解决算法中的查找问题&#xff0c;跟平衡搜索树和哈希表的价值是 一样的&#xff0c;可…

2321. 拼接数组的最大分数;768. 最多能完成排序的块 II;2192. 有向无环图中一个节点的所有祖先

2321. 拼接数组的最大分数 核心思想&#xff1a;数学思维。假设nums1的和为a0a1a2a3...an-1 sum(nums1),nums2的和为b0b1b2b3...bn-1 sum(nums2),交换al...ar与bl..br&#xff0c;现在nums1的和要最大&#xff0c;则s sum(nums1) (br-ar)(br-1-ar-1)...(bl-al),所以你要使…

MATLAB遗传算法求解带容量约束的物流配送选址问题实例

MATLAB遗传算法求解带容量约束的物流配送选址问题实例 作者&#xff1a;麦哥爱西芹 MATLAB遗传算法求解带容量约束物流配送中心选址问题代码实例 遗传算法编程问题实例&#xff1a; 在经度范围为(116, 118)&#xff0c;纬度范围为(38, 40)的矩形区域内&#xff0c;散布着37个需…

物联网大数据传输安全难题与解决方案

随着物联网时代的到来&#xff0c;大数据传输变得更加频繁和庞大&#xff0c;同时也给传输安全带来了更高的风险和挑战。本文将探讨物联网时代的大数据传输安全问题&#xff0c;并介绍镭速传输如何有效地解决这些问题。 首先&#xff0c;物联网时代的大数据传输面临的一个主要问…

LeetCode[148]排序链表

难度&#xff1a;Medium 题目&#xff1a; 给你链表的头结点 head &#xff0c;请将其按 升序 排列并返回 排序后的链表 。 示例 1&#xff1a; 输入&#xff1a;head [4,2,1,3] 输出&#xff1a;[1,2,3,4]示例 2&#xff1a; 输入&#xff1a;head [-1,5,3,4,0] 输出&…