【p2p、分布式,区块链笔记 UPNP】: Libupnp test_init.c 02 初始化SDK --- UpnpInitPreamble

news2024/12/24 17:23:12

启动前全局资源配置

      • 代码解析
      • 函数分析
      • 代码中的重要部分
      • 1. Winsock 初始化 (`WinsockInit`):
      • 2. 锁初始化资源 (`UpnpInitMutexes`):
      • 3. 句柄表HandleTable(SDK 内部资源的表)初始化:
      • 4.线程池初始化 (`UpnpInitThreadPools`):
      • 5. 回调函数设置:
        • 5.1. SOAP(Simple Object Access Protocol)简单对象访问协议
        • 5.2. GENA(General Event Notification Architecture)通用事件通知架构
      • 6.定时器线程初始化 (`TimerThreadInit`):

在这里插入图片描述

  • 该代码是用于初始化 UPnP(Universal Plug and Play)SDK 的初始步骤的实现。英文preamble /ˈpriːæmbl/ 意为n.序言;前言;导言;开场白;绪论 v. 作序言[绪论]。函数 UpnpInitPreamble() 负责执行一系列关键任务,以确保 UPnP SDK 在使用之前的各个组件都被正确配置和启动。

代码解析

/*! 
 * \brief 初始化过程如下 Performs the initial steps in initializing the UPnP SDK.
 *
 * \li **初始化 Winsock 库**(仅限 Windows)                Winsock library is initialized for the process (Windows specific). 
 * \li **初始化日志系统**,用于调试信息输出。                The logging (for debug messages) is initialized.
 * \li **初始化互斥锁、句柄表和线程池**,确保多线程环境中的安全和稳定。 Mutexes, Handle table and thread pools are allocated and initialized.
 * \li **设置 SOAP 和 GENA 的回调函数**,如果启用了这些功能。 Callback functions for SOAP and GENA are set, if they're enabled.
 * \li **初始化 SDK 的定时器线程**,用于定时任务。           The SDK timer thread is initialized.
 *
 * \return UPNP_E_SUCCESS on success.
 */

函数分析

static int UpnpInitPreamble(void) {
    int retVal = UPNP_E_SUCCESS;  // 初始化返回值为成功
    int i;                        // 循环变量
    
#ifdef UPNP_HAVE_OPTSSDP // FALSE
    uuid_upnp nls_uuid;           // 用于 SSDP 的 NLS UUID
#endif /* UPNP_HAVE_OPTSSDP */

    // WinsockInit根据平台初始化 Winsock 库(Windows 平台特有,Winsock 是 Windows 中用于网络编程的 API,定义在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L281-L319)
    //  Linux 上不需要显式地初始化网络库,也不需要像 Windows 上那样调用 WSAStartup() 和 WSACleanup()
    retVal = WinsockInit();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // Winsock 初始化失败,返回错误代码
    }

    // 通过时间来初始化随机数种子,用于 SDK 内部的随机数生成
    srand((unsigned int)time(NULL));

    // 初始化日志系统,用于调试输出
    retVal = UpnpInitLog();
    if (retVal != UPNP_E_SUCCESS) {
        // 如果日志系统初始化失败,返回通用初始化失败错误
        return UPNP_E_INIT_FAILED;
    }

    UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Inside UpnpInitPreamble\n");

    // 初始化 SDK 全局互斥锁
    retVal = UpnpInitMutexes();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // 互斥锁初始化失败,返回错误
    }

#ifdef UPNP_HAVE_OPTSSDP
    // 如果启用了 SSDP,生成并设置 NLS UUID
    uuid_create(&nls_uuid);
    upnp_uuid_unpack(&nls_uuid, gUpnpSdkNLSuuid);
#endif /* UPNP_HAVE_OPTSSDP */

    // 初始化句柄列表
    HandleLock();  // 加锁,防止多线程竞争
    for (i = 0; i < NUM_HANDLE; ++i) {
        HandleTable[i] = NULL;  // 将句柄表初始化为空
    }
    HandleUnlock();  // 解锁

    // 初始化 SDK 的全局线程池
    retVal = UpnpInitThreadPools();
    if (retVal != UPNP_E_SUCCESS) {
        return retVal;  // 线程池初始化失败,返回错误
    }

