启动前全局资源配置 代码解析 函数分析 代码中的重要部分 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 在使用之前的各个组件都被正确配置和启动。
代码解析
函数分析
static int UpnpInitPreamble ( void ) { int retVal = UPNP_E_SUCCESS; int i; # ifdef UPNP_HAVE_OPTSSDP uuid_upnp nls_uuid;
# endif retVal = WinsockInit ( ) ; if ( retVal != UPNP_E_SUCCESS) { return retVal; } 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" ) ; retVal = UpnpInitMutexes ( ) ; if ( retVal != UPNP_E_SUCCESS) { return retVal; } # ifdef UPNP_HAVE_OPTSSDP uuid_create ( & nls_uuid) ; upnp_uuid_unpack ( & nls_uuid, gUpnpSdkNLSuuid) ;
# endif HandleLock ( ) ; for ( i = 0 ; i < NUM_HANDLE; ++ i) { HandleTable[ i] = NULL ; } HandleUnlock ( ) ; retVal = UpnpInitThreadPools ( ) ; if ( retVal != UPNP_E_SUCCESS) { return retVal; } # ifdef INCLUDE_DEVICE_APIS # if EXCLUDE_SOAP == 0 SetSoapCallback ( soap_device_callback) ; # endif
# endif # ifdef INTERNAL_WEB_SERVER # if EXCLUDE_GENA == 0 SetGenaCallback ( genaCallback) ; # endif
# endif retVal = TimerThreadInit ( & gTimerThread, & gSendThreadPool) ; if ( retVal != UPNP_E_SUCCESS) { 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_APISif (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
函数SetSoapCallback
(https://github1s.com/pupnp/pupnp/blob/branch-1.14.x/upnp/src/genlib/miniserver/miniserver.c#L139)定义如下:
#ifdef INCLUDE_DEVICE_APISvoid 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
函数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,容量为 100rc += FreeListInit(&timer->freeEvents, sizeof(TimerEvent), 100);assert(rc == 0);// 将定时器的关闭标志设为 0(未关闭)timer->shutdown = 0;// 设置定时器的线程池为传入的 tptimer->tp = tp;// 初始化最后一个事件 ID 为 0timer->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;
}