通用接口开放平台设计与实现——(31)API服务线程安全问题确认与修复

背景

在本系列的前面一篇博客评论中,有小伙伴指出,API服务存在线程安全问题:

https://blog.csdn.net/seawaving/article/details/122905199#comments_34477405

今天来确认下,线程是否安全?如不安全,如何修复?

回顾

先来回顾下先前的实现,可能存在线程安全的是自己实现的过滤器链,如下:

package tech.abc.platform.cip.api.framework;


import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * API服务过滤器链条
 *
 * @author wqliu
 * @date 2022-2-12
 **/
public class ApiFilterChain {

    /**
     * 请求
     */
    private ApiRequest request;

    /**
     * 响应
     */
    private ApiResponse response = new ApiResponse();


    /**
     * 过滤器集合
     */
    private final List<ApiFilter> filters;

    /**
     * 过滤器迭代器
     */
    private Iterator<ApiFilter> iterator;

    public ApiFilterChain() {
        filters = Collections.EMPTY_LIST;
    }

    public ApiFilterChain(ApiFilter... filters) {
        this.filters = Arrays.asList(filters);

    }


    /**
     * 获取请求
     *
     * @return {@link ApiRequest}
     */
    public ApiRequest getRequest() {
        return this.request;
    }

    /**
     * 获取响应
     *
     * @return {@link ApiResponse}
     */
    public ApiResponse getResponse() {
        return this.response;
    }


    /**
     * 执行过滤
     *
     * @param request  请求
     * @param response 响应
     */
    public void doFilter(ApiRequest request, ApiResponse response) {
        // 如迭代器为空,则初始化
        if (this.iterator == null) {
            this.iterator = this.filters.iterator();
        }

        // 集合中还有过滤器,则继续往下传递
        if (this.iterator.hasNext()) {
            ApiFilter nextFilter = this.iterator.next();
            nextFilter.doFilter(request, response, this);
        }

        // 将处理结果更新到属性中
        this.request = request;
        this.response = response;
    }


    /**
     * 重置
     */
    public void reset() {
        this.request = null;
        this.response = null;
        this.iterator = null;
    }

}

该类是参照官方的MockFilterChain实现的,源码如下:

/*
 * Copyright 2002-2018 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.mock.web;

import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * Mock implementation of the {@link javax.servlet.FilterChain} interface.
 *
 * <p>A {@link MockFilterChain} can be configured with one or more filters and a
 * Servlet to invoke. The first time the chain is called, it invokes all filters
 * and the Servlet, and saves the request and response. Subsequent invocations
 * raise an {@link IllegalStateException} unless {@link #reset()} is called.
 *
 * @author Juergen Hoeller
 * @author Rob Winch
 * @author Rossen Stoyanchev
 * @since 2.0.3
 * @see MockFilterConfig
 * @see PassThroughFilterChain
 */
public class MockFilterChain implements FilterChain {

    @Nullable
    private ServletRequest request;

    @Nullable
    private ServletResponse response;

    private final List<Filter> filters;

    @Nullable
    private Iterator<Filter> iterator;


    /**
     * Register a single do-nothing {@link Filter} implementation. The first
     * invocation saves the request and response. Subsequent invocations raise
     * an {@link IllegalStateException} unless {@link #reset()} is called.
     */
    public MockFilterChain() {
        this.filters = Collections.emptyList();
    }

    /**
     * Create a FilterChain with a Servlet.
     * @param servlet the Servlet to invoke
     * @since 3.2
     */
    public MockFilterChain(Servlet servlet) {
        this.filters = initFilterList(servlet);
    }

    /**
     * Create a {@code FilterChain} with Filter's and a Servlet.
     * @param servlet the {@link Servlet} to invoke in this {@link FilterChain}
     * @param filters the {@link Filter}'s to invoke in this {@link FilterChain}
     * @since 3.2
     */
    public MockFilterChain(Servlet servlet, Filter... filters) {
        Assert.notNull(filters, "filters cannot be null");
        Assert.noNullElements(filters, "filters cannot contain null values");
        this.filters = initFilterList(servlet, filters);
    }

    private static List<Filter> initFilterList(Servlet servlet, Filter... filters) {
        Filter[] allFilters = ObjectUtils.addObjectToArray(filters, new ServletFilterProxy(servlet));
        return Arrays.asList(allFilters);
    }


    /**
     * Return the request that {@link #doFilter} has been called with.
     */
    @Nullable
    public ServletRequest getRequest() {
        return this.request;
    }

    /**
     * Return the response that {@link #doFilter} has been called with.
     */
    @Nullable
    public ServletResponse getResponse() {
        return this.response;
    }

