USB系列-LibUSB使用指南(1)-Windows下的报错与踩坑

这篇具有很好参考价值的文章主要介绍了USB系列-LibUSB使用指南(1)-Windows下的报错与踩坑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

成就更好的自己

时隔一年再次开始撰写博客,这一年的时间经历了很多,现在终于稳定下来。以后很长一段时间都能够稳定的学习和更新。时间将会聚焦于USB和PCIe的开发进行,能和大家共同进步真的很高兴。

本篇为USB系列的LibUSB使用指南的第一篇。

USB系列主要围绕USB的知识、协议、开发总结、使用说明等进行。

LibUSB使用指南主要围绕LibUSB库的使用进行。


LibUSB中的描述符结构分析

LibUSB中的描述符结构主要分为一下几种层次:

设备描述符->配置描述符->接口描述符(备用接口描述符)->端点描述符

看到这篇博客的都应该知道上述描述符的包含关系和可能的存在数量,基础知识不再赘述,到时候会专门写对应的基础知识博客。

LibUSB中的描述符包含关系与结构如下图所示:

libusb,USB开发系列,USB,Windows,LibUSB

 

从图中可以看出是没有设备描述符libusb_device_descriptor的,因为设备描述符一般可以看做是以设备为对象的一种描述结构,因此每个设备只有一个而且地位特殊。

图中最高层是配置描述符libusb_config_descripto,使用libusb_get_config_descriptor函数传入索引可以获得该设备中的某一个配置描述符。配置描述符中有一项bNumInterfaces为接口描述符的个数,可通过该项获得在该配置描述符中接口描述符的个数。

在配置描述符中还有一项为libusb_interface *interface,该项就是接口描述符的结构体数组。这个接口描述符与我们广义上理解的接口描述符不同,这里的接口描述符是由备用接口描述符结构体数组和备用描述符个数组成的,也就是libusb_interface_descriptor *altsetting和num_altsetting这两项。

要注意的是bNumInterfaces所指是接口描述符数组的个数,num_altsetting所指的是每个接口描述符数组成员中的备用接口描述符的数量。因此实际的配置描述符是由备用接口描述符组成的一个二维数组,这个二维数组的主索引是bNumInterfaces,副索引是num_altsetting。

在备用接口描述符数组中的每个成员都携带着在该备用接口下所属的若干端点的描述符结构体。

整个上面的过程中只有这个备用接口的概念和广义理解上的不太一样,剩下各个描述符中的各个字段均与协议栈中我们所通识的一样。实际上,这些结构体的存在方式和使用LibUSB函数获取描述符的方式与USB设备的枚举过程和方式的有着本质上的关联(后期博客提到)。

下面就是以博主电脑上为例,输出能探测到设备的详细信息(太长了,部分截图):

libusb,USB开发系列,USB,Windows,LibUSB

Windows下的USB驱动问题

Windows下LibUSB产生的很多报错与驱动有着很大关系。

我先言简意骇的简要介绍一下在你的Windows中可能存在的各种USB驱动,我暂时驱动分为高级的功能性驱动和低级的设备性驱动:

高级的功能性驱动:比如插入MSD类设备、移动硬盘、蓝牙适配器、HID类设备、网卡等具有实际功能性意义的设备时,Windows底层会自动的识别该设备的类和功能,从而找到一款能够直接适配该设备功能的通用高级驱动,这样就实现了目前Windows下多数陌生设备的免驱。

低级的设备性驱动:当插入的设备极为特殊,Windows认不出来这个玩意是干嘛的(比如设备描述符和接口描述符中的类字段与子类字段全部为0xff,即厂家定义),这个时候仅仅只是能够进行设备的基础枚举操作和通信。此时能够支撑设备枚举和通信的驱动就是设备性驱动,比如libusb-win32或WinUSB等。

假如我现在插入一个U盘就开始调用LibUSB库进行Open操作,这样是一定会返回错误的,因为LibUSB本身不是驱动,实际上也是要调用驱动留出的系统调用接口进行数据和指令传递的(这里类比linux的特性,表述可能些许不合适)。当这个设备底层根本不是LibUSB需要的接口的时候,当然是无法正常调用。

