将 RTC 客户端 API 用于可缩放的应用程序

Microsoft Corporation
2003 年 10 月

适用于:
     Microsoft® 实时通信客户端 API 1.2 版
     Live Communications Server
     即时消息应用程序

摘要:在大多数情况下,Microsoft 实时通信 (RTC) 客户端应用程序编程接口 (API) 1.2 版的效率都很高。但是,如果是使用同一台计算机为多个客户端提供服务,那么在设计应用程序时就需要重点考虑提供最佳的可缩放性。本文论述的是可以并入到 RTC 客户端 API 应用程序中以提高其可缩放性的技术。

目录

简介
应用程序方案
RTC 客户端 API 体系结构的注意事项
可缩放性技术
方案
小结
相关链接

简介

当组织计划大规模部署实时通信应用程序时,非常关键的一点就是要确保这些应用程序可以进行伸缩来满足所需目标。对于客户端类应用程序(每个客户端在其自己的计算机上运行),RTC 客户端 API 是十分有效的。要构建可以通过一台计算机为多个客户端提供服务的、可缩放的 RTC 客户端 API 应用程序,需要在设计时确保该应用程序是可缩放的。

本文探讨了 RTC 客户端 API 中可用于提高 RTC 客户端应用程序的可缩放性的技术。文章以介绍可以使用 RTC 客户端 API 来构建的应用程序类型作为开篇,随后讨论了构建可缩放的 RTC 客户端应用程序时需要考虑的因素。接下来,本文阐述了可以与 RTC 客户端 API 配套使用以提高可缩放性的技术。最后,按照其实现可缩放性的方式详细说明了三个示例应用程序。

应用程序方案

这里所涉及到的应用程序有两类:客户端类应用程序和服务器类应用程序。前者在每台计算机上都有一个实时客户端,例如传统的即时消息 (IM) 应用程序。后者通常要代表多个用户执行操作,或者同时与成百上千的用户进行通信。

服务器类应用程序通常以与用户进行交互的智能应用程序为基础。这些智能应用程序也称为“automatic robot”(自动机器人),或简称为“bot”。

基于 bot 的应用程序可以分为两类:向客户端发送信息的通知 bot 和接受信息并向客户端作出响应的交互式 bot。此外还有第三种服务器类应用程序,即基于 Web 的客户端,它通过 Web 服务器与用户进行交互。

通知 bot

通知 bot 是实时应用程序,它从中央服务器向多个客户端发送信息(请参见图 1)。这种单向传输意味着客户端无法直接与通知 bot 进行通信,而是必须使用某些其他技术(例如 Web 应用程序)来选择所要接收的事件。

例如,通知某个特定电子邮件服务器的所有用户该服务器将要脱机的应用程序就是一个通知 bot。另一个很有用的通知 bot 可以在出现恶劣天气时发送警报。

图 1:一个通知 bot 向多个 IM 客户端传输信息。

交互式 bot

交互式 bot 是允许多个客户端与某个中央服务器进行实时通信的应用程序,如图 2 所示。它与通知 bot 的不同之处在于交互式 bot 支持与客户端进行双向通信。使用这种方法,可以构建与用户进行实时交互的应用程序。

在此方案中,有两个主要的子方案。第一个方案为用户提供信息并等待用户响应。例如,某个应用程序可以通知用户股票价格的变化,然后等待用户选择购入还是卖出。第二个方案等待用户请求与 bot 的会话,然后响应用户提出的请求。例如,某个日历应用程序使用户可以安排会议或其他事件,并且用户会在会议或事件即将开始之前收到提示。

图2:一个交互式 bot 接收和响应来自多个 IM 客户端的请求。

基于 Web 的客户端

基于 Web 的客户端通过 Web 界面提供了与传统 IM 客户端相同的基本功能,这可以最大限度地使更多用户使用应用程序,如图 3 所示。不仅如此,它还有另一个作用,那就是使用户不必下载本地软件,从而减少了用户对下载可能包含病毒的担心。

