websocket (@ServerEndpoint)基本使用指南

概述

websocket 介绍

  • WebSocket 是一种通信协议,通过单个 TCP 连接提供全双工通信通道。它允许客户端和服务器之间进行双向通信、实时交互,比如在线聊天、实时数据展示等。

    与传统的 HTTP 协议不同,WebSocket 连接是持久的,可以在服务器和客户端之间发送实时数据。

  • WebSocket的 一些关键特点

    • 全双工通信: WebSocket 支持全双工通信,允许服务器和客户端同时发送和接收数据,而无需等待对方的响应。
    • 持久连接: 一旦建立 WebSocket 连接,它可以保持打开状态,允许实时数据的即时传输,而无需为每个通信步骤重新建立连接。
    • 低延迟: WebSocket 连接通常比传统的 HTTP 请求-响应模型更具有低延迟。这使得它非常适合实时应用程序,如在线游戏、聊天应用和股票市场数据更新。
    • 简单握手过程: WebSocket 连接的建立通过简单的握手过程进行,使用 HTTP 协议进行初始握手后,连接就升级为WebSocket 连接。
    • 协议标准: WebSocket 定义了一个标准的通信协议,包括数据帧格式、握手过程和关闭连接的规范。
    • 适用于各种应用: WebSocket 广泛用于各种实时应用程序,包括在线聊天、协作工具、实时通知、在线游戏和金融应用等。
  • 基本工作原理:

    • 握手过程: 客户端发起一个 HTTP 请求,表明希望升级为 WebSocket 连接。服务器接受请求后,进行握手过程,确认协议升级。
    • 数据帧传输: 一旦握手成功,双方可以通过发送数据帧进行通信。数据帧可以包含文本、二进制数据等。
    • 保持连接: WebSocket 连接是持久的,保持打开状态,直到一方发起关闭握手。
    • 关闭连接: 任何一方都可以发起关闭握手,双方会交换关闭帧,最终关闭连接。

Java 实现 websocket 服务端的方式

主要有两种:

  • Tomcat 7 的 @ServerEndpoint 注解方式
  • springboot 集成 websocket 方式

@ServerEndpoint 注解方式

@ServerEndpoint 注解

介绍

  • @ServerEndpoint 注解是 tomcat 7 中新增加的一个注解,用于标识一个类为 WebSocket 服务端点

    WebSocket 服务端点监听客户端的 WebSocket 连接,并将连接管理起来,供客户端和服务端进行实时通信

  • 一个标注有 @ServerEndpoint 的类必须包含一个无参构造函数,并且可以有一个或多个注解为 @OnOpen、@OnClose、@OnMessage、@OnError 的方法。

    • @OnOpen:当 WebSocket 连接建立时,会调用标注有 @onOpen 的方法

    • @OnClose:当 WebSocket 连接关闭时,会调用标注有 @OnClose 的方法

    • @OnMessage:当收到客户端发送的消息时,会调用标注有 @OnMessage 的方法

      在 @OnMessage 方法中,可以通过参数文本、二进制、PongMessage 等方式来接收客户端发送的消息。

      同样可以通过 Session 给客户端发送消息,以实现双向通信。

    • @OnError:当出现错误时,会调用标注有 @OnError 的方法

  • 注意:标注 @ServerEndpoint 注解的类对象是多例的,即每个连接都会创建一个新的对象


@ServerEndpoint 注解的参数配置

  • value 参数:必选参数,用于指定 WebSocket 服务端点的 URI 地址

  • **decoders **参数:数组类型,指定解码器

    包含用于将 WebSocket 消息解码为 Java 对象的解码器(Decoder)的类

    解码器帮助将原始消息转换为 Java 对象

  • encoders 参数:数组类型,指定编解码器

    包含用于将 Java 对象编码为 WebSocket 消息的编码器(Encoder)的类。

    编码器帮助将 Java 对象转换为可以发送到客户端的消息

  • subprotocols 参数:用于指定一个或多个 WebSocket 的子协议

  • configurator 参数:一个类,用于提供配置 WebSocket 端点的自定义配置器

    这允许在 WebSocket 端点创建和配置过程中进行额外的设置


