模型部署 - onnx的导出和分析 - onnx 的架构和 onnx helper 的使用 - 学习记录

news2024/11/13 8:00:11

onnx 的架构和 onnx helper 的使用

  • 简介
  • 一、onnx 的架构
  • 二、onnx 实践
    • 2.1、 create - linear.onnx
      • 2.1.1、要点一:创建节点
      • 2.1.2、要点二:创建张量
      • 2.1.3、要点三:创建图
    • 2.2、 create - onnx.convnet
    • 2.3、使用 onnx helper 导出的基本流程总结
  • 三、parse onnx
    • 3.1、案例一
    • 3.2、案例二(带有权重的)

简介

首先来了解一下 学习 onnx 架构和 onnx helper 的使用的目的。

一般模型部署的流程是 前处理+模型推理+后处理,然后c++ 上进行推理。

  • 前处理(与tensorRT无关)
  • 模型推理(生成的engine交给tensorRT去做前向传播)
  • 后处理(与tensorRT无关)

模型推理部分是指利用 tensorRT 的 onnx 解析器编译生成 engine (即转换为tensorRT能看懂的模型)。

1、有些时候我们应该把后处理部分在onnx模型中实现,降低后处理复杂度。
比如说yolov5的后处理中,要借助anchor要做一些乘加的操作,如果我们单独分开在后处理中去做的话,你就会发现你既要准备一个模型,还得专门储存这个模型的anchor的信息,这样代码的复杂度就很高,后处理的逻辑就会非常麻烦。所以把后处理的逻辑尽量得放在模型里面,使得它的tensor很简单通过decode就能实现。然后自己做的后处理性能可能还不够高,如果放到onnx里,tensorRT顺便还能帮你加速一下。

很多时候我们onnx已经导出来了,如果我还想去实现onnx后处理的增加,该怎么做呢? 有两种做法,一种是直接用onnx这个包去操作onnx文件,去增加一些节点是没有问题的,但这个难度系数比较高。第二种做法是可以用pytorch去实现后处理逻辑的代码,把这个后处理专门导出一个onnx,然后再把这个onnx合并到原来的onnx上,这也是实际上我们针对复杂任务专门定制的一个做法。

2、还有些时候我们无法直接用pytorch的export_onnx函数导出onnx,这个时候就要自己构建一个onnx 了。
比如 bevfusion的 spconv 部分,利用 onnx.helper() 自己构建一个onnx,然后再转 tensorrt

这些场景都需要自己理解、解析和构建 onnx。

一、onnx 的架构

首先我们来理解一下 onnx 的架构:ONNX是一种神经网络的格式,采用Protobuf (Protocal Buffer。是Google提出来的一套表示和序列化数据的机制) 二进制形式进行序列化模型。Protobuf会根据用于定义的数据结构来进行序列化存储。我们可以根据官方提供的数据结构信息,去修改或者创建onnx。onnx的各类proto的定义需要看官方文档 (https://github.com/onnx/onnx/tree/main)。这里面的onnx/onnx.in.proto定义了所有onnx的Proto 。

大概 总结 onnx 中的组织结构 如下:

  • ModelProto (描述的是整个模型的信息)
    — GraphProto (描述的是整个网络的信息)
    ------ NodeProto (描述的是各个计算节点,比如conv, linear)
    ------ TensorProto (描述的是tensor的信息,主要包括权重)
    ------ ValueInfoProto (描述的是input/output信息)
    ------ AttributeProto (描述的是node节点的各种属性信息)

下面我们根据总结的组织结构信息,来实践创建几个 onnx 。

二、onnx 实践

2.1、 create - linear.onnx

总体程序如下:

import onnx
from onnx import helper
from onnx import TensorProto

def create_onnx():
    # 创建ValueProto
    a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])
    x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])
    b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])
    y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 10])

    # 创建NodeProto
    mul = helper.make_node('Mul', ['a', 'x'], 'c', "multiply")
    add = helper.make_node('Add', ['c', 'b'], 'y', "add")

    # 构建GraphProto
    graph = helper.make_graph([mul, add], 'sample-linear', [a, x, b], [y])

    # 构建ModelProto
    model = helper.make_model(graph)

    # 检查model是否有错误
    onnx.checker.check_model(model)
    # print(model)

    # 保存model
    onnx.save(model, "sample-linear.onnx")

    return model

