当前位置: 首页 > news >正文

Windows蓝牙驱动开发之模拟HID设备(一)(把Windows电脑模拟成蓝牙鼠标和蓝牙键盘等设备)

                            by fanxiushu 2024-03-14 转载或引用请注明原作者
把Windows电脑模拟成蓝牙鼠标和蓝牙键盘,简单的说,就是把笨重的PC电脑当成鼠标键盘来使用。
这应该是一个挺小众的应用,但有时感觉也应该算比较好玩吧,
毕竟实现一种一般人都感觉没戏的功能,
尤其是windows平台中,并不是简单实现蓝牙服务端,还得做一个小小的破解。

windows电脑当成蓝牙鼠标键盘,这个功能有什么用呢?
可以用来控制手机啊!我最初研究把windows当成蓝牙鼠标键盘,确实是出于这个目的。
我们先来看看手机系统,Android和iOS,
Android不知道从哪个版本开始,已经可以提供用户层API,可以直接调用对应接口,
直接在程序中进行 touch,mouse,keyboard 等控制模拟。也就是说,Android系统是可以通过编程实现被远程控制的。
但iOS比较特殊,至今都没看到集成这样的用户层API来进行输入控制模拟。
因此iOS是没法像目前的通用远程控制软件那样,直接远程控制iOS系统。当然,只是投屏的话是没问题的。

以前开发iOS系统下的xdisp_virt远程控制程序,苦于找不到对应的输入模拟接口,只能把xdisp_virt当成投屏来使用。
后来发现iOS系统虽然不提供对应接口,但是却支持蓝牙鼠标键盘。
于是就打起了模拟蓝牙鼠标键盘的主意。
当然,蓝牙鼠标键盘模拟,不等同于普通意义上的远程控制,因为蓝牙传输距离有限 ,也就顶多几十米。
我们也可以使用一个笨办法:
使用linux系统电脑(因为linux下的蓝牙鼠标键盘更好模拟)模拟出蓝牙鼠标键盘,然后连上iOS苹果手机。
然后linux系统模拟的蓝牙鼠标键盘程序再通过普通网络把控制事件传输出去,这样就能达到普通意义上的远程控制了。
就是麻烦了些。

回到正题,如何在windows平台实现蓝牙鼠标键盘呢?
一般桌面电脑模拟设备终端,都有天然的障碍,因为当初设计就不是为了实现设备功能的。
比如要把桌面电脑模拟成 USB 设备,需要底层硬件支持,需要有UDC硬件控制器。
有了UDC底层硬件还不够,还得系统提供对应接口,
好在linux内核很早前就提供了UDC对应系统接口,windows10以上的系统也提供了UDC接口。

值得庆幸的是蓝牙设计的时候,同时提供了服务端和客户端,也就是同时提供了两端。
这也挺好理解,蓝牙本质就是无线传输,如果蓝牙传输堆栈只提供客户端或者只提供服务端,就像缺胳膊少腿一样。

蓝牙鼠标和蓝牙键盘是作为蓝牙服务端对外提供服务的。而且是作为HID标准的输入设备。
因此本文也只阐述蓝牙服务端的实现过程,至于蓝牙客户端如何实现,可以去查阅WDK下的例子代码。
具体例子代码在bluetooth或者bth目录下的bthecho目录,它同时演示了服务端和客户端,以及如何安装。
例子代码是实现自己的上层传输(ECHO回显),但是作为蓝牙鼠标键盘,其上层传输协议是固定和公开的。
这也是与例子不同的地方,除此之外,流程什么的都是一样的。

开发windows蓝牙服务端,需要实现以下几个部分:
1,初始化驱动,获取 BTH_PROFILE_DRIVER_INTERFACE,
      BTHDDI_SDP_PARSE_INTERFACE,BTHDDI_SDP_NODE_INTERFACE
     等三个接口,里边全是接口函数,用于后面处理,其中第一个接口主要是BRB分配和释放,第二,三个用于生成SDP信息