WebSocket 端点类常用对象

在一个使用@ServerEndpoint注解定义的 WebSocket 端点类中,可以自动注入以下类型的对象:

  • javax.websocket.Session:WebSocket 的一个会话对象,代表了 WebSocket 的一个客户端和服务器的连接。

    可以使用 Session 对象来获取 WebSocket 中的各种状态和信息,比如获得客户端的地址、判断客户端是否已经关闭等。

  • javax.websocket.EndpointConfig: 用于在 WebSocket 端点的生命周期内共享配置信息的对象

  • 其他 Spring Bean: 如果 WebSocket 端点类是一个 Spring 管理的 Bean,可以通过使用 @Autowired 注解或构造函数注入其他 Spring Bean,以便在 WebSocket 端点中使用它们。

  • Servlet API 对象: 如果 WebSocket 应用程序与 Servlet 容器集成,可以注入 Servlet API 的相关对象,例如HttpServletRequestHttpServletResponse 等,以便在 WebSocket 处理中访问Web请求和响应

    @Autowired
    private HttpServletRequest request;@OnOpen
    public void onOpen(Session session) {// 使用注入的HttpServletRequest对象
    }
    
  • 其他常用对象

    • RemoteEndpoint :WebSocket 的一个远程端点对象,代表了客户端和服务器的一个连接通道。

      通过 RemoteEndpoint 对象,可以向客户端发送消息、关闭连接等。

      RemoteEndpoint 对象获取方法:通过 Session 对象的 basicRemote 属性获取

      RemoteEndpoint.Basic remote = session.getBasicRemote();
      

使用注意事项

  • 多线程:WebSocket 是一种基于事件驱动的编程模型,因此需要注意多线程问题(线程安全性和同步)
  • 长连接:WebSocket 是一种长连接的通信模型,需要特别注意长时间连接的问题(资源占用和关闭连接)
  • 消息大小:WebSocket 的消息大小是有限制的,需要特别注意消息大小的问题(消息的大小和格式)

基本使用

依赖

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

核心处理器(@ServerEndpoint)

import com.blackcrow.common.utils.UUIDUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** websocket 服务端点。注意:websocket对象是多例的*/
@Slf4j
@Service
//@ServerEndpoint(value = "/chat")
@ServerEndpoint(value = "/chat", configurator = WebSocketServerConfigurator.class)
public class WebSocketServer {// 用于存储每个用户客户端对象public static Map<String, WebsocketServer> onlineUserMap = new ConcurrentHashMap<>();// 用户idprivate String userId;// 会话private Session session;@OnOpenpublic void onOpen(Session session, EndpointConfig config){this.session = session;this.userId = UUIDUtil.get4UUID();log.info("收到来自窗口的连接,userId={}", this.userId);onlineUserMap.put(this.userId, this);Object aaaa = config.getUserProperties().get("aaaa");log.info("aaaa={}", aaaa);}@OnMessagepublic void onMessage(String message, Session session){log.info("收到来自窗口[{}]的的信息: {}", this.userId, message);}@OnClosepublic void onClose(Session session){onlineUserMap.remove(this.userId);log.info("有一连接[{}]关闭!当前连接数为 {}", this.userId, onlineUserMap.size());}@OnErrorpublic void onError(Session session, Throwable throwable){log.error("WebsocketServer 连接发生错误", throwable);}/*** 給session连接推送消息*/private void sendMessage(Object message) {try {this.session.getBasicRemote().sendObject(message);} catch (Exception e) {e.printStackTrace();log.error("向客户端推送数据发生错误", e);}}/*** 向所有连接群发消息*/public static void sendMessageToAll(Object message){for (WebsocketServer item : onlineUserMap.values()) {try {item.sendMessage(message);} catch (Exception e) {log.error("向客户端推送数据发生错误", e);}}}
}