此时需要将该设备的驱动换成合适的低级驱动(linux的优势就出来了),LibUSB库在Windows平台上是基于WinUSB底层驱动去开发的。因此最适合LibUSB开发的底层驱动当然是WinUSB。博主尝试过使用linusb-win32等其他的驱动,他们可以open设备但是在claim接口的时候会有掉设备的情况(一claim就听到Windows掉USB设备的声音,正常后不会这样),直接导致transfer的时候返回LIBUSB_ERROR_IO(-1)。因此,在进行Windows下的LibUSB开发之前先解决你的驱动问题。

驱动问题的解决方式是通过zadig工具进行驱动替换,首先插入你的设备,list一下获得设备名单,选择你的设备,选择winUSB驱动,replace即可。前提是要有网(公司的机器没有网,GG)。如图(博主使用的SMT32USBD虚拟为com):

libusb,USB开发系列,USB,Windows,LibUSB

注意,使用此种方式他只会根据选择设备的设备号进行定向更换,这样就产生了两个问题:第一,替换的效果只能适用同一VID和PID的设备。第二,被替换的设备从此以后只能使用这个低级驱动,原来高级驱动的功能将不能使用。若不小心将重要设备的驱动进行重置和降级,那就去zadig工具的官网上寻找答案吧(当时无知把移动硬盘驱动给降级了,人都裂开了)。

zadig官网与技术支持:

​​​​​​https://www.baidu.com/link?url=UuDz3G0f6UbanYtYLC8SIC6sDu-aTCuBlW1BkriYHqK&wd=&eqid=cd9600c2000f6899000000046358207b

Open_device过程

这一节咱们直接面向对象来说:

  1. libusb_open_device_with_vid_pid或者open_device:打开设备,得到一个libusb_device_handle,没啥说的,驱动没安好你就是开不了,气不气。
  2. libusb_get_device:由libusb_device_handle结构体反推libusb_device_descriptor结构体用的(open_device的不用)。
  3. libusb_get_device_descriptor:获取端点信息的第一步—获取设备描述符。
  4. libusb_get_config_descriptor:获取端点信息的第二步—获取配置描述符。、
  5. 使用上一步的信息,通过一系列if筛选,找到你想要建立通信的备用接口和备用接口中的端点地址。
  6. libusb_claim_interface和libusb_set_interface_alt_setting:声明接口和备用接口,传入参数为索引值,上一步得到的。驱动没安好你就是claim不了(ret=-5),气不气。
  7. libusb_clear_halt:清除端点和对应通道中的残存的数据,为下一次通信做好准备。
  8. 该free的free,该release的release。

到此为止,正常情况下你就应该得到一个已经open的设备和一些(>=1)准备传输的端点。注意:第六第七步的顺序千万不能倒过来,否则你的libusb_clear_halt会报错LIBUSB_ERROR_NOT_FOUND (ret=-5)。而且在Windows开发中,LibUSB中的detach_kernel系列的函数都是多余的,人家Windows的原理就不需要这些操作。

Transfer过程

博主使用的是libusb_bulk_transfer传输的,中间出了点小问题,简要说一下:

  1. libusb_bulk_transfer返回LIBUSB_ERROR_IO(-1)大概率是因为你的驱动没有装对,libusb0 (v2.6.0)的驱动能够open和claim不报错,但是就是在传输的时候报错。
  2. libusb_bulk_transfer返回LIBUSB_ERROR_NOT_FOUND (-5)大概率是因为你claim这一步没有正常claim。注意如果你的端点出自备用接口,记得claim备用接口(claim标准接口好像是默认claim该接口下索引为0的备用接口,保险起见不管备用接口索引是几,记得claim一下备用接口)。

贴一下程序(bulk)

main.c

#include <stdio.h>
#include <main.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>
#include "usb_device_opt.h"