2,注册PSM,因为蓝牙鼠标键盘(就是统一的HID输入设备)的PSM是固定的 0x11 和 0x13, 其中0x11用于传输控制信息,
      0x13传输具体的鼠标键盘事件。
      但是windows系统把0x11和0x13作为保留值,也就是说,我们在自己的蓝牙驱动中,是无法注册这两个值的。
      这就是windows最大的坑,而且是一开始就让你觉得没戏的坑。
      因为它无法通过修改某些配置信息改变,而是被硬编码到windows系统组件中。

3,注册 L2CAP Server, 并且设置接收回调函数,也就是说如果有蓝牙客户端连上来,这个回调函数就会被调用,
      从而建立起连接,传输数据。

4,创建并且发布带有 HID 报告描述的 SDP 。

5,从第3步骤注册的L2CAP Server的回调函数中,接收到蓝牙客户端的连接请求,然后回复之后,连接就建立起来了。
      客户端会发起 0x11 和 0x13 共两条连接,根据 4 步骤的HID SDP配置,会在0x11控制传输中收到某些控制命令,
       响应这些命令,然后就可以通过 0x13这个连接,发送固定格式的鼠标键盘事件数据。

通过已上步骤,一个蓝牙鼠标键盘就模拟成功了。
通过以上我们也能发现,这个跟socket网络编程的服务端很像:
第1步骤就像是创建socket, 第2步骤是bind绑定socket,第3,4步骤在listen,
第5步骤就是 accept了。最后就是send和recv 了。

当然,为了更好的理解和开发蓝牙鼠标键盘驱动,我们还得去网上下载 蓝牙的 HID规范文档,
因为里边规定了蓝牙HID数据传输格式,SDP协议格式等。
同时也得准备windows的WDK开发包中 tools目录下的 bluetooth ,其中 sdpverify.exe 可以帮我们查看 SDP协议格式,
而蓝牙HID 格式内容较多,光看规范文档,是很头大的,所以还不如找个现成的蓝牙鼠标,然后用sdpverify程序查看SDP格式,
再然后仿照它建立自己的HID SDP 。

首先初始化蓝牙驱动,这就按照一般的 即插即用wdm驱动开发就可以了,
需要特别主意的是,它的安装方式比较特别,
我们需要使用 应用层WIN32API 函数 BluetoothSetLocalServiceInfo 创建一个底层设备,然后再把我们的驱动安装上去,
只有这样我们的驱动才是蓝牙驱动,才能获取到BTH_PROFILE_DRIVER_INTERFACE等接口。

接着在驱动的AddDevice初始化函数中,获取到BTH_PROFILE_DRIVER_INTERFACE等接口。
如果你是使用KMDF框架的(微软例子里也是KMDF框架)可以直接使用 WdfFdoQueryForInterface 函数获取。
而我的驱动是基于WDM的(以下阐述的都是基于WDM实现的蓝牙驱动)。所以得自己实现,其实也不难。如下:
NTSTATUS query_interface(PDEVICE_OBJECT device_object,
    LPCGUID InterfaceType, PINTERFACE Interface,
    USHORT Size, USHORT Version, PVOID InterfaceSpecificData)
{
    NTSTATUS status = STATUS_SUCCESS;
    KEVENT  event;
    PIRP irp;
    IO_STATUS_BLOCK ioStatusBlock;
    PIO_STACK_LOCATION irpStack;

    KeInitializeEvent(&event, NotificationEvent, FALSE);

    irp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP,
        device_object,
        NULL,
        0,
        NULL,
        &event,
        &ioStatusBlock);
    if (irp == NULL) {
        status = STATUS_INSUFFICIENT_RESOURCES;
        return status;
    }

    irpStack = IoGetNextIrpStackLocation( irp );
    irpStack->MinorFunction = IRP_MN_QUERY_INTERFACE;
    irpStack->Parameters.QueryInterface.InterfaceType =
                        (LPGUID)InterfaceType;
    irpStack->Parameters.QueryInterface.Size = Size;
    irpStack->Parameters.QueryInterface.Version = Version;
    irpStack->Parameters.QueryInterface.Interface =
                                        (PINTERFACE)Interface;

    irpStack->Parameters.QueryInterface.InterfaceSpecificData = InterfaceSpecificData;

    //
    // Initialize the status to error in case the bus driver does not
    // set it correctly.
    irp->IoStatus.Status = STATUS_NOT_SUPPORTED ;

    status = IoCallDriver( device_object, irp );
    if (status == STATUS_PENDING) {

        status = KeWaitForSingleObject( &event, Executive, KernelMode, FALSE, NULL);

        status = ioStatusBlock.Status;

    }

    return status;
}