配置类(ServerEndpointExporter )

注:

  • 如果是在 SpringBoot 环境,则必须注册 ServerEndpointExporter 对象,用于扫描 @ServerEndpoint 注解的配置,不然在客户端连接的时候会一直连不上。若不是在 SpringBoot 下开发的可以跳过这一环节。
  • ServerEndpointExporter 类是 spring-boot-starter-websocket 提供的
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {/*** ServerEndpointExporter 将会扫描所有使用 @ServerEndpoint 注解标记的类,并将它们注册为 WebSocket 服务端点*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

握手连接处理类

  • 这个类是一个自定义的 WebSocket 配置器,它实现了 javax.websocket.server.ServerEndpointConfig.Configurator 接口。它的作用是在 WebSocket 握手期间修改握手请求和响应。

    具体来说,这个类重写了 modifyHandshake 方法,在 WebSocket 握手期间被调用。

    modifyHandshake 方法允许在这个握手阶段进行干预,以便:

    • 修改请求头:可以添加、修改或删除 HTTP 请求头中的信息。这对于传递身份验证信息、自定义参数或修改其他请求属性非常有用。
    • 定制子协议:WebSocket 支持客户端和服务器协商使用哪个子协议。通过 modifyHandshake,可以根据客户端的请求或其他条件来选择要使用的子协议。
    • 处理跨域问题:在某些情况下,可能需要处理跨域 WebSocket 连接。modifyHandshake 可以配置跨域相关的设置,如允许的来源、凭据模式等。
    • 添加自定义逻辑:可以使用 modifyHandshake 来添加任何自定义逻辑,这些逻辑在握手阶段需要执行。
    • 拒绝连接:可以在 modifyHandshake 方法中添加逻辑来检查客户端的请求,并据此决定是否继续握手过程。如果决定拒绝连接,可以抛出一个异常来中断握手。
  • 注:在 WebSocket 的生命周期中,握手是客户端和服务器建立连接的第一步。这一步通常涉及 HTTP 请求和响应,以便双方能够就将要建立的 WebSocket 连接达成协议。

import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.List;
import java.util.Map;/*** 握手处理器*/
public class WebSocketServerConfigurator extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {// 获取客户端发送的HTTP请求头信息Map<String, List<String>> headers = request.getHeaders();// 检查某个特定的请求头是否存在或是否符合要求List<String> customHeaderValues = headers.get("Custom-Header");if (customHeaderValues == null || customHeaderValues.isEmpty() || !customHeaderValues.get(0).equals("ExpectedValue")) {// 如果请求头不符合要求,则拒绝握手throw new RuntimeException("Custom header is missing or invalid");}// 如果请求头符合要求,则继续握手过程super.modifyHandshake(sec, request, response);}
}

springboot 集成 websocket 方式

WebSocket 端点类常用对象

WebSocketMessage

public interface WebSocketMessage<T> {/*** 消息载荷*/T getPayload();/*** 消息字节长度*/int getPayloadLength();/*** 当org.springframework.web.socket.WebSocketHandler#supportsPartialMessages()配置允许分片消息时,* 如果当前消息是客户端本次送达消息的最后一部分时,该方法返回true。如果分片消息不可用或是被禁用,放回false*/boolean isLast();
}

WebSocketSession

常用方法:

  • 主动推送消息:void sendMessage(WebSocketMessage<?> message);
  • 关闭连接:void close();
public interface WebSocketSession extends Closeable {/*** 会话标识*/String getId();/*** WebSocket 连接的URI*/@NullableURI getUri();/*** 返回握手请求中使用的Headers*/HttpHeaders getHandshakeHeaders();/***返回WebSocke会话关联的属性。*在服务端,可以使用org.springframework.web.socket.server.HandshakeInterceptor填充属性*在客户端,可以使用org.springframework.web.socket.client.WebSocketClient的握手方法填充属性*/Map<String, Object> getAttributes();/*** 返回一个包含已验证的用户名称的java.security.Principal实例,如果用户没有验证成功返回null*/@NullablePrincipal getPrincipal();/*** 返回请求接收方的地址*/@NullableInetSocketAddress getLocalAddress();/*** 返回客户端的地址*/@NullableInetSocketAddress getRemoteAddress();/***返回约定的子协议,如果没有协议或是协议失败返回null*/@NullableString getAcceptedProtocol();/*** 配置一次接收文本消息最大值*/void setTextMessageSizeLimit(int messageSizeLimit);/*** 获取一次接收文本消息最大值*/int getTextMessageSizeLimit();/*** 配置一次接收二进制消息最大值*/void setBinaryMessageSizeLimit(int messageSizeLimit);/*** 获取一次接收二进制消息最大值*/int getBinaryMessageSizeLimit();/*** 获取约定的扩展*/List<WebSocketExtension> getExtensions();/*** 发送消息,WebSocket会话底层协议不支持并发发送消息,因此发送必须是同步的。* 保证信息发送同步进行,一种方法是使用org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator* 包装WebSocketSession*/void sendMessage(WebSocketMessage<?> message) throws IOException;/*** 底层连接是否打开*/boolean isOpen();/*** 使用状态码1000关闭WebSocket连接*/@Overridevoid close() throws IOException;/*** 使用指定状态码WebSocket连接*/void close(CloseStatus status) throws IOException;
}

基本使用

依赖

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

核心处理器(WebSocketHandler)

  • 可以实现 WebSocketHandler 接口,也可以继承 AbstractWebSocketHandler 类来创建 WebSocketHandler 实例

  • WebSocketHandler 接口提供了五个方法:

    • afterConnectionEstablished:连接成功后调用

    • handleMessage:处理发送来的消息

      handleMessage 方法中有一个 WebSocketMessage 参数,是一个接口,但一般不直接使用这个接口而是使用它的实现类,它有以下几个实现类:

      • BinaryMessage:二进制消息体
      • TextMessage:文本消息体
      • PingMessage: Ping 消息体
      • PongMessage: Pong 消息体

      但是由于 handleMessage 方法的参数是WebSocketMessage 接口,所以实际使用中可能需要判断一下当前来的消息具体是它的哪个子类,比如这样:

      public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {if (message instanceof TextMessage) {this.handleTextMessage(session, (TextMessage)message);} else if (message instanceof BinaryMessage) {this.handleBinaryMessage(session, (BinaryMessage)message);}
      }
      

      可以直接继承 AbstractWebSocketHandler 类,然后重写想要处理的消息类型,它已经封装了这些重复劳动,

    • handleTransportError: WS 连接出错时调用

    • afterConnectionClosed:连接关闭后调用

    • supportsPartialMessages:是否支持分片消息。没什么用,返回 false 就完事了

import org.springframework.web.socket.*;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** websocket核心处理器*/
public class MyWebSocketHandler implements WebSocketHandler {private static final Map<String, WebSocketSession> SESSIONS = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {String userName = session.getAttributes().get("userName").toString();SESSIONS.put(userName, session);System.out.println(String.format("成功建立连接~ userName: %s", userName));}@Overridepublic void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {String msg = message.getPayload().toString();System.out.println(msg);}@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("连接出错");if (session.isOpen()) {session.close();}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {System.out.println("连接已关闭,status:" + closeStatus);}@Overridepublic boolean supportsPartialMessages() {return false;}/*** 指定发消息*/public static void sendMessage(String userName, String message) {WebSocketSession webSocketSession = SESSIONS.get(userName);if (webSocketSession == null || !webSocketSession.isOpen()) return;try {webSocketSession.sendMessage(new TextMessage(message));} catch (IOException e) {e.printStackTrace();}}/*** 群发消息*/public static void fanoutMessage(String message) {SESSIONS.keySet().forEach(us -> sendMessage(us, message));}
}

核心配置类(WebSocketConfigurer)

  • 可以配置 websocket 入口,允许访问的域、注册 Handler、定义拦截器等

  • 注意 WebSocketHandlerRegistry .addHandler(WebSocketHandler webSocketHandler, String… paths) 的第二个参数是一个字符串类型的参数列表,说明可以为多个端点指定同样配置的 WebSocketHandler 处理

  • WebSocketHandlerRegistry.addHandler() 方法注册 WebSocketHandler 之后会返回 WebSocketHandlerRegistration 用于配置 WebSocketHandler

    public interface WebSocketHandlerRegistration {// 继续添加消息处理器WebSocketHandlerRegistration addHandler(WebSocketHandler handler, String... paths);// 添加握手处理器,处理握手事件WebSocketHandlerRegistration setHandshakeHandler(HandshakeHandler handshakeHandler);// 添加握手拦截器,可以在处理握手前和握手后处理一些业务逻辑WebSocketHandlerRegistration addInterceptors(HandshakeInterceptor... interceptors);// 配置允许的浏览器跨源请求类型WebSocketHandlerRegistration setAllowedOrigins(String... origins);// 配置允许的浏览器跨源请求类型WebSocketHandlerRegistration setAllowedOriginPatterns(String... originPatterns);// 允许使用SockJS应急选项SockJsServiceRegistration withSockJS();
    }
import org.springframework.context.annotation.Configuration;
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.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;/*** websocket 核心配置类*/
@Configuration
@EnableWebSocket  // 开启注解接收和发送消息
public class WebSocketConfig implements WebSocketConfigurer {/*** 配置 websocket 入口,允许访问的域、注册 Handler、定义拦截器等* 注;配置注册的处理器和拦截器是单例的,无论多少连接进来,都是用相同的对象处理。*/@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new MyWebSocketHandler(), "/ws")    // 设置连接路径和处理.setAllowedOrigins("*").addInterceptors(new MyWebSocketInterceptor());     // 设置拦截器}/*** 自定义拦截器拦截WebSocket请求*/class MyWebSocketInterceptor implements HandshakeInterceptor {/*** 握手前置拦截。一般用来注册用户信息,绑定 WebSocketSession*/@Overridepublic boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {System.out.println("握手前置拦截~~");if (!(request instanceof ServletServerHttpRequest)) return true;
//            HttpServletRequest servletRequest = ((ServletServerHttpRequest) request).getServletRequest();
//            String userName = (String) servletRequest.getSession().getAttribute("userName");String userName = "Koishipyb";attributes.put("userName", userName);Object userName1 = attributes.get("userName");return true;}/*** 握手后置拦截*/@Overridepublic void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,WebSocketHandler wsHandler, Exception exception) {System.out.println("握手后置拦截~~");}}
}