    /**
     * Invoke registered {@link Filter Filters} and/or {@link Servlet} also saving the
     * request and response.
     */
    @Override
    public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
        Assert.notNull(request, "Request must not be null");
        Assert.notNull(response, "Response must not be null");
        Assert.state(this.request == null, "This FilterChain has already been called!");

        if (this.iterator == null) {
            this.iterator = this.filters.iterator();
        }

        if (this.iterator.hasNext()) {
            Filter nextFilter = this.iterator.next();
            nextFilter.doFilter(request, response, this);
        }

        this.request = request;
        this.response = response;
    }

    /**
     * Reset the {@link MockFilterChain} allowing it to be invoked again.
     */
    public void reset() {
        this.request = null;
        this.response = null;
        this.iterator = null;
    }


    /**
     * A filter that simply delegates to a Servlet.
     */
    private static final class ServletFilterProxy implements Filter {

        private final Servlet delegateServlet;

        private ServletFilterProxy(Servlet servlet) {
            Assert.notNull(servlet, "servlet cannot be null");
            this.delegateServlet = servlet;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {

            this.delegateServlet.service(request, response);
        }

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }

        @Override
        public void destroy() {
        }

        @Override
        public String toString() {
            return this.delegateServlet.toString();
        }
    }

}

这个类用在了我们API服务中,如下:

package tech.abc.platform.cip.api.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;

import java.time.LocalDateTime;

/**
 * '
 * API服务技术框架实现
 *
 * @author wqliu
 * @date 2022-2-10
 **/
@Service
public class ApiRestServiceImpl implements ApiRestService {
    private ApiFilterChain filterChain;

    @Autowired
    private ApiServiceLogService apiServiceLogService;

    public ApiRestServiceImpl(FrameworkValidateFilter frameworkValidateFilter,
                              BusinessFilter businessFilter, BasicValidateFilter basicValidateFilter) {
        filterChain = new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);

    }

    @Override
    public ApiResponse handle(ApiRequest apiRequest) {

        LocalDateTime receiveTime = LocalDateTime.now();
        ApiResponse apiResponse = new ApiResponse();
        try {
            filterChain.doFilter(apiRequest, apiResponse);
            apiResponse = this.filterChain.getResponse();
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());
        } catch (CustomException ex) {
            // 自定义异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode("S00");
            apiResponse.setErrorMessage(ex.getMessage());
        } catch (ApiException ex) {
            // API异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode(ex.getErrorCode());
            apiResponse.setErrorMessage(ex.getMessage());
        } catch (Exception ex) {
            // 非预期异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode("S99");
            apiResponse.setErrorMessage("未定义异常:" + ex.getMessage());
        } finally {
            // 需要重置,为下次请求服务
            filterChain.reset();
            // 记录日志
            apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);

        }

        return apiResponse;
    }
}

分析

其中ApiRestServiceImpl使用的@Service注解,Spring的默认处理是单例模式,ApiFilterChain是在构造函数中new出来的。线程安全的关键点,在于ApiFilterChain持有和保存了ApiRequest和ApiResponse对象。

从代码层面分析了一下,ApiFilterChain确实没有持有ApiRequest和ApiResponse对象的必要,通过方法接收ApiRequest对象,然后处理过程中修改ApiResponse对象,都是引用,没必要再保存一份。

至于当时为什么这么写,大概是参照官方MockFilterChain写法,高度信任而没有深度思考是否需要这么做。

验证

上面是从代码层面分析,接下来就动手验证下,线程安全问题是否存在,借此也夯实下实际工作中很少用到的多线程并发及线程安全基础。

如何验证呢?

其实思路也挺简单,发起多次接口调用,通过日志输出对象的HashCode,看看HashCode是否是同一个就好了。

使用Postman来做接口测试,调用平台内置的查询待处理消息的服务接口platform.message.query,如下:

注:为方便测试,把签名验证处理临时注释掉了,因此入参sign属性随便写了个1111,以通过非空验证。

在服务接口处理中添加日志输出,这里用error而不是info目的是更容易找到,如下:

然后使用postman发起两次接口调用,查看日志,如下:

可以看到,无论是ApiRestService,还是其所属的filterChain,哈希码是完全相同的,已经足以说明就是同一个对象,因此存在线程安全问题。当接口同时收到多个请求时,也就是多线程并发时,持有的请求对象和响应对象会混乱掉,是个大问题。

修正

既然问题已经确认,那接下来就修正它。

在前面分析环节,实际已经分析出来,filterChain并不需要持有请求对象和响应对象,去除掉后,就从根本上解决了线程安全问题,调整如下:

package tech.abc.platform.cip.api.framework;


import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;

import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
 * API服务过滤器链条
 *
 * @author wqliu
 * @date 2022-2-12
 **/
public class ApiFilterChain {


    /**
     * 过滤器集合
     */
    private final List<ApiFilter> filters;

    /**
     * 过滤器迭代器
     */
    private Iterator<ApiFilter> iterator;

    public ApiFilterChain() {
        filters = Collections.EMPTY_LIST;
    }

    public ApiFilterChain(ApiFilter... filters) {
        this.filters = Arrays.asList(filters);

    }


    /**
     * 执行过滤
     *
     * @param request  请求
     * @param response 响应
     */
    public void doFilter(ApiRequest request, ApiResponse response) {
        // 如迭代器为空,则初始化
        if (this.iterator == null) {
            this.iterator = this.filters.iterator();
        }

        // 集合中还有过滤器,则继续往下传递
        if (this.iterator.hasNext()) {
            ApiFilter nextFilter = this.iterator.next();
            nextFilter.doFilter(request, response, this);
        }


    }


    /**
     * 重置
     */
    public void reset() {

        this.iterator = null;
    }

}

测试功能正常,如下:

新的疑问点

在调整ApiFilterChain的过程中,去除了存在线程安全的ApiRequest和ApiResponse对象,同时发现还持有一个过滤器集合对象

private final List filters,该对象在构造方法中初始化,在接口服务的finally里执行reset清理工作,不过reset方法是重置过滤器集合的迭代器,而不是清空过滤器集合本身。

假设是多线程并发情况下,A、B两个请求先后到达,A请求处理结束了,调用reset清空了过滤器的迭代器,而B请求还在只走完了3个过滤器中的2个,会不会有问题呢?

按照前面的验证方法,输出哈希码,确定是同一个对象。

要做并发测试,比较麻烦,得辅助Jmeter等工具来实现了

这个地方高度怀疑存在线程安全问题,比较彻底的解决办法,就是把API服务变更为非单例模式。

彻底改造

接口服务对应的控制器中直接new对象,不使用依赖注入,如下;

@RestController
@RequestMapping("/api")
@Slf4j
public class ApiRestController {


    @PostMapping("/rest")
    @AllowAll
    public ResponseEntity<ApiResponse> post(@RequestBody ApiRequest apiRequest) {
        ApiRestService apiRestService = new ApiRestServiceImpl();
        ApiResponse apiResponse = apiRestService.handle(apiRequest);
        return new ResponseEntity<ApiResponse>(apiResponse, HttpStatus.OK);
    }

}

ApiRestServiceImpl去除@Service注解,从而也不再是单例模式,调整构造方法,以及内部获取类的方式,如下:

package tech.abc.platform.cip.api.service.impl;

import lombok.extern.slf4j.Slf4j;
import tech.abc.platform.cip.api.exception.ApiException;
import tech.abc.platform.cip.api.framework.ApiFilterChain;
import tech.abc.platform.cip.api.framework.BasicValidateFilter;
import tech.abc.platform.cip.api.framework.BusinessFilter;
import tech.abc.platform.cip.api.framework.FrameworkValidateFilter;
import tech.abc.platform.cip.api.service.ApiRestService;
import tech.abc.platform.cip.common.entity.ApiRequest;
import tech.abc.platform.cip.common.entity.ApiResponse;
import tech.abc.platform.cip.enums.ApiServiceExecuteResultEnum;
import tech.abc.platform.cip.service.ApiServiceLogService;
import tech.abc.platform.common.exception.CustomException;
import tech.abc.platform.common.utils.SpringUtil;

import java.time.LocalDateTime;

/**
 * '
 * API服务技术框架实现
 *
 * @author wqliu
 * @date 2022-2-10
 **/

@Slf4j
public class ApiRestServiceImpl implements ApiRestService {
    private ApiFilterChain filterChain;


    public ApiRestServiceImpl() {
        BusinessFilter businessFilter = SpringUtil.getBean(BusinessFilter.class);
        FrameworkValidateFilter frameworkValidateFilter = SpringUtil.getBean(FrameworkValidateFilter.class);
        BasicValidateFilter basicValidateFilter = SpringUtil.getBean(BasicValidateFilter.class);
        filterChain = new ApiFilterChain(basicValidateFilter, frameworkValidateFilter, businessFilter);
    }