然后如下调用
status = query_interface(fdo->LowerDeviceObject,
        &GUID_BTHDDI_SDP_PARSE_INTERFACE, (PINTERFACE)&fdo->sdp_parse_interface,
        sizeof(fdo->sdp_parse_interface), BTHDDI_SDP_PARSE_INTERFACE_VERSION_FOR_QI, NULL);
就获取到了 BTH_PROFILE_DRIVER_INTERFACE 接口,使用同样办法获取其他两个用于操作 SDP 的接口。

同USB驱动类似, 蓝牙驱动使用 BRB 结构来传输数据,因此我们先实现一些通用函数,比如如下同步提交brb的函数:
///同步提交BRB
NTSTATUS bth_sync_call_driver(PDEVICE_OBJECT device_object, PVOID Brb )
{
    NTSTATUS status = STATUS_SUCCESS;
    KEVENT  event;
    IO_STATUS_BLOCK ioStatus;
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    PIRP irp = IoBuildDeviceIoControlRequest(IOCTL_INTERNAL_BTH_SUBMIT_BRB,
        device_object, NULL, 0, NULL,
        0, TRUE, &event, &ioStatus);
    ///
    if (!irp){
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    PIO_STACK_LOCATION nextStack = IoGetNextIrpStackLocation(irp);

    nextStack->Parameters.Others.Argument1 = Brb;

    status = IoCallDriver(device_object, irp);

    if (status == STATUS_PENDING){

       
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); /// wait for ever

        status = ioStatus.Status;
    }

    

    return status;
}

然后还可以实现异步传输 NTSTATUS bth_async_call_driver(
    PDEVICE_OBJECT device_object, PVOID Brb,
    void(*brb_complete)(brb_async_context* brb_ctx), PVOID ctx)
其中BrB就是BRB指针,brb_complete 是当完成这个brb请求时候的完成函数。brb_async_context是自己定义的结构体。
这里不再罗嗦列出代码了。

当初始化驱动完成,我们就需要开始注册 PSM了。这些需要注册 0x11和0x13,两个都需要注册。
PSM注册伪代码如下:

struct _BRB_PSM * brb;
brb = (struct _BRB_PSM*)brb_alloc(fdo, bRegister ? BRB_REGISTER_PSM : BRB_UNREGISTER_PSM );

brb->Psm = PSM;  // 这里应该是 0x11和0x13,两个都需要分别注册,

status = bth_sync_call_driver(fdo->LowerDeviceObject, brb);

brb_free(fdo, brb);

是的,注册PSM就这么简单,上面代码把注册和注销放到一起了。

但是注册0x11和0x13,肯定不会成功,既然这个都不会成功,那么接下来注册L2CAP等步骤也没有了意义。
这是因为在PSM注册过程中,windows系统组件会对PSM值做判断,如果是系统保留的,自然就会失败。
因此,为了保证注册成功,我们就得想办法让这个判断失效,
这等于是在做破解,不过破解的不是应用层的dll,
而是驱动sys文件,具体完成这个注册的系统组件是 bthport.sys ,bthport.sys如同应用层的dll一样,
是个扩展库,主要是帮忙实现蓝牙驱动的核心功能,比如注册,传输等各类都是在bthport.sys中完成。
所以可以对bthport.sys做逆向,比如使用 IDA工具软件进行分析,然后就会发现 里边有个未公开的函数
BthIsSystemPSM ,就是判断是否是系统保留的PSM,我们只要让这个函数失效,自然就能成功注册0x11和0x13了。
下面的连接(就有阐述如何破解BthIsSystemPSM函数)
Windows Kernel | Nadav's Blog
至于关于这方面的更多的内容,以后的章节会详细阐述。