nginx 配置 WebSocket

server{# 监听的端口号listen      9095;server_name robotchat.lukeewin.top; # 这里填写的是访问的域名location / {proxy_pass http://127.0.0.1:9090; # 这里填写的是代理的路径和端口proxy_set_header Host $host;proxy_set_header X-Real_IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;}# 以下配置针对websocketlocation /ws { # onlineCount为websocket的访问uriproxy_redirect off;proxy_pass http://127.0.0.1:9090;proxy_set_header Host $host;proxy_set_header X-Real_IP $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_http_version 1.1;proxy_read_timeout 36000s;proxy_send_timeout 36000s;proxy_set_header Upgrade $http_upgrade;   # 升级协议头 websocketproxy_set_header Connection "upgrade";}
}

注:

  • 添加如下三行语句,才能在后台中拿到真实的 ip 地址

    proxy_set_header Host $host;
    proxy_set_header X-Real_IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    

    获取真实 ip 地址代码如下:

    public String getRealIp(HttpServletRequest request) {String ip = request.getHeader("X-Forwarded-For");if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {int index = ip.indexOf(",");if (index != -1) {return ip.substring(0, index);} else {return ip;}}ip = request.getHeader("X-Real-IP");if (StringUtils.isNotEmpty(ip) && !"unKnown".equalsIgnoreCase(ip)) {return ip;}return request.getRemoteAddr();
    }
    