const uint16_t VID = 0x1234;
const uint16_t PID = 0x1234;
//const uint16_t VID = 0x0bda;
//const uint16_t PID = 0x8179;
int main(void)
{
	libusb_device **devs;
	USB_MSD_ST msd = {NULL, NULL, 0x00, 0x00};
	uint8_t err = 0;
	int16_t dev_num;

    Log_Init(3);
	err = libusb_init(NULL);
	if(err > 0)
    {
        LOG_ERROR("libusb_init() err with %d", err);
        goto init_err;
    }
    //libusb_set_option(NULL, LIBUSB_OPTION_LOG_LEVEL , LIBUSB_LOG_LEVEL_INFO);

	dev_num = libusb_get_device_list(NULL, &devs);
	if(dev_num < 0)
    {
        LOG_ERROR("libusb_get_device_list() err with %d", err);
        goto getlist_err;
    }

	USB_Dev_Scan_A_Print(dev_num, devs);

    err = USB_MSD_Open(VID, PID, &msd);
    if(err > 0)
    {
        USB_MSD_Close(&msd);
        goto getlist_err;
    }

    uint8_t buff[512];
    int size = 0;
    memset(buff, 9, 512);
    err = USB_MSD_Bulk_Write(&msd, buff, 64, &size, 1000);
    if(err > 0)
    {
        USB_MSD_Close(&msd);
        goto getlist_err;
    }
    else
    {
        LOG_INFO("Send OK!");
    }
    err = USB_MSD_Close(&msd);

getlist_err:
	libusb_free_device_list(devs, 1);
init_err:
	libusb_exit(NULL);
	return 0;
}

 usb_device_opt.c

#include <stdio.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>
#include "usb_device_opt.h"

uint8_t USB_Dev_Scan_A_Print(int16_t dev_num, libusb_device **devs)
{
    struct libusb_device_descriptor usb_dev_desc;
    struct libusb_config_descriptor *usb_cfg_desc;
    uint8_t err = 0;


    LOG_INFO("Dev_Num:%d", dev_num);
    for(uint16_t i = 0;i < dev_num; i++)
    {
        err = libusb_get_device_descriptor(devs[i], &usb_dev_desc);
        if(err > 0)
        {
            LOG_ERROR("libusb_get_device_descriptor()  err with %d", err);
            goto getdevdesc_err;
        }

        printf("|--[Vid:0x%04x, Pid:0x%04x]-[Class:0x%02x, SubClass:0x%02x]-[bus:%d, device:%d, port:%d]-[cfg_desc_num:%d]\n",
            usb_dev_desc.idVendor, usb_dev_desc.idProduct, usb_dev_desc.bDeviceClass, usb_dev_desc.bDeviceSubClass,
            libusb_get_bus_number(devs[i]), libusb_get_device_address(devs[i]), libusb_get_port_number(devs[i]), usb_dev_desc.bNumConfigurations);
        //printf()
        for(uint8_t j = 0;j < usb_dev_desc.bNumConfigurations; j++)
        {
            err = libusb_get_config_descriptor(devs[i], j, &usb_cfg_desc);
            if(err > 0)
            {
                LOG_ERROR("libusb_get_config_descriptor(cfg_index:%d)  err with %d", j, err);
                goto getcfgdesc_err;
            }
            printf("|  |--cfg_desc:%02d-[cfg_value:0x%01x]-[infc_desc_num:%02d]\n",
                j, usb_cfg_desc->bConfigurationValue, usb_cfg_desc->bNumInterfaces);
            for(uint8_t l = 0;l < usb_cfg_desc->bNumInterfaces; l++)
            for(uint8_t n = 0;n < usb_cfg_desc->interface[l].num_altsetting; n++)
            {
                printf("|  |  |--intfc_desc: %02d:%02d-[Class:0x%02x, SubClass:0x%02x]-[ep_desc_num:%02d]\n",
                    l, n, usb_cfg_desc->interface[l].altsetting[n].bInterfaceClass, usb_cfg_desc->interface[l].altsetting[n].bInterfaceSubClass,
                    usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints);
                 for(uint8_t m = 0;m < usb_cfg_desc->interface[l].altsetting[n].bNumEndpoints; m++)
                 {
                    printf("|  |  |  |--ep_desc:%02d-[Add:0x%02x]-[Attr:0x%02x]-[MaxPkgLen:%02d]\n",
                        m, usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bEndpointAddress,
                        usb_cfg_desc->interface[l].altsetting[n].endpoint[m].bmAttributes,
                        usb_cfg_desc->interface[l].altsetting[n].endpoint[m].wMaxPacketSize);
                 }
            }
        }
    }
    return 0;
getdevdesc_err:
    return 0xff;
getcfgdesc_err:
    return 0xff;
}