    @Override
    public ApiResponse handle(ApiRequest apiRequest) {


        LocalDateTime receiveTime = LocalDateTime.now();
        ApiResponse apiResponse = new ApiResponse();
        try {
            filterChain.doFilter(apiRequest, apiResponse);
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.SUCCESS.name());
        } catch (CustomException ex) {
            // 自定义异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode("S00");
            apiResponse.setErrorMessage(ex.getMessage());
        } catch (ApiException ex) {
            // API异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode(ex.getErrorCode());
            apiResponse.setErrorMessage(ex.getMessage());
        } catch (Exception ex) {
            // 非预期异常处理
            apiResponse.setExecuteResult(ApiServiceExecuteResultEnum.ERROR.name());
            apiResponse.setErrorCode("S99");
            apiResponse.setErrorMessage("未定义异常:" + ex.getMessage());
        } finally {
            ApiServiceLogService apiServiceLogService = SpringUtil.getBean(ApiServiceLogService.class);
            // 记录日志
            apiServiceLogService.recordLog(apiRequest, apiResponse, receiveTime);

        }

        return apiResponse;
    }
}

运行,发现多次接口调用进行测试,每次接口调用,无论是ApiRestService还是ApiFilterChain,都不是同一个对象了,因此线程肯定是安全的了。

开源平台资料

平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:[csdn专栏]
开源地址:[Gitee]
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~
请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!

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

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

相关文章

高配小主机加装SSD固态硬盘,我选择性能与设计兼备的希捷酷鱼 530

高配小主机加装SSD固态硬盘&#xff0c;我选择性能与设计兼备的希捷酷鱼 530 哈喽小伙伴们好&#xff0c;我是Stark-C~ 我最近入手了零刻的一款新发布的 GTi12 Ultra高性能迷你主机&#xff0c;其出色的配置与强大的功能让我有了将它用作主力机的打算。不过因为它的高配版本搭…

【记录一下VMware上开虚拟端口映射到公网】

材料 win11 和装在vmware上的ubuntu 步骤一在Ubuntu上配置静态地址&#xff0c;配置如下 vim /etc/netplan/01-network-manager-all.yaml(此文件看系统上对应的是哪个文件&#xff0c;建议先备份)network:version: 2renderer: NetworkManagerethernets:ens33:dhcp4: falseadd…

四十一、完成内容添加功能(使用go测试方法)

目录 一、添加model 二、完成相关dao 三、使用测试类进行测试 1、把光标防止要测试的方法上&#xff0c;右击并选择 2、自动会生成一个以dao文件加_test命名的文件 3、在其中完善方法并完成测试 四、完成content_create_handle 一、添加model 按数据库字段以及字段格式完…

Android 如何实现搜索功能:本地搜索?数据模型如何设计?数据如何展示和保存?

目录 效果图为什么需要搜索功能如何设计搜索本地的功能&#xff0c;如何维护呢&#xff1f;总结 一、效果图 二、为什么需要搜索功能 找一个选项&#xff0c;需要花非常多的时间&#xff0c;并且每次都需要指导客户在哪里&#xff0c;现在只要让他们搜索一下就可以。这也是模…

基于SpringBoot+Vue的剧本杀管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的…

AIGC7: 高通骁龙AIPC开发者沙龙过程记录A

图中是一座高耸的宫殿。 就像AI的出现&#xff0c;慢慢初现端倪&#xff0c;头角峥嵘。 背景 一直以来都比较关注AI的发展&#xff0c;有幸再一次参加异常AI的盛会。 从我的角度看。 高通是一家生产芯片的公司&#xff0c;国内的小米&#xff0c;荣耀&#xff0c;Oppo , Vi…

华为为什么要做三折叠屏手机?

前些天我做了一条视频&#xff0c;关于讲华W的新的三折叠屏手机。我说我有点失望&#xff0c;结果引起了华W的同事的一些关注。于是&#xff0c;华W几位高管都跑过来&#xff0c;跟我解释为什么会出现这样的一个状态。 我才知道&#xff0c;这款手机他们其实是亏着钱在卖的。因…

【测试】——Selenium API (万字详解)

&#x1f4d6; 前言&#xff1a;本文详细介绍了如何利用Selenium进行Web自动化测试&#xff0c;包括定位元素&#xff08;如cssSelector和xpath&#xff09;、常用操作函数&#xff08;如点击、输入等&#xff09;、窗口管理、键盘鼠标事件和浏览器导航&#xff0c;以及处理弹窗…

图说GPT网络结构(参数量与计算量估计)

现在AI领域的主流模型几乎都是Transformer网络架构衍生而来。大热的LLM中的生成类模型很多都是来自于Transformer的变体&#xff0c;即decoder only架构。而GPT就是该类中的经典模型。尽管现在变体甚多&#xff0c;但大多没有根本性地改变其套路。 为了阐述方便&#xff0c;首…

