启动前全局资源配置
代码解析 函数分析 代码中的重要部分 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_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
函数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
函数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;
}