uint8_t USB_MSD_Open(uint16_t VID, uint16_t PID, USB_MSD_ST *msd)
{
    struct libusb_device_descriptor usb_dev_desc;
    struct libusb_config_descriptor *usb_cfg_desc;
    uint8_t intfc_index = 0;
    uint8_t err = 0;

    msd->msd_handle = libusb_open_device_with_vid_pid(NULL, VID, PID);
    if(msd->msd_handle == NULL)
    {
        LOG_ERROR("[0x%04x:0x%04x] MSD Open failed!", VID, PID);
        goto opendev_err;
    }
    msd->msd_dev = libusb_get_device(msd->msd_handle);
    if(msd->msd_dev == NULL)
    {
        LOG_ERROR("[0x%04x:0x%04x] get dev failed!", VID, PID);
        goto getdev_err;
    }
    err = libusb_get_device_descriptor(msd->msd_dev, &usb_dev_desc);
    if(err > 0)
    {
        LOG_ERROR("[0x%04x:0x%04x] get dev_desc failed err with %d", VID, PID, err);
        goto getdevdesc_err;
    }
    err = libusb_get_config_descriptor(msd->msd_dev, 0, &usb_cfg_desc);
    if(err > 0)
    {
        LOG_ERROR("[0x%04x:0x%04x] get cfg_desc failed err with %d", VID, PID, err);
        goto getcfgdesc_err;
    }
    for(uint8_t m = 0;m < usb_cfg_desc->bNumInterfaces; m++)
    {
        for(uint8_t n = 0;n < usb_cfg_desc->interface[m].num_altsetting;n++)
        {
            if(usb_cfg_desc->interface[m].altsetting[n].bInterfaceClass == 0x0a && usb_cfg_desc->interface[m].altsetting[n].bInterfaceSubClass == 0x00)
            {
                for(uint8_t i = 0;i < usb_cfg_desc->interface[m].altsetting[n].bNumEndpoints;i++)
                {
                    if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK)
                    {
                        if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_IN)
                        {
                            msd->endpoint_in = usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress;
                        }
                        if((usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress & 0x80) == LIBUSB_ENDPOINT_OUT)
                        {
                            msd->endpoint_out = usb_cfg_desc->interface[m].altsetting[n].endpoint[i].bEndpointAddress;
                        }
                    }
                }
                if(msd->endpoint_in != 0x00 && msd->endpoint_out != 0x00)
                {
                    intfc_index = m;
                }
                else
                {
                    msd->endpoint_in = 0x00;
                    msd->endpoint_out = 0x00;
                }
            }
        }
    }
    if(msd->endpoint_in == 0x00 || msd->endpoint_out == 0x00)
    {
        LOG_ERROR("[0x%04x:0x%04x] get ep_addr failed!", VID, PID);
        goto getepaddr_err;
    }

    err = libusb_claim_interface(msd->msd_handle, 1);
    err = libusb_set_interface_alt_setting(msd->msd_handle, 1, 0);
    err = libusb_claim_interface(msd->msd_handle, 0);
    err = libusb_set_interface_alt_setting(msd->msd_handle, 0, 0);
    if(err > 0)
    {
        LOG_ERROR("[0x%04x:0x%04x] claim intfc failed err with %d", VID, PID, err);
        goto claimintfc_err;
    }

    err = libusb_clear_halt(msd->msd_handle, msd->endpoint_out);
    if(err > 0)
    {
        LOG_ERROR("[0x%04x:0x%04x] ep_out:%x clear halt failed err with %d", VID, PID, msd->endpoint_out, (int8_t)err);
        goto epclrhalt_err;
    }

    err = libusb_clear_halt(msd->msd_handle, msd->endpoint_in);
    if(err > 0)
    {
        LOG_ERROR("[0x%04x:0x%04x] ep_in:%x clear halt failed err with %d", VID, PID, msd->endpoint_in, (int8_t)err);
        goto epclrhalt_err;
    }

    libusb_free_config_descriptor(usb_cfg_desc);
    libusb_reset_device(msd->msd_handle);
    LOG_INFO("[0x%04x:0x%04x]-[EP_IN:0x%02x, EP_OUT:0x%02x] Open success!", VID, PID, msd->endpoint_in, msd->endpoint_out);

    return 0;