对于想要为其内部 IM 系统提供一个基于 Web 的前端的组织来说,这些类型的客户端非常有用。例如,一个公司可能想要使用基于 Web 的 IM 客户端连接客户与支持小组。这样做可以最大限度地增加能够连接到支持小组的用户数量。

图3:通过使用 Web 服务器处理用户界面任务,基于 Web 的客户端在一台计算机上为多个 IM 客户端提供服务。

RTC 客户端 API 体系结构的注意事项

RTC 客户端 API 最初的设计目的是用来支持单用户的工作站。这意味着在设计服务器类应用程序程序时需要注意一些问题。

线程问题

有些时候,服务器类应用程序需要代表多个用户注册到 SIP 服务器(例如,将基于 Web 的前端注册到 RTC 客户端 API),该应用程序应当创建分布在多个线程中的多个 RTCClient 对象。

每个线程可以支持的 RTCClient 对象数量取决于应用程序的复杂性,以及要为每个客户端处理的消息的频率。开始时,应用程序中每个线程的 RTCClient 对象数量最好为 10 到 15 个。然后可以向上或向下调整该起始值以优化性能。

例如,某个可缩放应用程序的创建者只关注 IM 功能而不准备实现任何出席功能。与某些为每个登录的用户都实现出席功能和 IM 功能的其他可缩放应用程序相比,我们可能希望该应用程序在每个线程上具有更多的 RTCClient 对象。

由于 RTC 客户端 API 采用的是单元线程模式,因而还应当在同一线程上创建该特定 RTCClient 对象所需的所有资源。并且由于 RTC 客户端 API 在很大程度上依赖于在 RTC 客户端 API 和用户程序之间传递的 Microsoft Windows® 消息,因而接收和处理 Windows 消息的用户代码应当位于与创建 RTCClient 对象的线程相同的线程中。

阻塞问题

RTC 客户端应用程序应尽可能避免调用任何可能会阻塞线程的例程。这样做可以防止在该相同线程上为任何其他客户端处理工作,直至阻塞活动完成。例如,数据库请求应当以异步方式进行,以便在等待数据库服务器完成请求时不会阻塞该线程。

可缩放性技术

当与 RTC 客户端 API 进行交互时,可以应用多种技术来提高应用程序的性能。通常情况下,这些技术将对服务器类应用程序不再需要的功能进行优化。

禁用媒体管理器

从总体来看,服务器类应用程序用于发送和接收文本消息以及跟踪出席信息,一般不传输也不接收音频或视频信息。这样便有可能通过禁用 RTCClient 对象中的媒体管理器来节省资源。这会同时节省通常情况下 RTC 客户端为应用程序将来发送或接收媒体作准备而创建的内存和线程。

要禁用媒体管理器,请调用 IRTCClient2.InitializeEx 方法并指定 RTCIF_DISABLE_MEDIA 值,如下所示:

    hr = m_pClient->InitializeEx(RTCIF_DISABLE_MEDIA);
// 实现错误处理并清理代码
禁用对基于 UPnP 的 NAT 的检测

另一种在初始化 RTCClient 对象时节省资源的技术是禁用对基于通用即插即用 (UPnP) 的网络地址转换 (NAT) 的检测。如果 RTC 客户端计算机位于已启用 UPnP 的 NAT 之后,则 UPnP 将用于与 NAT 设备进行通信以确定应当用于实时会话的相应 IP 地址和端口号。如果应用程序不通过 NAT 与 Internet 进行通信,则禁用此功能可以节省更多资源。

要禁用对基于 UPnP 的 NAT 的检测,请调用 ITRCClient2.InitializeEx 方法并指定 RTCIF_DISABLE_UPNP。

    hr = m_pClient->InitializeEx(RTCIF_DISABLE_UPNP);
// 实现错误处理并清理代码