接着,我们需要注册L2CAP Server,注册这个不会有什么系统保留限制,只要不出其他问题,都会成功
如下伪代码,注册L2CAP Server:

   struct _BRB_L2CA_REGISTER_SERVER *brb;
   brb = (struct _BRB_L2CA_REGISTER_SERVER*)brb_alloc(fdo, BRB_L2CA_REGISTER_SERVER);
   
    brb->BtAddress = BTH_ADDR_NULL;
    brb->PSM = 0; //we have already registered the PSM
    brb->IndicationCallback = &SrvIndicationCallback;
    brb->IndicationCallbackContext = userCtx;
    brb->IndicationFlags = 0;
    brb->ReferenceObject = fdo->DeviceObject;

    status = bth_sync_call_driver(fdo->LowerDeviceObject, brb ); //调用同步提交BRB函数,

    fdo->L2CAPServerHandle = brb->ServerHandle; /// save ptr,保持此句柄,再注销的时候会使用到

    brb_free(fdo, brb);

static void SrvIndicationCallback(
    __in PVOID Context,
    __in INDICATION_CODE Indication,
    __in PINDICATION_PARAMETERS Parameters
{
      bth_hid_user_t* user = (bth_hid_user_t*)Context;
      switch (Indication)
      {
      case IndicationRemoteConnect: 有客户端连接上来,
     {
        DPT("@@@@@@  Ctrl Connect --- PSM=0x%X; addr=%p\n",
            Parameters->Parameters.Connect.Request.PSM,
            Parameters->BtAddress );

        bth_connect_remote(user, Parameters); 调用我们的函数,初始化有新客户端连上的各种结构,并且回复客户端

        break;
       }
       }
}

以上就是注册L2CAP Server的过程,接下来就是如何创建 HID SDP 和发布SDP了。
HID SDP 的内容有点多,创建起来有点麻烦,以下是我的HID SDP内容:



包括的内容基本如上图所描述的那样,其中红色框中的 HID Descriptor List 包含的就是HID REPORT DESC
这个就是 HID REPORT DESC 则是标准的HID报告描述符,关于这个细节可以去查阅HID的规范文档。

至于如何生成 SDP报告, 则是全程使用 BTHDDI_SDP_NODE_INTERFACE 来构建各种NODE,
最终合并到 PSDP_TREE_ROOT_NODE 根 root tree 中,代码调用细节可以去查阅 微软的bthecho例子代码。
然后调用 BTHDDI_SDP_PARSE_INTERFACE 接口中的 SdpConvertTreeToStream 函数把root tree序列化为 stream,
假设序列化为Stream, 大小为StreamSize,再通过 IOCTL_BTH_SDP_SUBMIT_RECORD 把这个SDP Publish出去,
让蓝牙客户端能够看到我们的蓝牙HID设备,伪代码如下:

    KEVENT  event;
    IO_STATUS_BLOCK ioStatus;
    KeInitializeEvent(&event, NotificationEvent, FALSE);

    HANDLE_SDP handle = HANDLE_SDP_NULL;
    PIRP irp = IoBuildDeviceIoControlRequest(IOCTL_BTH_SDP_SUBMIT_RECORD,   / 发布SDP的 IOCTL
                         fdo->LowerDeviceObject, Stream, StreamSize, &handle,
                         sizeof(HANDLE_SDP), FALSE, &event, &ioStatus);

     status = IoCallDriver(fdo->LowerDeviceObject, irp);

    if (status == STATUS_PENDING) {

       
        KeWaitForSingleObject(&event, Executive, KernelMode, FALSE, NULL); /// wait for ever

        status = ioStatus.Status;
    }

至此之后,我们就可以安心等待蓝牙客户端连上来。
当有蓝牙客户端连上来之后,上面注册L2CAP Server时候设置的SrvIndicationCallback回调函数就会被调用。
我们响应 IndicationRemoteConnect 请求,在这个请求中,我们需要构建 _BRB_L2CA_OPEN_CHANNEL 的 BRB,
然后回答给客户端,只有这样才能真正建立起一条新的连接。
大致伪代码如下:
NTSTATUS bth_connect_remote(bth_hid_user_t* user, PINDICATION_PARAMETERS Parameters)
{
        。。其他初始化代码
    _BRB_L2CA_OPEN_CHANNEL* brb
    brb = (_BRB_L2CA_OPEN_CHANNEL*)brb_alloc(user->fdo, BRB_L2CA_OPEN_CHANNEL_RESPONSE);

    ///init brb
    brb->Hdr.ClientContext[0] = client;  /client是我们新建的结构体,代表一个蓝牙连接
    brb->BtAddress = Parameters->BtAddress;
    brb->Psm = Parameters->Parameters.Connect.Request.PSM;
    brb->ChannelHandle = Parameters->ConnectionHandle;
    brb->Response = CONNECT_RSP_RESULT_SUCCESS;

    brb->ChannelFlags = CF_ROLE_EITHER;

    brb->ConfigOut.Flags = 0;
    brb->ConfigIn.Flags = 0;

    brb->ConfigOut.Flags |= CFG_MTU;
    brb->ConfigOut.Mtu.Max = L2CAP_DEFAULT_MTU;
    brb->ConfigOut.Mtu.Min = L2CAP_MIN_MTU;
    brb->ConfigOut.Mtu.Preferred = L2CAP_DEFAULT_MTU;

    brb->ConfigIn.Flags = CFG_MTU;
    brb->ConfigIn.Mtu.Max = brb->ConfigOut.Mtu.Max;
    brb->ConfigIn.Mtu.Min = brb->ConfigOut.Mtu.Min;
    brb->ConfigIn.Mtu.Preferred = brb->ConfigOut.Mtu.Max;

    //
    // Get notifications about disconnect
    //设置对方断开连接的时候的回调函数
    brb->CallbackFlags = CALLBACK_DISCONNECT;
    brb->Callback = &BthSvrConnectionIndicationCallback;
    brb->CallbackContext = client;
    brb->ReferenceObject = user->fdo->DeviceObject;

   ///  采用异步方式调用BRB,以免阻塞系统的回调函数,
    status = bth_async_call_driver(user->fdo->LowerDeviceObject, brb, open_channel_response_complete, client);
    。。。。
}
static void
BthSvrConnectionIndicationCallback(
    __in PVOID Context,
    __in INDICATION_CODE Indication,
    __in PINDICATION_PARAMETERS Parameters
)
{
       。。。。
       switch(Indication)
       {
        case IndicationRemoteDisconnect: / 客户端已经关闭了此连接,我们也需要关闭以及释放相关结构
         。。。。
        break;
       }
}

static void open_channel_response_complete( brb_async_context* brbctx)
{
       NTSTATUS status = brbctx->Irp->IoStatus.Status;
        。。。。 回答客户端已经完成,通过判断 status 来确定是否已经成功建立了连接。
       if (NT_SUCCESS(status)) { /// response success..
       
        client->handle  = brb->ChannelHandle;  需要使用此handle 来接收和发送蓝牙数据包
        client->address = brb->BtAddress;          远端蓝牙地址
        client->OutMTU  = brb->OutResults.Params.Mtu;
        client->InMTU   = brb->InResults.Params.Mtu;
        。。。。其他处理。。。。
      }
}

至此,客户端发起的一个连接就建立了起来,我们可以通过这个连接收和发送蓝牙数据。‘

未完待续。。。

 

相关文章:

Windows蓝牙驱动开发之模拟HID设备(一)(把Windows电脑模拟成蓝牙鼠标和蓝牙键盘等设备)

by fanxiushu 2024-03-14 转载或引用请注明原作者 把Windows电脑模拟成蓝牙鼠标和蓝牙键盘,简单的说,就是把笨重的PC电脑当成鼠标键盘来使用。 这应该是一个挺小众的应用,但有时感觉也应该算比较好玩吧, 毕竟实现一种一般人都感觉…...

LlamaParse: 高效的PDF文件RAG解析工具

LlamaParse: 高效的PDF文件RAG解析工具 通过Thomas Reid的深入探索,LlamaParse成为了目前我所见最优秀的RAG实现用PDF解析器。基于AI的技术,尤其在处理像SEC Q10这样的复杂文件时表现出色,这些文件通常包含文本、数字及其组合构成的表格&…...

platform设备注册驱动模块的测试

一. 简介 上一篇文章编写了 platform设备注册代码,文章地址如下: 无设备树platform驱动实验:platform设备注册代码实现-CSDN博客 本文继续无设备树platform驱动实验,本文对编译好的 设备注册程序进行测试,测试所实…...

鸿蒙Harmony应用开发—ArkTS声明式开发(容器组件:ListItemGroup)

该组件用来展示列表item分组,宽度默认充满List组件,必须配合List组件来使用。 说明: 该组件从API Version 9开始支持。后续版本如有新增内容,则采用上角标单独标记该内容的起始版本。该组件的父组件只能是List。 使用说明 当List…...

Docker:常用命令

文章目录 docker作用常用指令 docker 作用 Docker 是一种容器化平台,可以让开发者打包应用程序及其依赖项,并以容器的形式进行发布、交付和运行。 Docker 的一些主要作用: 应用程序隔离:Docker 使用容器技术,将应用程…...

如何搭建“Docker Registry私有仓库,在CentOS7”?

1、下载镜像Docker Registry docker pull registry:2.7.1 2、运行私有库Registry docker run -d -p 5000:5000 -v ${PWD}/registry:/var/lib/registry --restartalways --name registry registry:2.7.1 3、拉取镜像 docker pull busybox 4、打标签,修改IP&#x…...

DBA面试题:MySQL缓存池LRU算法做了哪些改进?

下图是MySQL(MySQL5.7版本)体系架构图 MySQL的InnoDb Buffer Pool 缓冲池是主内存中的一个区域,用来缓存InnoDB在访问表和索引时的数据。对于频繁使用的数据可以直接从内存中访问,从而加快处理速度。如果一台服务器专用作MySQL数据…...

idea+vim+pycharm的块选择快捷键

平时开发的时候,有的时候我们想用矩形框住代码,或者想在某列上插入相同字符 例如下图所示,我想在22-24行的前面插入0000 1. Idea的快捷键:option 鼠标 2. Pycharm的快捷键:shift option 鼠标 2. Vim 块选择 v/V/c…...

ansible 部署FATE集群单边场景

官方文档: https://github.com/FederatedAI/AnsibleFATE/blob/main/docs/ansible_deploy_FATE_manual.md https://github.com/FederatedAI/AnsibleFATE/blob/main/docs/ansible_deploy_two_sides.md gitee详细文档: docs/ansible_deploy_one_side.md…...

融入Facebook的世界:探索数字化社交的魅力

融入Facebook的世界,是一场数字化社交的奇妙之旅。在这个广袤的虚拟社交空间中,人们可以尽情展现自己、分享生活,与全球朋友、家人和同事保持紧密联系,共同探索社交互动的乐趣与魅力。让我们深入了解这个世界的魅力所在&#xff1…...

stm32-定时器输出比较PWM

目录 一、输出比较简介 二、PWM简介 三、输出比较模式实现 1.输出比较框图(以通用定时器为例) 2.PWM基本结构 四、固件库实现 1.程序1:PWM呼吸灯 2.程序2:PWM驱动直流电机 3.程序3:控制舵机 一、输出比较简介 死区生成和互补输出一般…...

Redis对过期key的删除策略

假设设置了一批 key 只能存活 1 个小时,那么 1 小时后,redis 是怎么对这批 key 进行删除的? 定期删除 惰性删除 定期删除: redis是默认每隔100ms就随机抽取一些设置了过期时间的key,检查是否过期,如果过期就删除。…...

http的body格式

body数据都通常放在 HTTP 请求的 body 部分。 在 HTTP 请求中,Content-Type 头用于指示 body 中的数据格式。例如,对于 x-www-form-urlencoded 格式的数据,通常会设置 Content-Type: application/x-www-form-urlencoded,而对于 fo…...

Java Web开发从0到1

文章目录 总纲第1章 Java Web应用开发概述1.1 程序开发体系结构1.1.1 C/S体系结构介绍1.1.2 B/S体系结构介绍1.1.3 两种体系结构的比较1.2 Web应用程序的工作原理1.3 Web应用技术1.3.1 客服端应用技术1.3.2 服务端应用技术1.4 Java Web应用的开发环境变量1.5 Tomcat的安装与配置…...

002——编译鸿蒙(Liteos -a)

目录 一、鸿蒙是什么 二、Kconfig 2.1 概述 2.2 编译器 2.3 make使用 本文章引用了很多韦东山老师的教程内容,算是我学习过程中的笔记吧。如果侵权请联系我。 一、鸿蒙是什么 这里我补充一下对鸿蒙的描述 这张图片是鸿蒙发布时使用的,鸿蒙是一个很…...

Ansible--详解

目录 一、Ansible核心组件 二、Ansible配置 1.配置案例 (1)管理安装ansible (2)管理机分发公匙 (3)配置管理 (4)测试连接 2.命令说明 三、playbook剧本编写 1.playbook模板…...

Django和Mysql数据库

Django学习笔记 Django和Mysql数据库 Django开发操作数据库更简单,内部提供了ORM框架。 1)安装mysqlclient pip3 install mysqlclient2)ORM ORM可以帮助我们做两件事: 1.创建、修改、修改数据库中的表(不用写sql语句)[不能创…...

[蓝桥杯]-最大的通过数-CPP-二分查找、前缀和

目录 一、题目描述: 二、整体思路: 三、代码: 一、题目描述: 二、整体思路: 首先要知道不是他们同时选择序号一样的关卡通关,而是两人同时进行两个入口闯关。就是说两条通道存在相同关卡编号的的关卡被通…...

安卓UI面试题 26-30

26. Window和DecorView是什么?DecorView又是如何和Window建立联系的?Window是 WindowManager 最顶层的视图,它负责背景(窗口背景)、Title之类的标准的UI元素, Window是一个抽 象类,整个Android系统中, PhoneWindow是 Window的唯一实现类。 至于 DecorView,它是一个顶级 …...

CPU、GPU、IPU、NPU、TPU、LPU、MCU、MPU、SOC、DSP、FPGA、ASIC、GPP、ECU、

CPU: 中央处理器(Central Processing Unit)是一块超大规模的集成电路,是一台计算机的运算核心(Core)和控制核心( Control Unit)。 它的功能主要是解释计算机指令以及处理计算机软件…...

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

【kafka】Golang实现分布式Masscan任务调度系统

要求: 输出两个程序,一个命令行程序(命令行参数用flag)和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽,然后将消息推送到kafka里面。 服务端程序: 从kafka消费者接收…...

利用ngx_stream_return_module构建简易 TCP/UDP 响应网关

一、模块概述 ngx_stream_return_module 提供了一个极简的指令&#xff1a; return <value>;在收到客户端连接后&#xff0c;立即将 <value> 写回并关闭连接。<value> 支持内嵌文本和内置变量&#xff08;如 $time_iso8601、$remote_addr 等&#xff09;&a…...

逻辑回归:给不确定性划界的分类大师

想象你是一名医生。面对患者的检查报告&#xff08;肿瘤大小、血液指标&#xff09;&#xff0c;你需要做出一个**决定性判断**&#xff1a;恶性还是良性&#xff1f;这种“非黑即白”的抉择&#xff0c;正是**逻辑回归&#xff08;Logistic Regression&#xff09;** 的战场&a…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...

go 里面的指针

指针 在 Go 中&#xff0c;指针&#xff08;pointer&#xff09;是一个变量的内存地址&#xff0c;就像 C 语言那样&#xff1a; a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10&#xff0c;通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...

若依登录用户名和密码加密

/*** 获取公钥&#xff1a;前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...

Python常用模块:time、os、shutil与flask初探

一、Flask初探 & PyCharm终端配置 目的: 快速搭建小型Web服务器以提供数据。 工具: 第三方Web框架 Flask (需 pip install flask 安装)。 安装 Flask: 建议: 使用 PyCharm 内置的 Terminal (模拟命令行) 进行安装,避免频繁切换。 PyCharm Terminal 配置建议: 打开 Py…...

游戏开发中常见的战斗数值英文缩写对照表

游戏开发中常见的战斗数值英文缩写对照表 基础属性&#xff08;Basic Attributes&#xff09; 缩写英文全称中文释义常见使用场景HPHit Points / Health Points生命值角色生存状态MPMana Points / Magic Points魔法值技能释放资源SPStamina Points体力值动作消耗资源APAction…...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...