#ifdef INCLUDE_DEVICE_APIS // FALSE
    #if EXCLUDE_SOAP == 0
    // 如果启用了 SOAP,设置 SOAP 的回调函数
    SetSoapCallback(soap_device_callback);
    #endif
#endif /* INCLUDE_DEVICE_APIS */

#ifdef INTERNAL_WEB_SERVER
    #if EXCLUDE_GENA == 0
    // 如果启用了 GENA,设置 GENA 的回调函数
    SetGenaCallback(genaCallback);
    #endif
#endif /* INTERNAL_WEB_SERVER */

    // 初始化 SDK 定时器线程
    retVal = TimerThreadInit(&gTimerThread, &gSendThreadPool);
    if (retVal != UPNP_E_SUCCESS) {
        // 如果定时器线程初始化失败,调用 UpnpFinish 清理资源
        UpnpFinish();
        return retVal;
    }

    return UPNP_E_SUCCESS;  // 初始化成功
}

代码中的重要部分

1. Winsock 初始化 (WinsockInit):

  • 此函数用于初始化 Winsock 库(如果是Windows系统)。Winsock 是 Windows 中用于网络编程的 API。
  • WinsockInit根据平台初始化 Winsock 库(Windows 平台特有,Winsock 是 Windows 中用于网络编程的 API,定义在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L281-L319)
  • Linux 上不需要显式地初始化网络库,也不需要像 Windows 上那样调用 WSAStartup() 和 WSACleanup()。

2. 锁初始化资源 (UpnpInitMutexes):

  • 互斥锁(mutex)用于在线程间保护共享资源。这个函数初始化 SDK 的全局互斥锁,以确保资源访问的同步性。(比如在后续的uuid创建时,需要调用如下代码,先加锁,操作,后解锁)
extern ithread_mutex_t gUUIDMutex;

#define UUIDLock() ithread_mutex_lock(&gUUIDMutex)
#define UUIDUnlock() ithread_mutex_unlock(&gUUIDMutex)
  • 其中ithread_mutex_init、ithread_rwlock_init等函数同样是对POSIX线程库中的函数封装。
/*!
 * \brief 初始化 UPnP SDK 使用的全局互斥锁。
 *
 * \return UPNP_E_SUCCESS: 初始化成功
 *         UPNP_E_INIT_FAILED: 互斥锁初始化失败
 */
static int UpnpInitMutexes(void)
{
#ifdef __CYGWIN__
    // 在 Cygwin 系统上,pthread_mutex_init() 函数在某些情况下会失败。
    // 为了解决这个问题,需要在调用该函数之前将 GlobalHndRWLock 结构体清零。
    // 这是一个临时解决方案,未来应该修复 Cygwin 的这个 bug。
    memset(&GlobalHndRWLock, 0, sizeof(GlobalHndRWLock));
#endif

    // 初始化全局读写锁。
    // 读写锁允许多个线程同时读取共享数据,但同一时刻只能有一个线程写入。
    if (ithread_rwlock_init(&GlobalHndRWLock, NULL) != 0) {
        // 如果初始化失败,返回错误码。
        return UPNP_E_INIT_FAILED;
    }

    // 初始化全局 UUID 互斥锁。
    // 互斥锁确保同一时刻只有一个线程可以访问 UUID 生成相关的代码。
    if (ithread_mutex_init(&gUUIDMutex, NULL) != 0) {
        return UPNP_E_INIT_FAILED;
    }

    // 如果定义了 INCLUDE_CLIENT_APIS,则初始化全局订阅互斥锁。
    // 订阅互斥锁用于保护订阅相关的操作。
#ifdef INCLUDE_CLIENT_APIS
    if (ithread_mutex_init(&GlobalClientSubscribeMutex, NULL) != 0) {
        return UPNP_E_INIT_FAILED;
    }
#endif

    // 如果所有互斥锁都初始化成功,则返回成功。
    return UPNP_E_SUCCESS;
}

3. 句柄表HandleTable(SDK 内部资源的表)初始化:

  • 句柄表是用于管理 SDK 内部资源的表。在多线程环境下,必须加锁保护:
/*! UPnP device and control point handle table  */ // https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/api/upnpapi.c#L187-L188 
static void *HandleTable[NUM_HANDLE];
  • 这里首先通过 HandleLock() 函数加锁,然后初始化所有句柄为 NULL,最后通过 HandleUnlock() 解除锁定。
  • HandleLock()HandleUnlock() 定义如下:
/*!
 * \brief Get handle information.
 *
 * \return HND_DEVICE, UPNP_E_INVALID_HANDLE
 */
Upnp_Handle_Type GetHandleInfo(
	/*! handle pointer (key for the client handle structure). */
	int Hnd,
	/*! handle structure passed by this function. */
	struct Handle_Info **HndInfo);

#define HandleLock() HandleWriteLock()

#define HandleWriteLock() \
	UpnpPrintf( \
		UPNP_INFO, API, __FILE__, __LINE__, "Trying a write lock\n"); \
	ithread_rwlock_wrlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Write lock acquired\n");

#define HandleReadLock() \
	UpnpPrintf( \
		UPNP_INFO, API, __FILE__, __LINE__, "Trying a read lock\n"); \
	ithread_rwlock_rdlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Read lock acquired\n");

#define HandleUnlock() \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Trying Unlock\n"); \
	ithread_rwlock_unlock(&GlobalHndRWLock); \
	UpnpPrintf(UPNP_INFO, API, __FILE__, __LINE__, "Unlocked rwlock\n");

4.线程池初始化 (UpnpInitThreadPools):

  • 线程池用于处理 SDK 中的异步任务。UpnpInitThreadPools 函数确保线程池正确初始化,以便 SDK 能够高效处理任务。
/*!
 * \brief 初始化 UPnP SDK 所使用的全局线程池
 *
 * \return 成功时返回 UPNP_E_SUCCESS,若互斥锁无法初始化则返回 UPNP_E_INIT_FAILED
 */
static int UpnpInitThreadPools(void)
{
    // 定义返回值并初始化为成功标志
    int ret = UPNP_E_SUCCESS;
    
    // 定义线程池属性结构体
    ThreadPoolAttr attr;

    // 初始化线程池属性
    TPAttrInit(&attr);
    
    // 设置线程池的最大线程数
    TPAttrSetMaxThreads(&attr, MAX_THREADS);
    
    // 设置线程池的最小线程数
    TPAttrSetMinThreads(&attr, MIN_THREADS);
    
    // 设置线程栈的大小
    TPAttrSetStackSize(&attr, THREAD_STACK_SIZE);
    
    // 设置每个线程最多处理的任务数
    TPAttrSetJobsPerThread(&attr, JOBS_PER_THREAD);
    
    // 设置线程空闲的时间
    TPAttrSetIdleTime(&attr, THREAD_IDLE_TIME);
    
    // 设置线程池允许的最大任务总数
    TPAttrSetMaxJobsTotal(&attr, MAX_JOBS_TOTAL);

    // 初始化发送线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gSendThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

    // 初始化接收线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gRecvThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

    // 初始化迷你服务器线程池,若失败则跳转到 exit_function 退出
    if (ThreadPoolInit(&gMiniServerThreadPool, &attr) != UPNP_E_SUCCESS) {
        ret = UPNP_E_INIT_FAILED;
        goto exit_function;
    }

exit_function:
    // 如果初始化失败,设置 SDK 初始化状态为 0,并调用 UpnpFinish 清理
    if (ret != UPNP_E_SUCCESS) {
        UpnpSdkInit = 0;
        UpnpFinish();
    }

    // 返回初始化结果
    return ret;
}
  • ThreadPoolInit函数的操作过程详见此链接🔗,这是项目中唯一调用并创建线程池的地方,后续的代码都直接使用此线程池。线程池结构体定义见连接,其仍使用mutex枷锁和条件变量唤醒线程,同时增加对线程队列进行优先级划分(低中高有限度和持久线程,持久线程为ThreadPoolJob类型,低中高为带有比较函数的LinkedList(区别标准库的std::list)类型)。
    在这里插入图片描述

5. 回调函数设置:

  • 如果启用了 SOAP 和 GENA 协议,分别设置它们的回调函数。这些函数用于处理设备 API 和事件通知。
5.1. SOAP(Simple Object Access Protocol)简单对象访问协议
  • SOAP 是一种基于 XML 的消息传输协议,设计用于在分布式网络环境中交换结构化信息。它常用于 web 服务中,尤其是在涉及复杂数据和远程过程调用 (RPC) 的场景下。
#ifdef INCLUDE_DEVICE_APIS
	#if EXCLUDE_SOAP == 0
	SetSoapCallback(soap_device_callback);
	#endif