请注意,InitializeEx 只能被调用一次,因此必须将所有初始化标志合并在一起。例如,下面的一段代码将 RTCIF_DISABLE_UPNP 标志与 RTCIF_DISABLE_MEDIA 标志合并在一起以禁用 UPnP 和媒体管理器。

    hr = m_pClient->InitializeEx(RTCIF_DISABLE_UPNP |
RTCIF_DISABLE_MEDIA);
// 实现错误处理并清理代码
关闭 IP 地址更改的检测和恢复

提高服务器类应用程序的可缩放性的另一种方法是禁用 IP 地址更改的检测和恢复。RTC 客户端 API 具有检测运行它的本地主机的 IP 地址更改情况的功能。在客户端类应用程序中,此功能非常有用,因为有若干不同情况会迫使客户端更改 IP 地址。这些原因可能是拨号连接断线或由 DHCP 初始设置的地址发生变化等,因此,RTC 客户端 API 为地址更改通知打开套接字以检测这些情况。

但是,所有这些原因都不适用于服务器类应用程序的主机。大多数用作服务器的主机已被分配了一个不会随时间更改的静态 IP 地址。

由于主机的 IP 地址不可能发生变化,因而可以关闭 IP 地址更改以节省更多资源。要禁用 IP 地址更改的检测和恢复,可以在初始化 RTCClient 对象时使用 RTCIF_ENABLE_SERVER_CLASS 标志。

    hr = m_pClient->InitializeEx(RTCIF_ENABLE_SERVER_CLASS);
// 实现错误处理并清理代码
禁用序列化

RTCIF_ENABLE_SERVER_CLASS 标志还可以禁用 SUBSCRIBE 请求和 getPresence SERVICE 请求的序列化。这可以显著减少处理这些请求所需的周期数。

为避免向服务器发送过多出席信息 SIP 请求,RTC 客户端 API 对用于获取出席信息的 SIP 请求进行了序列化。可缩放应用程序会创建大量与出席相关的请求,这些应用程序必须关闭此行为,以便在这些请求中获得更好的吞吐量。

RTCIF_ENABLE_SERVER_CLASS 标志也可以禁用序列化。

请注意,这些请求所发送到的 SIP 服务器或任何其他实体能够同时处理如此大量的请求。

禁用防火墙检测

RTC 客户端 API 可以检测和穿过 Internet 连接防火墙 (ICF)。此功能对于客户端用户应用程序非常有用,因为它可以最大限度地减少运行带有 ICF 的 RTC 客户端应用程序所需的工作量。但是,由于它要占用额外的资源,因而服务器类应用程序可能并不希望使用它。

通过将 RTCIF_ENABLE_SERVER_CLASS 标志传递给 InitializeEx 方法可以禁用此功能。这可以节省系统上的一些宝贵资源,从而获得更好的可缩放性。

避免不必要的事件

避免应用程序进行额外工作的一种方法是认真选择应当处理的事件。应当始终关闭与已禁用的功能相关联的事件,以减少 RTC 客户端 API 执行的工作量。即,如果在 RTC 客户端 API 中禁用了媒体管理器,则应当禁用所有与处理视频和音频流相关的事件。

假设应用程序仅响应所收到的消息。在这种情况下,我们无需捕获 IRTCBuddyEvent 或 IRTCWatcherEvent,而只需要 RTCEF_MESSAGING 事件。以下代码显示了如何滤除 RTCEF_MESSAGING 事件以外的所有事件。

    // 确定事件过滤器
long lFlags = RTCEF_MESSAGING;
// 为 RTC 客户端设置事件过滤器
hr = m_pClient->put_EventFilter(lFlags);
// 实现错误处理并清理代码
避免发送键入消息通知

显然,任何服务器类应用程序都不需要发送键入消息通知。键入消息主要用于使会话中的一个客户端让其他客户端知道它当前正在即时消息会话中键入文本。由于服务器类应用程序始终在线,因而服务器类应用程序无需提供键入消息通知功能。