音视频入门基础:AAC专题(8)——FFmpeg源码中计算AAC裸流AVStream的time_base的实现

音视频入门基础&#xff1a;AAC专题系列文章&#xff1a; 音视频入门基础&#xff1a;AAC专题&#xff08;1&#xff09;——AAC官方文档下载 音视频入门基础&#xff1a;AAC专题&#xff08;2&#xff09;——使用FFmpeg命令生成AAC裸流文件 音视频入门基础&#xff1a;AAC…

数据安全治理

数据安全治理 1.数据安全治理2.终端数据安全加密类权限控制类终端DLP类桌面虚拟化安全桌面 3.网络数据安全4.存储数据安全5.应用数据安全6.其他话题数据脱敏水印与溯源 7.UEBA8.CASB 1.数据安全治理 数据安全治理最为重要的是进行数据安全策略和流程制订。在企业或行业内经常发…

[大语言模型-论文精读] 以《黑神话:悟空》为研究案例探讨VLMs能否玩动作角色扮演游戏?

1. 论文简介 论文《Can VLMs Play Action Role-Playing Games? Take Black Myth Wukong as a Study Case》是阿里巴巴集团的Peng Chen、Pi Bu、Jun Song和Yuan Gao&#xff0c;在2024.09.19提交到arXiv上的研究论文。 论文: https://arxiv.org/abs/2409.12889代码和数据: h…

通过logstash同步elasticsearch数据

1 概述 logstash是一个对数据进行抽取、转换、输出的工具&#xff0c;能对接多种数据源和目标数据。本文介绍通过它来同步elasticsearch的数据。 2 环境 实验仅仅需要一台logstash机器和两台elasticsearch机器&#xff08;elasticsearch v7.1.0&#xff09;。本文用docker来模…

人工智能不是人工“制”能

文/孟永辉 如果你去过今年在上海举办的世界人工智能大会&#xff0c;就会知道当下的人工智能行业在中国是多么火爆。 的确&#xff0c;作为第四次工业革命的重要组成部分&#xff0c;人工智能愈发引起越来越多的重视。 不仅仅是在中国&#xff0c;当今世界的很多工业强国都在将…

HarmonyOS 速记

目录 装饰器Entry(入口)Component(组件)Builder(构建)State(状态)Prop(属性)Preview(预览)PreviewerInspector 结构体structbuild自定义组件自定义 Custom 组件 export(导出) & import(导入) Page(页面)生命周期aboutToAppear 数据Array(数组/集合)Map(映射) 容器&#xff…

Wireshark学习使用记录

wireshark 是一个非常好用的抓包工具&#xff0c;使用 wireshark 工具抓包分析&#xff0c;是学习网络编程必不可少的一项技能。 原理 Wireshark使用的环境大致分为两种:一种是电脑直连互联网的单机环境&#xff0c;另外一种就是应用比较多的互联网环境&#xff0c;也就是连接…

nginx模块篇(四)

文章目录 四、Nginx的扩展模块4.1. Lua4.1.1 概念4.1.2 特性4.1.3 应用场景4.1.4 Lua的安装4.1.5 Lua的语法4.1.5.1 第一个Lua程序4.1.5.2 Lua的注释4.1.5.3 标识符4.1.5.4 关键字4.1.5.5 运算符4.1.5.6 全局变量&局部变量4.1.5.7 Lua数据类型nilbooleannumberstringtablef…

Windows本地连接远程服务器并创建新用户详细记录

前提可知&#xff1a; &#xff08;1&#xff09;服务器IP地址&#xff1a;x.x.x.x &#xff08;2&#xff09;服务器名称&#xff1a;root&#xff08;一般默认为root&#xff0c;当然也有别的名称&#xff09; &#xff08;3&#xff09;服务器登陆密码&#xff1a;**** 一、…

都市女生热衷找搭子的原因?只因对生活的热爱和追求

在繁华的都市中&#xff0c;有一个叫小悠的女生。她独自在这个忙碌的世界里闯荡&#xff0c;常常感到孤独。 有一天&#xff0c;小悠想去看一场期待已久的演唱会&#xff0c;可是身边的朋友要么没时间&#xff0c;要么对这场演唱会不感兴趣。就在她感到失落的时候&#xff0c;她…

使用llama.cpp 在推理MiniCPM-1.2B模型

llama.cpp 是一个开源项目&#xff0c;它允许用户在C中实现与LLaMA&#xff08;Large Language Model Meta AI&#xff09;模型的交互。LLaMA模型是由Meta Platforms开发的一种大型语言模型&#xff0c;虽然llama.cpp本身并不包含LLaMA模型的训练代码或模型权重&#xff0c;但它…