参考

  • 传统@ServerEndpoint方式开发WebSocket应用和SpringBoot构建WebSocket应用程序
  • 一文搞懂四种 WebSocket 使用方式
  • Websocket学习(@EnableWebSocket&@ServerEndpoint&websocket认证&nginx配置WebSocket&gateway配置websocket)

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础STM32单片机编程入门(五)FreeRTOS实时操作系统详解及实战含源码视频

文章目录 一.概要二.什么是实时操作系统三.FreeRTOS的特性四.FreeRTOS的任务详解1.任务函数定义2.任务的创建3.任务的调度原理 五.CubeMX配置一个FreeRTOS例程1.硬件准备2.创建工程3.调试FreeRTOS任务调度 六.CubeMX工程源代码下载七.讲解视频链接地址八.小结 一.概要 FreeRTO…

新版一键AI视频图片换脸神器来了!目前最强的AI视频换脸工具Swapface!

之前发过一款AI换脸工具&#xff0c;可惜部署门槛太高&#xff0c; 有没有换头换脸的AI工具&#xff1f; 今天就给你们安排到家&#xff01; Swapface AI工具一键开箱包‍&#xff08;一键整合包添加下方领取~&#xff09; 它使用先进的人工智能和计算机视觉技术,可以在几秒…

秋招突击——6/24——复习{完全背包问题——买书,状态转换机——股票买卖V}——新作{两数相除,LRU缓存实现}