如果服务器类应用程序与某个自定义的客户端应用程序配套使用,则我们非常希望该客户端应用程序也不发送键入消息。尽管服务器应用程序会忽略这些消息,但仍然不可避免会有一定数量的系统开销。此外,键入消息会增加带宽的使用量,因此应当避免使用键入消息以便提高服务器应用程序的可缩放性。

将服务器类应用程序添加为始终在线的联系人

另一个显而易见的问题是,服务器类应用程序始终在线,因此无需确定服务器类应用程序的出席状态。强制客户端类应用程序始终将服务器类应用程序添加为“始终在线”的联系人可以减少 RTC 客户端为处理这些请求而必须完成的工作量。

虽然此技术不能直接应用于服务器类应用程序,但是可以将其并入与服务器应用程序进行交互的自定义客户端类应用程序中,如下所示:

    // 添加联系人
IRTCBuddy2 * pBuddy = NULL;
hr = pPresence->AddBuddyEx(
bstrURI,
bstrName,
NULL,
VARIANT_TRUE,
RTCBT_ALWAYS_ONLINE,
NULL,
0,
&pBuddy);
// 释放 pPresence
在基于 Web 的 UI 方案中提高可缩放性的技术

根据传输协议的不同,RTC 客户端 API 为每个 RTC 客户端使用一个或多个套接字。使用 TCP 作为传输协议时,RTC 客户端 API 为每个注册的客户端使用三个套接字。第一个套接字是客户端的监听端口,第二个套接字用于客户端为连接到要登录的注册器而创建的新动态端口,而第三个套接字则是在客户端在其监听端口上为所有通知接受来自注册器的连接请求时创建的。与 TCP 相比,RTC 客户端在使用 Transport Level Security (TLS) 登录时只使用一个套接字,因为在 TLS 模式中没有监听端口。

如果某个应用程序用完了所有可用端口,则可以通过创建以下注册表项和注册表值来增加可用端口数:

HKLM\Software\Policies\Microsoft\Windows\RTC\PortRange
HKLM\Software\Policies\Microsoft\Windows\RTC\PortRange\Enabled (REG_DWORD) set to 1
HKLM\Software\Policies\Microsoft\Windows\RTC\PortRange\MinSipDynamicPort (REG_DWORD) set to 5354
HKLM\Software\Policies\Microsoft\Windows\RTC\PortRange\MaxSipDynamicPort (REG_DWORD) set to 65535

RTC 客户端 API 为其使用的每个套接字创建一个事件窗口,这样,支持大量客户端所需的句柄数量会变得非常大。由于 Windows 限制了可用于单个进程的句柄数,因而可能需要使用以下一项或多项技术以提高可缩放性。

  1. 当 RTC 客户端使用 TLS 进行传输时,所需句柄数只是使用 TCP 作为传输协议时所需句柄数的三分之一。
  2. 将 RTC 客户端分散到多个进程中时,每个进程的句柄数将减少。
  3. 通过更改这两个注册表项,可以增加每个进程可以使用的句柄数。
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\USERProcessHandleQuota set to 10000
HKLM\Software\Microsoft\Windows NT\CurrentVersion\Windows\USERPostMessageLimit (REG_DWORD) set to 10000

方案

这一部分介绍如何实现这三种服务器类应用程序方案。请注意,在所有这三种方案中,假定已经正确初始化了相应的 RTC 客户端 API 对象,并且已经建立了所有必要的会话。

方案 1:通知 bot

此应用程序允许客户端与一个通知 bot 一起注册,每次某个特定的股票价格发生变化时,该通知 bot 都将向客户端发送即时消息。以下代码用于等待股票价格变化事件的发生,然后触发 OnStockPriceChangeEvent 例程。