//ÓÐÎÊÌâ

claimintfc_err:
    libusb_release_interface(msd->msd_handle, 1);
    libusb_release_interface(msd->msd_handle, 0);

detachkernel_err:

epclrhalt_err:

getepaddr_err:

getcfgdesc_err:

getdevdesc_err:

getdev_err:

opendev_err:
    libusb_free_config_descriptor(usb_cfg_desc);
    libusb_close(msd->msd_handle);
    return 0xff;
}

uint8_t USB_MSD_Close(USB_MSD_ST *msd)
{
    if(msd->msd_handle != NULL)
    {
        libusb_release_interface(msd->msd_handle, 0);
        libusb_close(msd->msd_handle);
    }
    return 0;
}

uint8_t USB_MSD_Bulk_Write(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms)
{
	int err = 0;

	err = libusb_bulk_transfer(msd->msd_handle, msd->endpoint_out, buffer, len, size, ms);
	if (err < 0)
	{
		LOG_ERROR("Write:%d", err);
		return -1;
	}

	return 0;
}

uint8_t USB_MSD_Bulk_Read(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms)
{
	int err = 0;

	err = libusb_bulk_transfer(msd->msd_handle, msd->endpoint_in, buffer, len, size, ms);
	if (err < 0)
	{
		LOG_ERROR("Read:%d", err);
		return -1;
	}

	return 0;
}


usb_device_opt.h

#ifndef _USB_DEVICE_OPT_
#define _USB_DEVICE_OPT_
#include <stdio.h>
#include <libusb.h>
#include "log.h"
#include <stdint.h>

typedef struct USB_MSD
{
    libusb_device* msd_dev;
    libusb_device_handle* msd_handle;
    uint8_t endpoint_in;
    uint8_t endpoint_out;
} USB_MSD_ST;

extern uint8_t USB_Dev_Scan_A_Print(int16_t dev_num, libusb_device **devs);
extern uint8_t USB_MSD_Open(uint16_t VID, uint16_t PID, USB_MSD_ST *msd);
extern uint8_t USB_MSD_Bulk_Write(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms);
extern uint8_t USB_MSD_Bulk_Read(USB_MSD_ST* msd, uint8_t* buffer, int len, int* size, uint32_t ms);
extern uint8_t USB_MSD_Close(USB_MSD_ST *msd);

#endif // _USB_DEVICE_OPT_

Good Night!文章来源地址https://www.toymoban.com/news/detail-836972.html