文章目录 引言复习完全背包问题——买书个人实现 状态转换机——股票买卖V个人实现参考实现 新作两数相除个人实现 新作LRU缓存实现个人实现unordered_map相关priority_queue相关 参考实现自己复现 总结 引言 今天知道拼多多挂掉了&#xff0c;难受&#xff0c;那实习就是颗粒无…

体验升级:扫描全能王智能高清滤镜2.0全面测评

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

【接口自动化测试】第四节.实现项目核心业务的单接口自动化测试

文章目录 前言一、登录单接口自动化测试 1.1 登录单接口文档信息 1.2 登录成功 1.3 登录失败&#xff08;用户名为空&#xff09;二、数据驱动的实现 2.1 json文件实现数据驱动总结 前言 一、登录单接口自动化测试 1.1 登录单接口文档信息 需求&#xff1…

LeetCode 子集

原题链接78. 子集 - 力扣&#xff08;LeetCode&#xff09; 这是一道暴力搜索问题参考大佬们的题解&#xff0c;对这类题目做出一下总结 1.确定递归参数变量 2.递归结束条件 3.做出选择&#xff0c;递归调用进入下一层 4.回溯&#xff0c;返回到递归前的状态 要完成前面这…

【Matlab函数分析】imread从图形文件读取图像

&#x1f517; 运行环境&#xff1a;Matlab &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

Qt6.6编译Qt二维图形编辑器QVGE源码

QVGE是一个开源的多平台QtC编写的图形编辑器&#xff0c;可以用来画网络节点图&#xff0c;或者其他作用。 QVGE可以轻松创建和参数设定的小型到中型图形(1000节点/边缘)&#xff0c;共同的视觉特性的节点和边缘&#xff1a;形状、尺寸、颜色、标签等。定义(用户定义)属性的图表…

前端技术(二)——javasctipt 介绍

一、javascript基础 1. javascript简介 ⑴ javascript的起源 ⑵ javascript 简史 ⑶ javascript发展的时间线 ⑷ javascript的实现 ⑸ js第一个代码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>…

探究Qt5【元对象编译器,moc】的 设计原理和技术细节

Qt5是一个跨平台C框架&#xff0c;它有个突出的特点就是其元对象系统&#xff0c;该系统通过扩展C的能力&#xff0c;为事件处理提供了信号与槽机制、为对象内省提供了属性系统。为了支持这些特性&#xff0c;Qt引入了元对象编译器&#xff08;Meta-Object Compiler, MOC&#…