// 等待股票价格变化事件
while(GetMessage(&msg, NULL,0, 0))
{
TranslateMessage(&msg);
switch(msg.message)
{
case WM_EVENT_STOCKPRICE_CHANGE:
OnStockPriceChangeEvent();
break;
case default:
// 必要的代码
}
}

在 OnStockPriceChangeEvent 例程中,使用了一个 while 循环并通过 EnumerateBuddies 方法来遍历联系人列表。在该 while 循环中,将检查每个 IRTCBuddy 对象以查看相关联的联系人是否在线。如果联系人在线,将创建一个单方 IM 会话,然后生成相应的消息并发送给客户端。

// 遍历为此事件注册的好友
// 创建与非脱机联系人的单方
// 会话
// 发送 IM 消息
IRTCEnumBuddies * pEnum=NULL;
hr= m_pClientPresence-> EnumerateBuddies (&pEnum);
// 处理 hr != S_OK 错误
IRTCBuddy * pBuddy=NULL;
RTC_PRESENCE_STATUS enStatus;
IRTCSession *pSession=NULL;
while(pEnum->Next(1, (IRTCBuddy **)&pBuddy, NULL) == S_OK)
{
hr=pBuddy->get_Status (&enStatus);
// 处理 hr != S_OK 错误
if(enStatus  ! = RTCXS_PRESENCE_OFFLINE)
{
// 创建单方 IM 会话
hr=pClient->CreateSession (RTCST_IM,NULL, NULL,
NULL, &pSession);
// 处理 hr != S_OK 错误
BSTR bstrURI;
hr=pBuddy->get_get_PresentityURI(&bstrURI)
// 处理 hr != S_OK 错误
// 将该好友添加为参加者
hr=pSession->AddParticipant (bstrURI, NULL, NULL);
// 处理 hr != S_OK 错误
DWORD dwCookie; // 根据需要初始化 cookie
BSTR bstrMsg=::SysAllocString(
L"Stock price changed..");
// 发送消息
hr=pSession->SendMessage (NULL, bstrMsg, dwCookie);
// 处理 hr != S_OK 错误
}
}

收到 SessionOperationComplete 事件后,该应用程序将终止会话。应用程序会从使用 get_Session 属性传递给事件的 IRTCSessionOperationCompleteEvent 对象中提取一个指向 IRTCSession 对象的指针。然后将使用 Terminate 方法终止会话。

// 终止会话
IRTCSession *pSession=NULL;
hr=pEvent->get_Session(&pSession);
// 处理 hr != S_OK 错误
// 终止原因
pSession->Terminate(RTCTR_NORMAL);
// 释放所有引用
方案 2:交互式 bot

在此方案中,交互式 bot 将等待客户端的连接,然后接受来自客户端的消息。消息被解码后,将准备对该消息的响应并将响应返回给客户端。由于此应用程序可能要进行成百上千的同步 IM 会话,因而必须将工作分布到多个线程。

作为初始化进程的一部分,RTCClient 对象被设置为自动接受传入的会话。

// 在初始化阶段,将客户端设置为自动
// 响应连接
hr = m_pClient->put_AllowedPorts(RTCTR_TCP,
RTCLM_DYNAMIC);
hr = m_pClient->put_AnswerMode(RTCST_IM,
RTCAM_AUTOMATICALLY_ACCEPT);
hr = m_pClient->put_AnswerMode(RTCST_MULTIPARTY_IM,
RTCAM_AUTOMATICALLY_ACCEPT);
// 为每个呼叫处理 hr!=S_OK 错误

当接收到客户端的消息时,将调用 OnMessageEvent。此例程提取各种有关客户端消息的信息,包括会话对象和客户端的名称以及消息的类型和文本。如果消息类型为 RTCMSET_MESSAGE,将分析消息中的信息以确定应当如何对其进行处理。在本例中,消息必须以 QUERYDB 开头才能够被处理。