#endif /* INCLUDE_DEVICE_APIS */
  • 函数SetSoapCallback(https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/genlib/miniserver/miniserver.c#L139)定义如下:
#ifdef INCLUDE_DEVICE_APIS
    void SetSoapCallback(MiniServerCallback callback) { gSoapCallback = callback; }
#endif /* INCLUDE_DEVICE_APIS */
  • 其传入的参数为定义为如下的函数指针类型:
/*! . */
typedef void (*MiniServerCallback)(
	/* ! [in] . 指向 http_parser_t 类型的指针,表示一个 HTTP 解析器,用于解析 HTTP 请求或响应 */
	http_parser_t *parser,
	/* ! [in] . 指向 http_message_t 类型的指针,表示一个 HTTP 请求消息,包含请求的详细内容。*/
	http_message_t *request,
	/* ! [in] .指向 SOCKINFO 类型的指针,表示套接字信息,可能包含有关网络连接的相关数据。 */
	SOCKINFO *info);
5.2. GENA(General Event Notification Architecture)通用事件通知架构
  • GENA 是一种用于异步事件通知的协议。它的主要目的是允许客户端订阅并接收来自服务器端的事件通知。GENA 协议最常见的使用是在 UPnP 环境中进行事件通知。
#ifdef INTERNAL_WEB_SERVER
	#if EXCLUDE_GENA == 0
	SetGenaCallback(genaCallback);
	#endif
#endif /* INTERNAL_WEB_SERVER */
  • 函数SetGenaCallback在https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/genlib/miniserver/miniserver.c#L142
void SetGenaCallback(MiniServerCallback callback) { gGenaCallback = callback; }

6.定时器线程初始化 (TimerThreadInit):

  • 这是 SDK 的最后一个初始化步骤,用于启动一个专门处理定时任务的线程。如果这个步骤失败,会调用 UpnpFinish() 来清理已经分配的资源。
  • 函数TimerThreadInit的主要作用是初始化一个定时器线程,并将其添加到指定的线程池中,以便定时器能够在线程池中异步执行任务。
  • 通过 TPJobInit 初始化线程池任务,并设置任务的高优先级,然后将任务添加到线程池中,确保定时器线程能够持续运行。
int TimerThreadInit(TimerThread *timer, ThreadPool *tp)
{
    // 定义返回值并初始化为 0,表示无错误
    int rc = 0;

    // 定义线程池任务结构体,代表定时器线程的工作
    ThreadPoolJob timerThreadWorker;

    // 断言 timer 和 tp 不为空,如果为空则程序中断
    assert(timer != NULL);
    assert(tp != NULL);

    // 如果 timer 或者线程池 tp 为空,返回 EINVAL 错误码
    if ((timer == NULL) || (tp == NULL)) {
        return EINVAL;
    }

    // 初始化定时器互斥锁,rc 累加返回值(0 为成功)
    rc += ithread_mutex_init(&timer->mutex, NULL);

    // 断言互斥锁初始化成功
    assert(rc == 0);

    // 锁定定时器的互斥锁
    rc += ithread_mutex_lock(&timer->mutex);
    assert(rc == 0);

    // 初始化定时器的条件变量
    rc += ithread_cond_init(&timer->condition, NULL);
    assert(rc == 0);

    // 初始化定时器事件的空闲列表(FreeList),每个事件的大小为 TimerEvent,容量为 100
    rc += FreeListInit(&timer->freeEvents, sizeof(TimerEvent), 100);
    assert(rc == 0);

    // 将定时器的关闭标志设为 0(未关闭)
    timer->shutdown = 0;

    // 设置定时器的线程池为传入的 tp
    timer->tp = tp;

    // 初始化最后一个事件 ID 为 0
    timer->lastEventId = 0;

    // 初始化事件队列(eventQ),事件队列存储定时器的所有事件
    rc += ListInit(&timer->eventQ, NULL, NULL);
    assert(rc == 0);

    // 如果初始化过程有错误(rc != 0),则返回 EAGAIN 错误码
    if (rc != 0) {
        rc = EAGAIN;
    } else {
        // 初始化线程池任务,指定工作函数 TimerThreadWorker,并传入定时器结构体
        TPJobInit(&timerThreadWorker, TimerThreadWorker, timer);

        // 设置线程池任务的优先级为高优先级
        TPJobSetPriority(&timerThreadWorker, HIGH_PRIORITY);

        // 将任务添加到线程池,作为一个持久任务运行
        rc = ThreadPoolAddPersistent(tp, &timerThreadWorker, NULL);
    }

    // 解锁定时器的互斥锁
    ithread_mutex_unlock(&timer->mutex);

    // 如果任务添加失败,则销毁已经初始化的资源(条件变量、互斥锁、空闲列表、事件队列)
    if (rc != 0) {
        ithread_cond_destroy(&timer->condition);
        ithread_mutex_destroy(&timer->mutex);
        FreeListDestroy(&timer->freeEvents);
        ListDestroy(&timer->eventQ, 0);
    }

    // 返回初始化的结果码,0 表示成功,非 0 表示错误
    return rc;
}

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

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

相关文章

Android OpenGLES2.0开发(四):矩阵变换和相机投影

事物的本质是事物本身所固有的、深藏于‌现象背后并决定或支配现象的方面‌。 还记得我们上一篇绘制的三角形吗&#xff0c;我们确实能够顺利用OpenGL ES绘制出图形了&#xff0c;这是一个好的开始&#xff0c;但这还远远不够。我们定义的坐标是正三角形&#xff0c;但是绘制出…

解决无法安装“vue.volar“扩展,跟vscode版本不兼容问题

问题&#xff1a;安装volar插件的时候提示跟vscode版本不兼容 解决方案 1、进入VSCode插件市场&#xff0c;搜索Vue.volar&#xff08;直达链接&#xff1a;volar下载界面&#xff09; 2、点击download Extension&#xff08;下载插件&#xff09; 3、下载.vsix文件完成后&a…

杨中科 .netcore Linq 。一前期准备知识

为什么要学Linq 一、为什么要学LINQ? 让数据处理变得简单: 统计一个字符串中每个字母出现的频率(忽略大小写)&#xff0c;然后按照从高到低的顺序输出出现频率高于2次的单词和其出现的频率。 var itemss.Where(c >char.lsLetter(c))//过滤非字母 .Select(c>char.ToLo…

【网络安全】CVE-2024-46990: Directus环回IP过滤器绕过实现SSRF

未经许可,不得转载。 文章目录 背景漏洞详情受影响版本解决方案背景 Directus 是一款开源 CMS,提供强大的内容管理 API,使开发人员能够轻松创建自定义应用程序,凭借其灵活的数据模型和用户友好的界面备受欢迎。然而,Directus 存在一个漏洞,允许攻击者绕过默认的环回 IP …

55100-3H-02微型法兰安装霍尔效应传感器

55100-3H-02微型法兰安装霍尔效应传感器应用领域如&#xff1a;位置和极限感应、无刷换向、RPM测量、直流电机、流量计量、角度感应、磁编码器。 概述 55100微型法兰安装霍尔效应传感器&#xff0c;尺寸为25.5mmx 11.00m&#xff0c;高度仅为3.00mm&#xff0c;可选择数字或可…

生物制药洁净室:设计、施工及常见问题解决

生物制药洁净室的设计施工要点对于保障生产环境的洁净度至关重要。生物制药洁净室的建设不仅是为了符合行业标准&#xff0c;更是确保产品安全和质量的重要环节。生物制药洁净室设计与生物制药洁净室施工是相辅相成的&#xff0c;接下来&#xff0c;我们将深入探讨这两方面的关…

交易中心态管理和情绪控制是第一位

情绪是交易中常见的障碍&#xff0c;无论是负面情绪还是喜悦等正面情绪&#xff0c;都可能成为妨碍交易的关键因素。交易的核心在于保持客观理性&#xff0c;而情绪的起伏往往不经意间扰乱我们的思绪。因此&#xff0c;成功的交易员若想突破自我&#xff0c;首要任务便是克服情…

事件轮询机制

引出闭包案例 for(var i 0; i < 5; i) {setTimeout(function () {console.log(i); // &#xff1f;}); } console.log(a); // 结果是什么&#xff1a;&#xff1f; 2.事件轮询机制&#xff08;Event Loop&#xff09; 事件轮询&#xff08;Event Loop&#xff09;是一个…

调整ApplyItem的界面

调整ApplyItem的界面 前言 在上一集&#xff0c;我们就完成了一个clickItem的一个函数&#xff0c;继承SessionFriendItem并通过它添加不同的Item&#xff0c;并且我添加了它的枚举类。 在上一集的最后&#xff0c;我们提了一嘴&#xff0c;我们要修改ApplyItem的样式。 分…

Flink 04 | 窗口介绍 - 无界数据流的核心

窗口介绍 Flink中Windows&#xff08;窗口&#xff09;是处理无界数据流的核心。因为无界数据集是不断增长的&#xff0c;无法直接对整个数据集进行操作。窗口将数据流分成有限大小的Buckets&#xff0c;我们可以在这些Buckets上应用计算。本文档重点介绍如何在 Flink 中选择合…

三菱FX3UPLC机械原点回归- DSZR/ZRN指令

机械原点回归用指令的种类 产生正转脉冲或者反转脉冲后&#xff0c;增减当前值寄存器的内容。可编程控制器的定位指令&#xff0c;可编程控制器的电源0FF后&#xff0c;当前值寄存器清零&#xff0c;因此上电后&#xff0c;请务必使机械位置和当前值寄存器的位置相吻合…

10.模拟实现s

前面我们了解了string类的常用接口使用&#xff0c;那么现在就来模拟实现一下。 1.constructor string.h namespace Ro {class string{public:string(){}string(const char* str){}~string(){}private:char* _str;size_t _size;size_t _capacity;}; } 为了和库里面的string…

Jenkins+kubernetes流水线构建java项目

在传统的业务环境中&#xff0c;我们的应用部署或者更新都是采用手工的方式&#xff0c;但是在企业内部&#xff0c;应用架构一般都采用微服务&#xff0c;大部分项目都会对应几十个、上百甚至上千个微服务&#xff0c;并且还不仅仅只有一个项目&#xff0c;所以采用收工方式上…

godot帧同步-关于“显示与逻辑分离”

很多教程说帧同步的关键是“显示与逻辑分离”&#xff0c;但是又没有具体讲解&#xff0c;我起初也没有搞懂这句话的意思&#xff0c;就直接上手开发帧同步了。在开发的过程中&#xff0c;一下子就悟了&#xff0c;所以分享一下。 显示与逻辑未分离&#xff08;单机&#xff0…

嵌入式中单链表基本实现

第一:单链表基本原理 依次读入表L=(a0,.....,an-1)中每一元素ai(假设为整型),若ai≠结束符(-1),则为ai创建一结点,然后插入表尾,最后返回链表的头结点指针H。 第二:单链表具体实现方法 1:实现单链表的时候,需要先定义基本文件link.h #ifndef __LINKLIST_H__ #define…

考华为认证拼了命,怎么还是没工作啊

在当今竞争激烈的就业市场中&#xff0c;网络工程领域的发展备受关注。当你疯狂地在某 BOSS 或者某联等招聘平台上浏览时&#xff0c;你必然会惊讶地发现&#xff0c;华为认证已赫然成为网络方向至关重要的资格认证之一&#xff0c;频繁地出现在形形色色的岗位 JD 里。 这一现…

如何设置 GitLab 密码长度?

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 60天专业…

【RabbitMQ——消息应答机制——分布式事务解决方式】

1. RabbitMQ高级-消息确认机制的配置 NONE值是禁用发布确认模式&#xff0c;是默认值 CORRELATED值是发布消息成功到交换器后会触发回调方法&#xff0c;如1示例SIMPLE值经测试有两种效果&#xff0c;其一效果和CORRELATED值一样会触发回调方法&#xff0c;其二在发布消息成功…

UE5 TimeLine入门

UE5 TimeLine入门 时间轴曲线 共计三个关键帧&#xff08;0,0&#xff09;(1.5,10) (3,0) 蓝图 1.按下空格键执行。 2.时间轴TimeLine函数。 3.动画播放结束后执行。 4.每一帧都执行。

GR-ConvNet论文 学习笔记

GR-ConvNet 文章目录 GR-ConvNet前言一、引言二、相关研究三、问题阐述四、方法A.推理模块B.控制模块C.模型结构D.训练方法E.损失函数 五、评估A.数据集B.抓取评判标准 六、实验A.设置B.家庭测试物体C.对抗性测试物体D.混合物体 七、结果A.康奈尔数据集B.Jacquard数据集C.抓取新…