C++视觉开发 一.OpenCV环境配置

一.OpenCV安装环境配置 1.OpenCV安装 &#xff08;1&#xff09;下载 官方下载链接&#xff1a;http://opencv.org/releases 这边选择需要的版本&#xff0c;我是在windows下的4.9.0。&#xff08;科学上网下载很快&#xff0c;否则可能会有点慢&#xff09; (2)安装 双击下…

使用systemd管理Linux下的frps服务:安装、配置及自动化操作指南

在 Linux 系统下&#xff0c;使用 systemd 可以方便地控制 frps 服务端的启动、停止、配置后台运行以及开机自启动。以下是具体的操作步骤&#xff1a; 1. 安装 systemd 如果您的 Linux 服务器上尚未安装 systemd&#xff0c;可以使用包管理器如 yum&#xff08;适用于 Cent…

基于RabbitMQ的异步消息传递:发送与消费

引言 RabbitMQ是一个流行的开源消息代理&#xff0c;用于在分布式系统中实现异步消息传递。它基于Erlang语言编写&#xff0c;具有高可用性和可伸缩性。在本文中&#xff0c;我们将探讨如何在Python中使用RabbitMQ进行消息发送和消费。 安装RabbitMQ 在 Ubuntu 上安装 Rabbi…

GaussDB关键技术原理:高性能(三)

GaussDB关键技术原理&#xff1a;高性能&#xff08;二&#xff09;从查询处理综述对GaussDB的高性能技术进行了解读&#xff0c;本篇将从查询重写RBO、物理优化CBO、分布式优化器、布式执行框架、轻量全局事务管理GTM-lite等五方面对高性能关键技术进行分享。 目录 3 高性能…

PyTorch之nn.Module、nn.Sequential、nn.ModuleList使用详解

文章目录 1. nn.Module1.1 基本使用1.2 常用函数1.2.1 核心函数1.2.2 查看函数1.2.3 设置函数1.2.4 注册函数1.2.5 转换函数1.2.6 加载函数 2. nn.Sequential()2.1 基本定义2.2 Sequential类不同的实现2.3 nn.Sequential()的本质作用 3. nn.ModuleList参考资料 本篇文章主要介绍…

AI绘画-Stable Diffusion 原理介绍及使用

引言 好像很多朋友对AI绘图有兴趣&#xff0c;AI绘画背后&#xff0c;依旧是大模型的训练。但绘图类AI对计算机显卡有较高要求。建议先了解基本原理及如何使用&#xff0c;在看看如何实现自己垂直行业的绘图AI逻辑。或者作为使用者&#xff0c;调用已有的server接口。 首先需…

Open3D (C++) 点云旋转至主成分空间

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 首先使用主成分分析法计算出点云的特征值与特征向量,然后根据点云的特征向量计算出点云与主成分空间之间的…

CAM350怎么添加文字?

CAM350怎么添加文字&#xff1f; CAM350只能修改用CAM350本身做的文字&#xff0c;其它软件生成的GERBER文件导入到CAM350会默认为线条&#xff0c;没办法修改。 如果想添加文字&#xff0c;先把原先的文字删除。然后在CAM350里面重新添加文字就可以了。 操作方法如下&#xf…

Java代码生成器(开源版本)

一、在线地址 Java在线代码生成器&#xff1a;在线访问 二、页面截图 三、核心功能 支持Mybatis、MybatisPlus、Jpa代码生成使用 antlr4 解析SQL语句&#xff0c;保证了SQL解析的成功率支持自定义包名、作者名信息支持自定义方法名、接口地址支持自定义选择是否生成某个方法…

力扣 单链表元素删除解析及高频面试题

目录 删除元素的万能方法 构造虚拟头结点来应对删除链表头结点的情况 一、203.移除链表元素 题目 题解 二、19.删除链表中倒数第K个节点 题目 题解 三、 83.删除某个升序链表中的重复元素&#xff0c;使重复的元素都只出现一次 题目 题解 82.删除某个升序链表中的…