// 接受传入的呼叫并进行适当处理
HRESULT OnMessageEvent(IDispatch *pDispatch)
{
...
IRTCMessagingEvent* pME = NULL;
IRTCParticipant* pPart = NULL;
IRTCSession *pSession = NULL;
// 声明下面要使用的其他本地变量。
hr = pDispatch->QueryInterface(IID_IRTCMessagingEvent,
(LPVOID *)&pME);
// 处理 hr!=S_OK 错误
hr = pME->get_Session(&pSession);
// 处理 hr!=S_OK 错误
pME->get_Participant(&pPart);
// 处理 hr!=S_OK 错误
hr=pPart->get_Name(&bstrName);
// 处理 hr!=S_OK 错误
hr=pME->get_Message(&bstrMsg);
// 处理 hr!=S_OK 错误
// 检查消息事件的类型,这样我们便不会处理
// 状态消息
RTC_MESSAGING_EVENT_TYPE enMessageType;
pME->get_EventType(&enMessageType);
if (enMessageType == RTCMSET_MESSAGE)
{
if( wcsncmp(bstrMsg, L"QUERYDB ",
wcslen(L"QUERYDB ")))
{
// 我们想要存储该会话,而不是跨越线程来
// 传递它。因此我们可以将会话放在
// 某种列表结构中并接收一个 ID
DWORD dwId = m_pList.Add(pSession);
// 将命令传送给辅助线程
PostThreadMessage(g_dwWorkerThreadID, WM_QUERYDB,
(LPARAM) bstrMsg, (WPARAM) dwId);
}
}
// 释放所有引用并进行清理
...
}

这里没有在内部处理消息,而是将与消息相关的信息添加到要进行处理的列表中,并且向辅助线程传送了一条命令以表明有一条新消息正等待处理。这可以释放主线程,让它继续接受传入的 RTC 请求,从而使整个应用程序具有更好的响应性能。

辅助线程将等待要发送的消息。收到消息后,它会转换该消息并生成一个响应。然后,一个命令将传送给主 RTC 客户端线程,表明已经生成对该消息的响应并且应当将其发送给客户端。

// 辅助线程接收消息并生成
// 响应
while(GetMessage(&msg, NULL,0, 0))
{
TranslateMessage(&msg);
switch(msg.message)
{
case WM_QUERYDB:
BSTR bstrMessage = msg.lParam;
DWORD dwId = msg.wParam;
BSTR bstrResultString = NULL;
// 处理 bstrMsg 查询并获取 bstrResultString 中的
// 结果
PostThreadMessage(g_dwMainThreadID,
WM_QUERYRESPONSE, (LPARAM) bstrResultString,
(WPARAM) dwId);
break;
// 可能具有的任何其他自定义消息
default:
DispatchMessage(&msg);
}
}

当主线程从辅助线程接收到表明该响应已准备就绪可以发送的命令时,将从会话列表中检索该会话并将消息返回给客户端。

// 主线程将响应返回给客户端
while(GetMessage(&msg, NULL,0, 0))
{
TranslateMessage(&msg);
switch(msg.message)
{
case WM_QUERYRESPONSE:
BSTR bstrResultString = msg.lParam;
DWORD dwId = msg.wParam;
// 从我们的列表中检索会话:
IRTCSession *pSession = m_pList.GetSession(dwId);
pSession->SendMessage(NULL, bstrResultString, 0);
::SysFreeString(bstrResultString);
break;
// 可能具有的任何其他自定义消息
default:
DispatchMessage(&msg);
}
}
方案 3:基于 Web 的用户界面

构建基于 Web 的用户界面时,很重要的一点是要将 RTCClient 对象分布在多个线程上。在多个线程上分布 RTCClient 对象可以限制任何一个线程所必须完成的工作量,这使应用程序可以进行伸缩并处理大量客户端。在主线程中,请求被接收并被分配到将创建 RTCClient 对象的辅助线程中。

该例程将查找已分配的 RTCClient 对象少于 10 个的辅助线程。如果找不到已分配的 RTCClient 对象少于 10 个的辅助线程,将创建一个新线程。无论是哪一种情况,都会向该线程传递一条创建新客户端的命令。