if __name__ == "__main__":
    model = create_onnx()

程序执行完会出现一个 sample-linear.onnx ,节点图如下。下面我们来看看程序里面的细节。
在这里插入图片描述)

2.1.1、要点一:创建节点

使用 helper.make_node 创建节点 (图中的黑色部分 'Mul','Add'

我们用下面两句话创建两个节点。

 # 创建NodeProto
    mul = helper.make_node('Mul', ['a', 'x'], 'c', "multiply")
    add = helper.make_node('Add', ['c', 'b'], 'y', "add")

函数对应参数解释:

在这里插入图片描述

op_type :The name of the operator to construct(要构造的运算符的名称)

这里填入的是onnx支持的算子的名称。这个地方是不可以乱写的,比如 不能将 ‘Mul’ 写成 ‘Mul2’,具体的参数查阅在 https://github.com/onnx/onnx/blob/main/docs/Operators.md

在这里插入图片描述

inputs :list of input names(节点输入名称),比如这里 Mul 的输入名字 是 ['a', 'x'] 两个
outputs : list of output names(节点输出名称)比如这里 Mul 的输出名字 是'c'一个
name : optional unique identifier for NodeProto(NodeProto的可选唯一标识符)

NodeProto 总结解释:

onnx中的 NodeProto,一般用来定义一个计算节点,比如conv, linear。
(input是repeated类型,意味着是数组)
(output是repeated类型,意味着是数组)
(attribute有一个自己的Proto)
(op_type需要严格根据onnx所提供的Operators写)

2.1.2、要点二:创建张量

helper.make_tensor, helper.make_value_info

一般用来定义网络的 input/output (会根据input/output的type来附加属性)

    a = helper.make_tensor_value_info('a', TensorProto.FLOAT, [10, 10])
    x = helper.make_tensor_value_info('x', TensorProto.FLOAT, [10, 10])
    b = helper.make_tensor_value_info('b', TensorProto.FLOAT, [10, 10])
    y = helper.make_tensor_value_info('y', TensorProto.FLOAT, [10, 10])

2.1.3、要点三:创建图

helper.make_graph

   # 构建GraphProto
    graph = helper.make_graph([mul, add], 'sample-linear', [a, x, b], [y])

nodes: list of NodeProto
name (string): graph name
inputs: list of ValueInfoProto
outputs: list of ValueInfoProto

GraphProto 总结解释:

一般用来定义一个网络。包括
• input/output
• initializer (在onnx中一般表示权重信息)
• node
(node是repeated,所以是数组)
(initializer是repeated,所以是数组)
(input/output是repeated,所以是数组)

2.2、 create - onnx.convnet

import numpy as np
import onnx
from onnx import numpy_helper


def create_initializer_tensor(
        name: str,
        tensor_array: np.ndarray,
        data_type: onnx.TensorProto = onnx.TensorProto.FLOAT
) -> onnx.TensorProto:

    initializer = onnx.helper.make_tensor(
        name      = name,
        data_type = data_type,
        dims      = tensor_array.shape,
        vals      = tensor_array.flatten().tolist())

    return initializer


def main():
    
    input_batch    = 1;
    input_channel  = 3;
    input_height   = 64;
    input_width    = 64;
    output_channel = 16;

    input_shape    = [input_batch, input_channel, input_height, input_width]
    output_shape   = [input_batch, output_channel, 1, 1]

    ##########################创建input/output################################
    model_input_name  = "input0"
    model_output_name = "output0"

    input = onnx.helper.make_tensor_value_info(
            model_input_name,
            onnx.TensorProto.FLOAT,
            input_shape)

    output = onnx.helper.make_tensor_value_info(
            model_output_name, 
            onnx.TensorProto.FLOAT, 
            output_shape)
    

    ##########################创建第一个conv节点##############################
    conv1_output_name = "conv2d_1.output"
    conv1_in_ch       = input_channel
    conv1_out_ch      = 32
    conv1_kernel      = 3
    conv1_pads        = 1

    # 创建conv节点的权重信息
    conv1_weight    = np.random.rand(conv1_out_ch, conv1_in_ch, conv1_kernel, conv1_kernel)
    conv1_bias      = np.random.rand(conv1_out_ch)

    conv1_weight_name = "conv2d_1.weight"
    conv1_weight_initializer = create_initializer_tensor(
        name         = conv1_weight_name,
        tensor_array = conv1_weight,
        data_type    = onnx.TensorProto.FLOAT)


    conv1_bias_name  = "conv2d_1.bias"
    conv1_bias_initializer = create_initializer_tensor(
        name         = conv1_bias_name,
        tensor_array = conv1_bias,
        data_type    = onnx.TensorProto.FLOAT)

    # 创建conv节点,注意conv节点的输入有3个: input, w, b
    conv1_node = onnx.helper.make_node(
        name         = "conv2d_1",
        op_type      = "Conv",
        inputs       = [
            model_input_name, 
            conv1_weight_name,
            conv1_bias_name
        ],
        outputs      = [conv1_output_name],
        kernel_shape = [conv1_kernel, conv1_kernel],
        pads         = [conv1_pads, conv1_pads, conv1_pads, conv1_pads],
    )

    ##########################创建一个BatchNorm节点###########################
    bn1_output_name = "batchNorm1.output"

    # 为BN节点添加权重信息
    bn1_scale = np.random.rand(conv1_out_ch)
    bn1_bias  = np.random.rand(conv1_out_ch)
    bn1_mean  = np.random.rand(conv1_out_ch)
    bn1_var   = np.random.rand(conv1_out_ch)

    # 通过create_initializer_tensor创建权重,方法和创建conv节点一样
    bn1_scale_name = "batchNorm1.scale"
    bn1_bias_name  = "batchNorm1.bias"
    bn1_mean_name  = "batchNorm1.mean"
    bn1_var_name   = "batchNorm1.var"

    bn1_scale_initializer = create_initializer_tensor(
        name         = bn1_scale_name,
        tensor_array = bn1_scale,
        data_type    = onnx.TensorProto.FLOAT)
    bn1_bias_initializer = create_initializer_tensor(
        name         = bn1_bias_name,
        tensor_array = bn1_bias,
        data_type    = onnx.TensorProto.FLOAT)
    bn1_mean_initializer = create_initializer_tensor(
        name         = bn1_mean_name,
        tensor_array = bn1_mean,
        data_type    = onnx.TensorProto.FLOAT)
    bn1_var_initializer  = create_initializer_tensor(
        name         = bn1_var_name,
        tensor_array = bn1_var,
        data_type    = onnx.TensorProto.FLOAT)

    # 创建BN节点,注意BN节点的输入信息有5个: input, scale, bias, mean, var
    bn1_node = onnx.helper.make_node(
        name    = "batchNorm1",
        op_type = "BatchNormalization",
        inputs  = [
            conv1_output_name,
            bn1_scale_name,
            bn1_bias_name,
            bn1_mean_name,
            bn1_var_name
        ],
        outputs=[bn1_output_name],
    )

    ##########################创建一个ReLU节点###########################
    relu1_output_name = "relu1.output"

    # 创建ReLU节点,ReLU不需要权重,所以直接make_node就好了
    relu1_node = onnx.helper.make_node(
        name    = "relu1",
        op_type = "Relu",
        inputs  = [bn1_output_name],
        outputs = [relu1_output_name],
    )

    ##########################创建一个AveragePool节点####################
    avg_pool1_output_name = "avg_pool1.output"

    # 创建AvgPool节点,AvgPool不需要权重,所以直接make_node就好了
    avg_pool1_node = onnx.helper.make_node(
        name    = "avg_pool1",
        op_type = "GlobalAveragePool",
        inputs  = [relu1_output_name],
        outputs = [avg_pool1_output_name],
    )

    ##########################创建第二个conv节点##############################

    # 创建conv节点的属性
    conv2_in_ch  = conv1_out_ch
    conv2_out_ch = output_channel
    conv2_kernel = 1
    conv2_pads   = 0

    # 创建conv节点的权重信息
    conv2_weight    = np.random.rand(conv2_out_ch, conv2_in_ch, conv2_kernel, conv2_kernel)
    conv2_bias      = np.random.rand(conv2_out_ch)
    
    conv2_weight_name = "conv2d_2.weight"
    conv2_weight_initializer = create_initializer_tensor(
        name         = conv2_weight_name,
        tensor_array = conv2_weight,
        data_type    = onnx.TensorProto.FLOAT)

    conv2_bias_name  = "conv2d_2.bias"
    conv2_bias_initializer = create_initializer_tensor(
        name         = conv2_bias_name,
        tensor_array = conv2_bias,
        data_type    = onnx.TensorProto.FLOAT)

    # 创建conv节点,注意conv节点的输入有3个: input, w, b
    conv2_node = onnx.helper.make_node(
        name         = "conv2d_2",
        op_type      = "Conv",
        inputs       = [
            avg_pool1_output_name,
            conv2_weight_name,
            conv2_bias_name
        ],
        outputs      = [model_output_name],
        kernel_shape = [conv2_kernel, conv2_kernel],
        pads         = [conv2_pads, conv2_pads, conv2_pads, conv2_pads],
    )

    ##########################创建graph##############################
    graph = onnx.helper.make_graph(
        name    = "sample-convnet",
        inputs  = [input],
        outputs = [output],
        nodes   = [
            conv1_node, 
            bn1_node, 
            relu1_node, 
            avg_pool1_node, 
            conv2_node],
        initializer =[
            conv1_weight_initializer, 
            conv1_bias_initializer,
            bn1_scale_initializer, 
            bn1_bias_initializer,
            bn1_mean_initializer, 
            bn1_var_initializer,
            conv2_weight_initializer, 
            conv2_bias_initializer
        ],
    )

    ##########################创建model##############################
    model = onnx.helper.make_model(graph, producer_name="onnx-sample")
    model.opset_import[0].version = 12
    
    ##########################验证&保存model##############################
    model = onnx.shape_inference.infer_shapes(model)
    onnx.checker.check_model(model)
    print("Congratulations!! Succeed in creating {}.onnx".format(graph.name))
    onnx.save(model, "sample-convnet.onnx")


# 使用onnx.helper创建一个最基本的ConvNet
#         input (ch=3, h=64, w=64)
#           |
#          Conv (in_ch=3, out_ch=32, kernel=3, pads=1)
#           |
#        BatchNorm
#           |
#          ReLU
#           |
#         AvgPool
#           |
#          Conv (in_ch=32, out_ch=10, kernel=1, pads=0)
#           |
#         output (ch=10, h=1, w=1)

if __name__ == "__main__":
    main()

与案例1不同的地方在


def create_initializer_tensor(
        name: str,
        tensor_array: np.ndarray,
        data_type: onnx.TensorProto = onnx.TensorProto.FLOAT
) -> onnx.TensorProto:

    initializer = onnx.helper.make_tensor(
        name      = name,
        data_type = data_type,
        dims      = tensor_array.shape,
        vals      = tensor_array.flatten().tolist())

    return initializer

这里使用了 onnx.helper.make_tensor()

2.3、使用 onnx helper 导出的基本流程总结

  1. helper.make_node
  2. helper.make_tensor
  3. helper.make_value_info
  4. helper.make_graph
  5. helper.make_operatorsetid
  6. helper.make_model
  7. onnx.save_model

参考链接

三、parse onnx

下面的案例展示如何使用 python 把 onnx 打印出来

3.1、案例一

import onnx

def main(): 

    model = onnx.load("sample-linear.onnx")
    onnx.checker.check_model(model)

    graph        = model.graph
    nodes        = graph.node
    inputs       = graph.input
    outputs      = graph.output

    print("\n**************parse input/output*****************")
    for input in inputs:
        input_shape = []
        for d in input.type.tensor_type.shape.dim:
            if d.dim_value == 0:
                input_shape.append(None)
            else:
                input_shape.append(d.dim_value)
        print("Input info: \
                \n\tname:      {} \
                \n\tdata Type: {} \
                \n\tshape:     {}".format(input.name, input.type.tensor_type.elem_type, input_shape))

    for output in outputs:
        output_shape = []
        for d in output.type.tensor_type.shape.dim:
            if d.dim_value == 0:
                output_shape.append(None)
            else:
                output_shape.append(d.dim_value)
        print("Output info: \
                \n\tname:      {} \
                \n\tdata Type: {} \
                \n\tshape:     {}".format(input.name, output.type.tensor_type.elem_type, input_shape))

    print("\n**************parse node************************")
    for node in nodes:
        print("node info: \
                \n\tname:      {} \
                \n\top_type:   {} \
                \n\tinputs:    {} \
                \n\toutputs:   {}".format(node.name, node.op_type, node.input, node.output))


if __name__ == "__main__":
    main()


3.2、案例二(带有权重的)

这里有两个 py 文件

parser.py

import onnx
import numpy as np

# 注意,因为weight是以字节的形式存储的,所以要想读,需要转变为float类型
def read_weight(initializer: onnx.TensorProto):
    shape = initializer.dims
    data  = np.frombuffer(initializer.raw_data, dtype=np.float32).reshape(shape)
    print("\n**************parse weight data******************")
    print("initializer info: \
            \n\tname:      {} \
            \n\tdata:    \n{}".format(initializer.name, data))
    

def parse_onnx(model: onnx.ModelProto):
    graph        = model.graph
    initializers = graph.initializer
    nodes        = graph.node
    inputs       = graph.input
    outputs      = graph.output

    print("\n**************parse input/output*****************")
    for input in inputs:
        input_shape = []
        for d in input.type.tensor_type.shape.dim:
            if d.dim_value == 0:
                input_shape.append(None)
            else:
                input_shape.append(d.dim_value)
        print("Input info: \
                \n\tname:      {} \
                \n\tdata Type: {} \
                \n\tshape:     {}".format(input.name, input.type.tensor_type.elem_type, input_shape))

    for output in outputs:
        output_shape = []
        for d in output.type.tensor_type.shape.dim:
            if d.dim_value == 0:
                output_shape.append(None)
            else:
                output_shape.append(d.dim_value)
        print("Output info: \
                \n\tname:      {} \
                \n\tdata Type: {} \
                \n\tshape:     {}".format(input.name, output.type.tensor_type.elem_type, input_shape))

    print("\n**************parse node************************")
    for node in nodes:
        print("node info: \
                \n\tname:      {} \
                \n\top_type:   {} \
                \n\tinputs:    {} \
                \n\toutputs:   {}".format(node.name, node.op_type, node.input, node.output))

    print("\n**************parse initializer*****************")
    for initializer in initializers:
        print("initializer info: \
                \n\tname:      {} \
                \n\tdata_type: {} \
                \n\tshape:     {}".format(initializer.name, initializer.data_type, initializer.dims))


parse_onnx_cbr.py

import torch
import torch.nn as nn
import torch.onnx
import onnx
from parser import parse_onnx
from parser import read_weight

class Model(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3)
        self.bn1   = nn.BatchNorm2d(num_features=16)
        self.act1  = nn.LeakyReLU()
    
    def forward(self, x):
        x = self.conv1(x)
        x = self.bn1(x)
        x = self.act1(x)
        return x


def export_norm_onnx():
    input   = torch.rand(1, 3, 5, 5)
    model   = Model()
    model.eval()

    file    = "sample-cbr.onnx"
    torch.onnx.export(
        model         = model, 
        args          = (input,),
        f             = file,
        input_names   = ["input0"],
        output_names  = ["output0"],
        opset_version = 15)
    print("Finished normal onnx export")

def main():
    export_norm_onnx()
    model = onnx.load_model("sample-cbr.onnx")
    parse_onnx(model)

    initializers = model.graph.initializer
    for item in initializers:
        read_weight(item)


if __name__ == "__main__":
    main()

sample-cbr.onnx 模型下载地址

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Web框架开发-Django模型层(数据库操作)

一、ORM介绍 MVC或者MVC框架中包括一个重要的部分,就是ORM,它实现了数据模型与数据库的解耦,即数据模型的设计不需要依赖于特定的数据库,通过简单的配置就可以轻松更换数据库,这极大的减轻了开发人员的工作量,不需要面对因数据库变更而导致的无效劳动ORM是“对象-关系-映…

g++在windows下使用C++进程库无法传入参数<求助>

如题: windows11使用g的时候,想使用下线程库。但是就发现了如题的问题。在使用时,不传入参数时不会报错的,但是传入参数之后就产生了报错。 点击进入定义发现头文件定义明明是正确的。 具体报错如下图。

流畅的 Python 第二版(GPT 重译)(二)

第三章:字典和集合 Python 基本上是用大量语法糖包装的字典。 Lalo Martins,早期数字游牧民和 Pythonista 我们在所有的 Python 程序中都使用字典。即使不是直接在我们的代码中,也是间接的,因为dict类型是 Python 实现的基本部分。…

绝地求生:PUBG延长GPU崩溃时间新方法

相信大家都在被GPU游戏崩溃苦恼已久,PUBG这个游戏崩溃,跟超频是没有多大关系的,只要超频TM5过测,YC过测,或者双烤过测,就没问题。主要是这个游戏的优化不行,特别40系显卡,对内存条也…

申请双软认证需要哪些材料?软件功能测试报告怎么获取?

“双软认证”是指软件产品评估和软件企业评估,其中需要软件测试报告。 企业申请双软认证除了获得软件企业和软件产品的认证资质,同时也是对企业知识产权的一种保护方式,更可以让企业享受国家提供给软件行业的税收优惠政策。 那么,…

PyTorch 深度学习(GPT 重译)(二)

四、使用张量表示真实世界数据 本章内容包括 将现实世界的数据表示为 PyTorch 张量 处理各种数据类型 从文件加载数据 将数据转换为张量 塑造张量,使其可以作为神经网络模型的输入 在上一章中,我们了解到张量是 PyTorch 中数据的构建块。神经网络…

SQLiteC/C++接口详细介绍sqlite3_stmt类(二)

返回目录:SQLite—免费开源数据库系列文章目录 上一篇:SQLiteC/C接口详细介绍sqlite3_stmt类简介 下一篇:SQLiteC/C接口详细介绍sqlite3_stmt类(三) sqlite3_reset() 功能:重置一个准备好执行的SQL语…

机器人路径规划:基于冠豪猪优化算法(Crested Porcupine Optimizer,CPO)的机器人路径规划(提供MATLAB代码)

一、机器人路径规划介绍 移动机器人(Mobile robot,MR)的路径规划是 移动机器人研究的重要分支之,是对其进行控制的基础。根据环境信息的已知程度不同,路径规划分为基于环境信息已知的全局路径规划和基于环境信息未知或…

【LeetCode: 224. 基本计算器 + 模拟 + 栈】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

【原创】三十分钟实时数据可视化网站前后端教程 Scrapy + Django + React 保姆级教程向

这个本来是想做视频的,所以是以讲稿的形式写的。最后没做视频,但是觉得这篇文还是值得记录一下。真的要多记录,不然一些不常用的东西即使做过几个月又有点陌生了。 文章目录 爬虫 SCRAPYxpath 后端 DJANGO前端 REACT Hello大家好这里是小鱼&a…

sheng的学习笔记-AI-Inception network

目录:sheng的学习笔记-AI目录-CSDN博客 基础知识 构建卷积层时,你要决定过滤器的大小究竟是11(原来是13,猜测为口误),33还是55,或者要不要添加池化层。而Inception网络的作用就是代替你来决定&…

kubesphere all in one部署Jenkins提示1 Insufficient cpu

原因 devops 至少一个cpu(1000m),但是其他资源已经占用了很多cpu CPU 资源以 CPU 单位度量。Kubernetes 中的一个 CPU 等同于: 1 个 AWS vCPU 1 个 GCP核心 1 个 Azure vCore 裸机上具有超线程能力的英特尔处理器上的 1 个超线程…

编译libcurl with openssl + zlib (gzip)

libcurl 编译说明 libcurl 正常不依赖第三方库也可以进行编译使用,但是只能访问不带ssl通道的http,不能访问https,而且不支持gzip 一般现在常用的https中的ssl是使用openssl、gzip使用zlib 下面是如何编译libcurl,我们在项目中使用的是第二种…

ubuntu部署wireguard服务端,ubuntu部署wireguard客户端

docker部署方式 docker run -d \--namewg-easy \-e WG_HOST6.6.6.6服务端IP \-e PASSWORD123abc登陆管理密码 \-e WG_DEFAULT_ADDRESS10.0.8.x客户端 IP 地址范围 \-e WG_DEFAULT_DNS1.1.1.1配置dns \-e WG_ALLOWED_IPS10.0.8.0/24 \-e WG_PERSISTENT_KEEPALIVE25 \-v ~/.wg-e…

CAPL如何实现TCP Packet的option字段

在TCP协议中,主机可以根据自身的需要决定TCP通信时是否携带option字段,来扩展TCP功能。option字段属于TCP首部的扩展部分,且是可选项,TCP根据首部中的offset字段值确定TCP报文是否携带option字段。 TCP首部固定的部分有20个字节,如果没有扩展部分(option字段),20个字节…

【Qt】使用Qt实现Web服务器(四):传递参数、表单提交和获取请求参数

1、示例 1)演示 2)提交 3)显示 2、源码 1)示例源码Demo1->FormController void FormController::service(HttpRequest& request, HttpResponse& response) {

在 3D 虚拟城市中展示自定义建筑

在本教程中,您将学习如何创建 Cesium 应用程序,用您自己的 3D 模型替换真实城市中的建筑物。您可以使用它来可视化拟建建筑的影响,及如何改变天际线?从特定楼层或房间看到的景色会是什么样子? 我们将介绍如何&#xf…

动态规划(算法竞赛、蓝桥杯)--斜率优化DP打印文章

1、B站视频链接&#xff1a;E51【模板】斜率优化DP 打印文章_哔哩哔哩_bilibili 题目链接&#xff1a;Problem - 3507 #include <bits/stdc.h> using namespace std; typedef long long LL; const int N500010; int n,m,q[N]; LL s[N],f[N];double sl(int i,int j){ret…

Qt笔记 事件处理_鼠标事件

什么是事件&#xff1f; 点击鼠标左键&#xff0c;双击鼠标左键&#xff0c;鼠标来回移动&#xff0c;按下键盘按钮&#xff0c;这些都是事件。 那么事件的响应机制是什么样的呢&#xff1f; 首先main函数中有一个QApplication&#xff0c;其作用是创建一个应用程序对象&…

web自动化3-pytest前后夹具

一、pytest前后置&#xff08;夹具&#xff09;-fixture 夹具的作用&#xff1a;在用例执行之前和之后&#xff0c;需要做的准备工作之前和收尾工作。 用于固定测试环境&#xff0c;以及清理回收资源。 举个例子&#xff1a;访问一个被测页面-登录页面&#xff0c;执行测试用…