到了这里,关于USB系列-LibUSB使用指南(1)-Windows下的报错与踩坑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 去掉鼠标系列之一: 语雀快捷键使用指南

    其实应该是系列之二了,因为前面写了一个关于Interlij IDEA的快捷键了。 为什么要写这个了,主要是觉得一会儿用鼠标,一会儿键盘,一点儿不酷,我希望可以一直用键盘,抛开鼠标。后面陆续记录一下各个软件的快捷键,一步步减少鼠标的使用。 gogogo! 1,搜索 Ctrl+J 2, 撤

    2024年02月12日
    浏览(46)
  • 16-3_Qt 5.9 C++开发指南_使用QStyle 设置界面外观_实现不同系统下的界面效果的匹配

    Qt 是一个跨平台的类库,相同的界面组件在不同的操作系统上显示效果是不一样的。QStyle是封装了 GUI 界面组件外观的抽象类,Qt 定义了 QStyle 类的一些子类,应用于不同的操作系统如QWindowsStyle和QMacStyle 等。这些样式是 QtGUI 模块自带的,在不同的平台上编译运行的程序具有缺

    2024年02月13日
    浏览(49)
  • grasscutter 使用指南——Android/Windows/IOS端均已支持

    grasscutter是某二次元手游的开源后端,目前功能并不完整,但正在contributers正在全速开发中,未来可期。可以部署在linux和windows下,通过代理与各种平台的客户端进行交互。本文提供grasscutter的linux端部署(windows端比较简单)、windows/andriod/ios端的客户端连接指导,其中安卓提

    2023年04月09日
    浏览(39)
  • 新机开荒攻略及新手电脑使用指南不完全手册:Windows 电脑

    介绍一下 Windows 的新机开荒、使用指南、软件推荐。 如果您没接触过电脑请先于 Bilibili 搜索零基础电脑入门教学,学习电脑基本操作。 我这里介绍的内容较多,有些功能的作用也不是必须,所以您可以酌情学习。 卸载多余的无用UWP应用 您可以使用 Geek Uninstaller工具卸载不需

    2024年02月04日
    浏览(48)
  • C语言调用libusb访问USB驱动

    目录 一、环境搭建 1. 下载库文件 2. 解压 3. 配置VS工程 3.1 头文件的配置 3.2 静态库文件的处理 3.3 配置运行时库 二、生成自定义设备的驱动 1. 禁用Windows驱动程序强制签名 2. 设备描述符的设计 3. 设备枚举 4. 如何为自己的设备安装WinUSB驱动 三、测试 1. 测试代码 2. 小结 开发环

    2024年02月07日
    浏览(26)
  • 网安工具 | Windows便携式渗透测试环境PentestBox入门到进阶使用指南

    [ 点击 👉 关注「 全栈工程师修炼指南」公众号 ] 微信改版了,现在看到我们全凭缘分,为了不错过【全栈工程师修炼指南】重要内容及福利,大家记得按照上方步骤设置「接收文章推送」哦~ 希望各位看友多多支持【关注、点赞、评论、收藏、投币】,助力每一个梦想。 【

    2024年02月08日
    浏览(42)
  • 测试员进阶必看系列 “ python自动化测试工具selenium使用指南 ”

    概述 python+selenium环境安装 使用selenium启动浏览器 selenium页面加载等待和检测 使用time.sleep()等待 使用implicitly_wait设置最长等待时间 使用WebDriverWait设置等待条件 检测document是否加载完成 selenium元素定位和读取 查找元素 dom元素交互 查找元素失败处理 selenium交互控制 ActionChains动

    2024年02月05日
    浏览(107)
  • Spark初学者指南:使用指南和示例

    本文介绍了如何使用Spark处理大规模数据集,并提供了一个Scala编写的Word Count示例,指导您从安装和配置到编写和运行Spark应用程序。无需担心,即使您是Spark初学者,也可以按照本文的步骤来学习和使用Spark。 Spark是一个流行的分布式计算框架,用于处理大规模数据集。它使

    2024年02月06日
    浏览(61)
  • 【12】Git工具 协同工作平台使用教程 Gitee使用指南 腾讯工蜂使用指南【Gitee】【腾讯工蜂】【Git】

    tips:少量的git安装和使用教程,更多讲快速使用上手Gitee和工蜂平台      Git - Downloads (git-scm.com) 找到对应操作系统,对应版本,对应的位数   下载后根据需求自己安装,然后用git --version验证是否成功   使用 SSH 密钥可以让你在与 Git 服务器进行通信时,实现更安全的身份

    2024年02月13日
    浏览(47)
  • Jmeter进阶使用指南-使用断言

    Apache JMeter是一个流行的开源负载和性能测试工具。在JMeter中,断言(Assertions)是用来验证响应数据是否符合预期的一个重要组件。它是对请求响应的一种检查,如果响应不符合预期,那么断言会标记为失败。 以下是如何在JMeter中使用断言的基本步骤: 添加断言 :首先,你

    2024年02月09日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包