// 主线程函数和回调
// 当一个新用户使用 Web 界面登录到
// 该服务时
HRESULT OnStartClient()
{
...
// 在本例中,我们将每个线程的客户端数量
// 限制为 10 个。可以根据需要调整此值以
// 优化性能
if (one of the worker threads has < 10 RTCClient
objects)
{
// 传送到辅助线程以创建新的
// RTCClient 客户端对象并将其登录
PostThreadMessage(dwWorkerThreadID, WM_CREATECLIENT,
...);
}
else
{
// 创建新线程
DWORD dwThreadID; // out 参数
_beginthreadex(NULL, 0, &UpdateWebUI, NULL, 0,
(unsigned int *) &dwThreadID);
PostThreadMessage(dwThreadID, WM_CREATECLIENT, ...);
}
}

主线程仍然负责更新 Web 用户界面,这样每个辅助线程将使用 UpdateCallBack 事件将信息发送回主线程。

// 将由辅助线程调用
// 回调以通知 RTC 事件
typedef void( * UpdateCallBack)( void * );
void UpdateWebUI(void *pUpdateInfo)
{
// 基于参数更新 UI
}

在辅助线程中,消息将被接收并被解码以确定应当执行的函数。例如,WM_CREATECLIENT 消息将导致调用 CreateAndLogon 例程。其他消息将触发辅助线程中的其他例程。同时还会保存对回调例程的引用,以便辅助线程可以向主线程传送命令以及相关信息并通过回调例程更新 Web 用户界面。

// 辅助线程进程和消息循环
unsigned int __stdcall RTCControllerThreadProc(
void *pParam )
{
// 将回调函数作为成员存储:
m_pCallBack = (UpdateCallBack) pParam;
while(GetMessage(&msg, NULL,0, 0))
{
TranslateMessage(&msg);
// 示例命令:登录此线程所拥有的
// 所有客户端
if (msg.message == WM_CREATECLIENT)
{
CreateAndLogon( );
}
// ... 处理其他命令
else
DispatchMessage(&msg);
}
}

CreateAndLogon 例程创建 RTCClient2 对象的新实例,然后执行初始化客户端会话所需的任何其他任务。

// 辅助线程的示例登录代码
HRESULT CreateAndLogon()
{
// 创建客户端并登录
IRTCClient *m_pClient = NULL;
hr = ::CoCreateInstance(CLSID_RTCClient, NULL,
CLSCTX_INPROC_SERVER, __uuidof(IRTCClient2),
(LPVOID *) &m_pClient);
// 检查 hr != S_OK
// 登录客户端...
return S_OK;
}

发生 RTC 事件时,将调用以下事件处理程序。此事件处理程序存在于辅助线程中,因为无法跨线程来使用 RTC 接口指针。根据所触发的事件,事件处理程序可以请求主线程通过回调例程将信息返回给客户端。

// 从 RTC API 处理事件
RTCEventHandler(RTC_EVENT enEvent, IDispatch *pDispatch)
{
// 发生了 RTC 事件。通知 Web UI。
// 这里将信息打包在一个单独的
// 数据结构中。不允许跨线程传递 IRTC* 接口
// 指针
*m_pCallBack(...);
}

小结

构建可缩放的应用程序通常基于以下思路,即查找应用程序不需要或不使用的功能,然后将其关闭。如果应用程序只为一个用户提供服务,零星地节省少量内存的作用并不大,但是当用户成百上千地增加时,这样做确实可以大大节省内存。使用本文中介绍的技术,可以设计可缩放的 RTC 应用程序以满足任何组织的需要。

相关链接

请参阅以下资源以获得更多信息:

原文:http://www.cnblogs.com/xiaoko/articles/458543.html


原文地址:https://www.cnblogs.com/HeroBeast/p/1576230.html