Intel® QAT 加速卡之IPSec示例
Intel QAT 加速卡之IPSec示例
在IPSec的使用过程中需要频繁的加解密操作,而加解密操作会极大的消耗CPU的资源。因此很多提供IPSec服务的设备厂商尝试用多种方式来提高加解密性能,从而缓解CPU的压力,提高设备的IPSec的性能和吞吐量。其中既有软件加速方式,也有硬件加速方式。而Intel QAT 加速卡便是之中的一款,这是Intel推出的硬件加速设备(Intel也推出过很多其他牛逼的加速设备)。使用专门的硬件加解密卡,从而将CPU解放出来处理其他的任务,这便是加速卡的基本思路。
下面我们就在IPSec中如何使用QAT加速技术流程做一个简单示例。(示例是人家Intel官方文档中提供的)
1. QAT处理IPSec入站报文
2. QAT处理IPSec出站报文
3. 组织架构
QAT相关的重要数据结构组织关系:
4. 示例源码
- 对于出站方向,此示例将使用对称API来执行链式密码和哈希操作。 它在CBC模式下使用高级加密标准(AES)算法对一些纯文本进行加密,然后对密文,初始化向量和ESP头部执行SHA1 HMAC操作,并在密文之后立即将完整性检查值(ICV)写入缓冲区。
- 对于入站方向,此示例将再次使用对称API来执行链式哈希和密码操作。 它对密文,初始化向量和ESP头部执行SHA1 HMAC操作,并将结果与输入的ICV进行比较,从而实现了完整性校验的功能。 然后,它在CBC模式下使用AES算法解密密文。
#include "cpa.h"
#include "cpa_cy_im.h"
#include "cpa_cy_sym.h"
#include "cpa_sample_utils.h"
#define TIMEOUT_MS 5000
#define ICV_LENGTH 12
/* For IPSec outbound direction we encrypt the payload and then
generate the ICV. For IPSec inbound direction we compare the
ICV and decrypt the payload
*/
#define IPSEC_OUTBOUND_DIR 0
#define IPSEC_INBOUND_DIR 1
extern int gDebugParam;
static Cpa8U sampleCipherKey[] = {
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
0x88, 0x99, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55
};
static Cpa8U sampleCipherIv[] = {
0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
0xde, 0xca, 0xf8, 0x88, 0x3d, 0x11, 0x59, 0x04
};
static Cpa8U sampleAuthKey[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF,
0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF,
0xDE, 0xAD, 0xBE, 0xEF
};
static Cpa8U sampleEspHdrData[] = {
0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x00, 0x05
};
/* Payload padded to a multiple of the cipher block size
(16), 2nd last byte gives pad length, last byte gives
next header info */
static Cpa8U samplePayload[] = {
0xd9, 0x31, 0x32, 0x25, 0xf8, 0x84, 0x06, 0xe5,
0xa5, 0x59, 0x09, 0xc5, 0xaf, 0xf5, 0x26, 0x9a,
0x86, 0xa7, 0xa9, 0x53, 0x15, 0x34, 0xf7, 0xda,
0x2e, 0x4c, 0x30, 0x3d, 0x8a, 0x31, 0x8a, 0x72,
0x1c, 0x3c, 0x0c, 0x95, 0x95, 0x68, 0x09, 0x53,
0x2f, 0xcf, 0x0e, 0x24, 0x49, 0xa6, 0xb5, 0x25,
0xb1, 0x6a, 0xed, 0xf5, 0xaa, 0x0d, 0xe6, 0x57,
0xba, 0x63, 0x7b, 0x39, 0x01, 0x02, 0x02, 0x61
};
static Cpa8U expectedOutput[] = {
/* ESP header unmodified */
0x00, 0x00, 0x01, 0x2c, 0x00, 0x00, 0x00, 0x05,
/* IV unmodified */
0xca, 0xfe, 0xba, 0xbe, 0xfa, 0xce, 0xdb, 0xad,
0xde, 0xca, 0xf8, 0x88, 0x3d, 0x11, 0x59, 0x04,
/* Ciphertext */
0x39, 0x8E, 0x4C, 0x1B, 0x7B, 0x28, 0x94, 0x52,
0x97, 0xAD, 0x95, 0x97, 0xD7, 0xF9, 0xB9, 0x4A,
0x49, 0x03, 0x51, 0x47, 0x45, 0xC7, 0x58, 0x6A,
0x9A, 0x48, 0xB6, 0x38, 0xB4, 0xD5, 0xEE, 0x42,
0x4F, 0x39, 0x09, 0x3D, 0xAB, 0x1E, 0xB3, 0x6A,
0x71, 0x0B, 0xFC, 0x80, 0xAD, 0x2E, 0x4C, 0xA5,
0xAB, 0x78, 0xB8, 0xAB, 0x87, 0xCC, 0x37, 0xF0,
0xB9, 0x61, 0xDC, 0xB1, 0xA7, 0x24, 0x26, 0x23,
/* ICV */
0xE6, 0x55, 0xBD, 0x90, 0x33, 0x2D, 0x04, 0x8C,
0x34, 0x06, 0xE3, 0x2D
};
CpaStatus
algChainSample(void);
/*
* Callback function
*
* This function is "called back" (invoked by the implementation of
* the API) when the asynchronous operation has completed. The
* context in which it is invoked depends on the implementation, but
* as described in the API it should not sleep (since it may be called
* in a context which does not permit sleeping, e.g. a Linux bottom
* half).
*
* This function can perform whatever processing is appropriate to the
* application. For example, it may free memory, continue processing
* of a decrypted packet, etc. In this example, the function checks
* verifyResult returned and sets the complete variable to indicate it
* has been called.
*/
static void
symCallback(void *pCallbackTag,
CpaStatus status,
const CpaCySymOp operationType,
void *pOpData,
CpaBufferList *pDstBuffer,
CpaBoolean verifyResult)
{
PRINT_DBG("Callback called with status = %d.\n", status);
/* For this implementation verifyResult is true by default. In
the digest generate case verifyDigest will never be false. In
the digest verify case verifyDigest can be false if digest
verification fails */
if(CPA_FALSE == verifyResult)
{
PRINT_ERR("Callback verify result error\n");
}
if (NULL != pCallbackTag)
{
/** indicate that the function has been called */
COMPLETE((struct COMPLETION_STRUCT *)pCallbackTag);
}
}
/*
* Perform an algorithm chaining operation
*/
static CpaStatus
algChainPerformOp(CpaInstanceHandle cyInstHandle, CpaCySymSessionCtx sessionCtx, int dir)
{
CpaStatus status = CPA_STATUS_SUCCESS;
Cpa8U *pBufferMeta = NULL;
Cpa32U bufferMetaSize = 0;
CpaBufferList *pBufferList = NULL;
CpaFlatBuffer *pFlatBuffer = NULL;
CpaCySymOpData *pOpData = NULL;
/* buffer size includes space for hdr, iv, payload and icv */
Cpa32U bufferSize = sizeof(sampleEspHdrData) + sizeof(sampleCipherIv)
+ sizeof(samplePayload) + ICV_LENGTH;
Cpa32U numBuffers = 1; /* only using 1 buffer in this case */
/* allocate memory for bufferlist and array of flat buffers in a contiguous
* area and carve it up to reduce number of memory allocations required. */
Cpa32U bufferListMemSize = sizeof(CpaBufferList) +
(numBuffers * sizeof(CpaFlatBuffer));
Cpa8U *pSrcBuffer = NULL;
Cpa8U *pIvBuffer = NULL;
/* The following variables are allocated on the stack because we block
* until the callback comes back. If a non-blocking approach was to be
* used then these variables should be dynamically allocated */
struct COMPLETION_STRUCT complete;
/* get meta information size */
status = cpaCyBufferListGetMetaSize( cyInstHandle,
numBuffers, &bufferMetaSize);
if (CPA_STATUS_SUCCESS == status)
{
status = PHYS_CONTIG_ALLOC(&pBufferMeta, bufferMetaSize);
}
if (CPA_STATUS_SUCCESS == status)
{
status = OS_MALLOC(&pBufferList, bufferListMemSize);
}
if (CPA_STATUS_SUCCESS == status)
{
status = PHYS_CONTIG_ALLOC(&pSrcBuffer, bufferSize);
}
if (CPA_STATUS_SUCCESS == status)
{
/* increment by sizeof(CpaBufferList) to get at the
* array of flatbuffers */
pFlatBuffer = (CpaFlatBuffer *) (pBufferList + 1);
pBufferList->pBuffers = pFlatBuffer;
pBufferList->numBuffers = 1;
pBufferList->pPrivateMetaData = pBufferMeta;
pFlatBuffer->dataLenInBytes = bufferSize;
pFlatBuffer->pData = pSrcBuffer;
/* copy source into buffer */
if(IPSEC_OUTBOUND_DIR == dir)
{
memcpy(pSrcBuffer, sampleEspHdrData, sizeof(sampleEspHdrData));
memcpy(pSrcBuffer+sizeof(sampleEspHdrData), sampleCipherIv, sizeof(sampleCipherIv));
memcpy(pSrcBuffer+(sizeof(sampleEspHdrData)+sizeof(sampleCipherIv)),
samplePayload, sizeof(samplePayload));
}
else
{
memcpy(pSrcBuffer, expectedOutput, sizeof(expectedOutput));
}
pIvBuffer = pSrcBuffer + sizeof(sampleEspHdrData);
status = OS_MALLOC(&pOpData, sizeof(CpaCySymOpData));
}
if (CPA_STATUS_SUCCESS == status)
{
if(IPSEC_OUTBOUND_DIR == dir)
{
//<snippet name="opDataIPSecOut">
/** Populate the structure containing the operational data that is
* needed to run the algorithm in outbound direction */
pOpData->sessionCtx = sessionCtx;
pOpData->packetType = CPA_CY_SYM_PACKET_TYPE_FULL;
pOpData->pIv = pIvBuffer;
pOpData->ivLenInBytes = sizeof(sampleCipherIv);
pOpData->cryptoStartSrcOffsetInBytes = sizeof(sampleEspHdrData)+sizeof(sampleCipherIv);
pOpData->messageLenToCipherInBytes = sizeof(samplePayload);
pOpData->hashStartSrcOffsetInBytes = 0;
pOpData->messageLenToHashInBytes = sizeof(sampleEspHdrData)
+sizeof(sampleCipherIv)+sizeof(samplePayload);
/* Even though ICV follows immediately after the region to hash
digestIsAppended is set to false in this case to workaround
errata number IXA00378322 */
pOpData->pDigestResult = pSrcBuffer+
(sizeof(sampleEspHdrData)+sizeof(sampleCipherIv)+sizeof(samplePayload));
//</snippet>
}
else
{
//<snippet name="opDataIPSecIn">
/** Populate the structure containing the operational data that is
* needed to run the algorithm in inbound direction */
pOpData->sessionCtx = sessionCtx;
pOpData->packetType = CPA_CY_SYM_PACKET_TYPE_FULL;
pOpData->pIv = pIvBuffer;
pOpData->ivLenInBytes = sizeof(sampleCipherIv);
pOpData->cryptoStartSrcOffsetInBytes = sizeof(sampleEspHdrData)+sizeof(sampleCipherIv);
pOpData->messageLenToCipherInBytes = bufferSize -
(sizeof(sampleEspHdrData)+sizeof(sampleCipherIv)+ICV_LENGTH);
pOpData->hashStartSrcOffsetInBytes = 0;
pOpData->messageLenToHashInBytes = bufferSize - ICV_LENGTH;
//</snippet>
}
}
if (CPA_STATUS_SUCCESS == status)
{
/** initialisation for callback; the "complete" variable is used by the
* callback function to indicate it has been called*/
COMPLETION_INIT(&complete);
PRINT_DBG("cpaCySymPerformOp\n");
/** Perform symmetric operation */
status = cpaCySymPerformOp(cyInstHandle,
(void *)&complete, /* data sent as is to the callback function*/
pOpData, /* operational data struct */
pBufferList, /* source buffer list */
pBufferList, /* same src & dst for an in-place operation*/
NULL); /* pVerifyResult not required in async mode */
if (CPA_STATUS_SUCCESS != status)
{
PRINT_ERR("cpaCySymPerformOp failed. (status = %d)\n", status);
}
if (CPA_STATUS_SUCCESS == status)
{
/** wait until the completion of the operation*/
if (!COMPLETION_WAIT(&complete, TIMEOUT_MS))
{
PRINT_ERR("timeout or interruption in cpaCySymPerformOp\n");
status = CPA_STATUS_FAIL;
}
}
if (CPA_STATUS_SUCCESS == status)
{
if(IPSEC_OUTBOUND_DIR == dir)
{
if (0 == memcmp(pSrcBuffer, expectedOutput, bufferSize))
{
PRINT_DBG("Output matches expected output encrypt generate\n");
}
else
{
PRINT_DBG("Output does not match expected output encrypt generate\n");
status = CPA_STATUS_FAIL;
}
}
else
{
if (0 == memcmp(pSrcBuffer+(sizeof(sampleEspHdrData)+sizeof(sampleCipherIv)),
samplePayload, sizeof(samplePayload)))
{
PRINT_DBG("Output matches expected output decrypt verify\n");
}
else
{
PRINT_DBG("Output does not match expected output decrypt verify\n");
status = CPA_STATUS_FAIL;
}
}
}
}
/* at this stage, the callback function has returned, so it is sure that
* the structures won't be needed any more*/
PHYS_CONTIG_FREE(pSrcBuffer);
OS_FREE(pBufferList);
PHYS_CONTIG_FREE(pBufferMeta);
OS_FREE(pOpData);
COMPLETION_DESTROY(&complete);
return status;
}
CpaStatus
algChainSample(void)
{
CpaStatus status = CPA_STATUS_FAIL;
CpaCySymSessionCtx sessionCtx = NULL;
Cpa32U sessionCtxSize = 0;
CpaInstanceHandle cyInstHandle = NULL;
CpaCySymSessionSetupData sessionSetupData = {0};
CpaCySymStats64 symStats = {0};
/*
* In this simplified version of instance discovery, we discover
* exactly one instance of a crypto service.
*/
sampleCyGetInstance(&cyInstHandle);
if (cyInstHandle == NULL)
{
PRINT_DBG("No crypto instances available\n");
return CPA_STATUS_FAIL;
}
/* Start Cryptographic component */
PRINT_DBG("cpaCyStartInstance\n");
status = cpaCyStartInstance(cyInstHandle);
if(CPA_STATUS_SUCCESS == status)
{
/*
* Set the address translation function for the instance
*/
status = cpaCySetAddressTranslation(cyInstHandle, sampleVirtToPhys);
}
if (CPA_STATUS_SUCCESS == status)
{
/*
* If the instance is polled start the polling thread. Note that
* how the polling is done is implementation-dependant.
*/
sampleCyStartPolling(cyInstHandle);
PRINT_DBG("Encrypt-Generate ICV\n");
/* populate symmetric session data structure */
sessionSetupData.sessionPriority = CPA_CY_PRIORITY_HIGH;
//<snippet name="initSessionIPSecEnc">
sessionSetupData.symOperation = CPA_CY_SYM_OP_ALGORITHM_CHAINING;
sessionSetupData.algChainOrder =
CPA_CY_SYM_ALG_CHAIN_ORDER_CIPHER_THEN_HASH;
sessionSetupData.cipherSetupData.cipherAlgorithm =
CPA_CY_SYM_CIPHER_AES_CBC;
sessionSetupData.cipherSetupData.pCipherKey = sampleCipherKey;
sessionSetupData.cipherSetupData.cipherKeyLenInBytes =
sizeof(sampleCipherKey);
sessionSetupData.cipherSetupData.cipherDirection =
CPA_CY_SYM_CIPHER_DIRECTION_ENCRYPT;
sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA1;
sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH;
sessionSetupData.hashSetupData.digestResultLenInBytes = ICV_LENGTH;
sessionSetupData.hashSetupData.authModeSetupData.authKey = sampleAuthKey;
sessionSetupData.hashSetupData.authModeSetupData.authKeyLenInBytes =
sizeof(sampleAuthKey);
/* Even though ICV follows immediately after the region to hash
digestIsAppended is set to false in this case to workaround
errata number IXA00378322 */
sessionSetupData.digestIsAppended = CPA_FALSE;
/* Generate the ICV in outbound direction */
sessionSetupData.verifyDigest = CPA_FALSE;
//</snippet>
/* Determine size of session context to allocate */
status = cpaCySymSessionCtxGetSize(cyInstHandle,
&sessionSetupData, &sessionCtxSize);
}
if (CPA_STATUS_SUCCESS == status)
{
/* Allocate session context */
status = PHYS_CONTIG_ALLOC(&sessionCtx, sessionCtxSize);
}
if (CPA_STATUS_SUCCESS == status)
{
/* Initialize the session */
status = cpaCySymInitSession(cyInstHandle,
symCallback, &sessionSetupData, sessionCtx);
}
if (CPA_STATUS_SUCCESS == status)
{
CpaStatus sessionStatus = CPA_STATUS_SUCCESS;
/* Perform algchaining operation */
status = algChainPerformOp(cyInstHandle, sessionCtx,
IPSEC_OUTBOUND_DIR);
/* Remove the session - session init has already succeeded */
sessionStatus = cpaCySymRemoveSession(
cyInstHandle, sessionCtx);
/* maintain status of remove session only when status of all operations
* before it are successful. */
if (CPA_STATUS_SUCCESS == status)
{
status = sessionStatus;
}
}
if(CPA_STATUS_SUCCESS == status)
{
PRINT_DBG("Decrypt-Verify ICV\n");
/* populate symmetric session data structure */
sessionSetupData.sessionPriority = CPA_CY_PRIORITY_HIGH;
//<snippet name="initSessionIPSecDec">
sessionSetupData.symOperation = CPA_CY_SYM_OP_ALGORITHM_CHAINING;
sessionSetupData.algChainOrder =
CPA_CY_SYM_ALG_CHAIN_ORDER_HASH_THEN_CIPHER;
sessionSetupData.cipherSetupData.cipherAlgorithm =
CPA_CY_SYM_CIPHER_AES_CBC;
sessionSetupData.cipherSetupData.pCipherKey = sampleCipherKey;
sessionSetupData.cipherSetupData.cipherKeyLenInBytes =
sizeof(sampleCipherKey);
sessionSetupData.cipherSetupData.cipherDirection =
CPA_CY_SYM_CIPHER_DIRECTION_DECRYPT;
sessionSetupData.hashSetupData.hashAlgorithm = CPA_CY_SYM_HASH_SHA1;
sessionSetupData.hashSetupData.hashMode = CPA_CY_SYM_HASH_MODE_AUTH;
sessionSetupData.hashSetupData.digestResultLenInBytes = ICV_LENGTH;
sessionSetupData.hashSetupData.authModeSetupData.authKey = sampleAuthKey;
sessionSetupData.hashSetupData.authModeSetupData.authKeyLenInBytes
= sizeof(sampleAuthKey);
/* ICV follows immediately after the region to hash */
sessionSetupData.digestIsAppended = CPA_TRUE;
/* Verify the ICV in the inbound direction */
sessionSetupData.verifyDigest = CPA_TRUE;
//</snippet>
}
if (CPA_STATUS_SUCCESS == status)
{
/* Initialize the session */
status = cpaCySymInitSession(cyInstHandle,
symCallback, &sessionSetupData, sessionCtx);
}
if (CPA_STATUS_SUCCESS == status)
{
CpaStatus sessionStatus = CPA_STATUS_SUCCESS;
/* Perform algchaining operation */
status = algChainPerformOp(cyInstHandle, sessionCtx,
IPSEC_INBOUND_DIR);
/* Remove the session - session init has already succeeded */
sessionStatus = cpaCySymRemoveSession(
cyInstHandle, sessionCtx);
/* maintain status of remove session only when status of all operations
* before it are successful. */
if (CPA_STATUS_SUCCESS == status)
{
status = sessionStatus;
}
}
if (CPA_STATUS_SUCCESS == status)
{
/* Query symmetric statistics */
status = cpaCySymQueryStats64(cyInstHandle, &symStats);
if (CPA_STATUS_SUCCESS != status)
{
PRINT_ERR("cpaCySymQueryStats failed, status = %d\n", status);
}
else
{
PRINT_DBG("Number of symmetric operations completed: %llu\n",
(unsigned long long)symStats.numSymOpCompleted);
}
}
/* Clean up */
/* Free session Context */
PHYS_CONTIG_FREE(sessionCtx);
/* Stop the polling thread */
sampleCyStopPolling();
PRINT_DBG("cpaCyStopInstance\n");
cpaCyStopInstance(cyInstHandle);
if (CPA_STATUS_SUCCESS == status)
{
PRINT_DBG("Sample code ran successfully\n");
}
else
{
PRINT_DBG("Sample code failed with status of %d\n", status);
}
return status;
}
示例路径:qat1.5.l.1.13.0-19\quickassist\lookaside\access_layer\src\sample_code\functional\sym\ipsec_sample
Intel® QAT 加速卡之IPSec示例的更多相关文章
- Intel® QAT加速卡之加密、哈希操作流程和示例
Intel QAT 加密API介绍 文章主要讲述了Intel QAT 加密API接口的说明,以及多种应用场景下的使用方法. 文章目录 Intel QAT 加密API介绍 1. 概述 1.1 会话(se ...
- Intel® QAT加速卡之逻辑实例
Intel QAT加速卡逻辑实例 1. QAT相关的名词组织关系 在本手册中描述的平台上,处理器可以连接到一个或多个英特尔通信芯片组8925至8955系列(PCH)设备. 从软件角度来看,每个PCH设 ...
- Intel® QAT加速卡之Linux上编程说明
QAT Software for Linux 1. Introduction 该程序员指南提供了有关软件体系结构和使用指南的信息. 相关的英特尔QAT软件库文档中记录了有关使用英特尔QuickAssi ...
- Intel® QAT加速卡之性能简介
Intel QuickAssist Adapter 8950 设备简介 支持英特尔QuickAssist技术的英特尔QuickAssist适配器提供加密加速和压缩加速服务. 1. Key featur ...
- Intel® QAT加速卡之编程demo框架
QAT demo流程框架 示例一: 代码路径:qat1.5.l.1.13.0-19\quickassist\lookaside\access_layer\src\sample_code\functio ...
- Intel® QAT加速卡之同步异步模式
QAT 的两种操作模式 Intel QAT API同时支持同步和异步两种操作模式. 为了获得最佳性能,该应用程序应能够向加速引擎提交多个未完成的请求. 提交多个未完成的请求可最大程度地减少加速引擎上的 ...
- Intel® QAT加速卡之Ring & Ring Bank
1. QAT的应用模式 Intel 通讯系列芯片对于每种受支持的加速服务(加密,数据压缩),都支持以下应用模式: 内核模式,其中应用程序和加速服务都在内核中运行空间. 用户空间直接访问在用户空间中运行 ...
- Intel® QAT 加速卡之数据面流程(图)
QAT数据面流程 sessionSetupData数据结构 pOpData数据结构
- 入坑Intel OpenVINO:记录一个示例出错的原因和解决方法
今天试用OpenVINO的例子,在过程中发现了一些其他人没有经历的坑特别记录一下. 出错时候:执行Intel OpenVINO示例的是时候,出错的提示代码: 用于 .NET Framework 的 M ...
随机推荐
- 数据库比对工具SQL(表、字段、触发器、索引、视图、存储过程)
做一个数据库比对小工具,把SQL做一个笔记 SELECT object_id AS ID --表ID,'表' sType,Name --表名FROM sys.tablesORDER BY Name-- ...
- mysql的安装,一步一步的教你
1.下载mysql安装包 ,我这里安装的是mysql-5.6.41-winx64 (https://downloads.mysql.com/archives/community/) 选择自己的版本 我 ...
- CTF中的序列化与反序列化
记一些CTF出现的序列化与反序列化的知识点和题目. 序列化和反序列化的概念 序列化就是将对象转换成字符串.字符串包括 属性名 属性值 属性类型和该对象对应的类名. 反序列化则相反将字符串重新恢复成对象 ...
- 从事 Android应用开发4年有余,现在工资7500。很不爽!怎么办?
最近到某论坛看到一个帖子: 坐标北京,在一个公司从事android应用开发4年有余(毕业至今没换过公司).公司利润越来越大,工资却每年长1000,如今才到7500.琢磨着换工作,又不想扔下这四年来逐步 ...
- 基于SpringBoot的药店管理系统java药房管理系统(源码+数据库文件+文档)
注意:该项目只展示部分功能,如需了解,评论区咨询即可. 1.开发环境 开发语言:Java 后台框架:SpringBoot 前端技术:HTML+CSS+JavaScript+Bootstrap+jQue ...
- Java文件和Java包结构
Java中的包概念 Java中的包是封装一组类,子包和接口的机制.软件包用于: 防止命名冲突.例如,可以有两个名称分别为Employee的类,college.staff.cse.Employee和co ...
- 结合scipy.linalg在Python中使用线性系统
摘要:将线性代数概念应用到实际问题中scipy.linalg 使用 Python 和 NumPy处理向量和矩阵 使用线性系统模拟实际问题 使用求解线性系统 scipy.linalg 本文分享自华为云社 ...
- 安全工具推荐之HackTools插件
朋友推荐 链接:https://github.com/LasCC/Hack-Tools 一款多合一Chromium类红队浏览器插件,火狐也有对应版本 功能包括: 动态反向Shell生成器(PHP.Ba ...
- 《JERRY Hexo & GitHub 静态网站搭建说明》
JERRY-Hexo-GitHub <JERRY Hexo & GitHub 静态网站搭建说明> 原创内容,转载请注明出处! 一.前言 1.1 什么是 Hexo? 一个基于 Nod ...
- Windows下安装RocketMQ
目录 前言 环境 具体操作 下载 环境变量配置 启动 关闭 生产.消费实例 RocketMQ Console 前言 项目中用到了延迟消息队列,记录下一win10下rocketmq的安装 环境 win1 ...