使用 Microsoft 实时通信 API 增强多客户端通信

使用 Microsoft 实时通信 API 增强多客户端通信


Jim Huang
Sr. Technical Marketing Engineer
Intel Corporation
2003年2月

适用于:
   Microsoft® Windows® XP

摘要:学习如何使用实时通信 (RTC) 客户端 API,结合出席信息、配置文件和好友列表等功能来创建社区并跟踪用户的可用性。本文以前一篇文章为基础,将多客户端通信与 Microsoft 实时通信 API 集成到一起。

下载 RTCSampleCode.zip。(请注意,在示例文件中,程序员的注释使用的是英文,本文中将其译为中文是为了便于读者理解。)

目录

  • 简介
  • 准备工作
  • 初始化出席信息和好友列表通知
  • 启用和检测出席信息
  • SIP 服务器注册
  • 创建 XML 配置文件
  • 创建好友列表和观察程序对象
  • 取消注册和禁用配置文件
  • 优化性能
  • 小结
  • 资源

简介

在前一篇文章 Integrating Rich Client Communications with the Microsoft Real-Time Communications API(英文)中,我们介绍了使用实时通信 (RTC) 客户端 API 将某些功能(例如音频/视频会议、即时消息和应用程序共享)集成到应用程序中是多么简单直接。

本文将进一步介绍使用 RTC 客户端 API 添加出席信息、配置文件和好友列表等功能来创建社区的简单性。您将学习使用第一篇文章中创建的 Windows XP 应用程序来实现此功能的步骤。

准备工作

本文假设您熟悉 XML 架构。有关初始化 RTC 对象的信息,请参阅第一篇文章。

本文附带的源代码有两个版本:

  • RTCSampleCode.exe 示例代码演示了启用音频/视频会议、即时消息、出席信息和好友列表的全部功能。
  • AVDConf2.exe 示例代码(可从 Intel® 的 Real-Time Communication Sample Application [英文] 页面获得)中包含的功能与 RTCSample.exe 示例代码包含的功能相同。另外还包含白板共享、应用程序共享、带有超线程技术检测的 Intel® Pentium® 4 处理器和 CPU 利用率指示器。AVDConf2.exe 代码使用了很多 RTCSample.exe 中的示例,RTC 代码节选自 Microsoft Software Developers Network Web 站点。
注意:超线程技术要求计算机系统中安装了 3.06 GHz 或更高频率的 Intel Pentium 4 处理器、使用该技术的芯片组和 BIOS 以及针对该技术进行了优化的操作系统。根据所使用的特定硬件和软件,性能会有所不同。有关详细信息,请参阅 Hyper-Threading Technology (英文)。

您需要安装:

  • Visual Studio® 6.0 SP5
  • Microsoft Platform SDK
  • SIP Server 或 Windows .NET Server Beta 3

会话启动协议服务器

目前,要将出席信息、配置文件和好友列表集成到应用程序中,需要使用 SIP 服务器或 Windows .NET Server Beta 3。需要出席信息(例如启用好友列表功能)时,要求使用 SIP 注册服务器。用户可以在服务器上注册其出席信息,并通过此服务检索其他人的出席信息。

服务器使用会话启动协议 (SIP) 及其相关协议 SIMPLE 作为底层通信协议。SIP 为多模式通信提供了出色的支持。SIP 和 SIMPLE 不仅是文本消息共享协议,而且可以管理声音、视频、应用程序共享等。

会话启动协议

SIP 协议用于在 IP 网络中启动会话并注册出席信息。会话可以是 PC 到 PC 的简单双向通信,也可以是协作的多媒体会议会话。SIP 是一种 Internet 工程任务组 (IETF) 信号处理协议,用于建立、操作和销毁会话。SIP 的主要用途是帮助会话启动者向各地可能的会话参加者发出邀请。SIP 被描述为“简单、可扩展”的 IP 电话信号处理协议。

初始化出席信息和好友列表通知

要接收出席信息、好友列表和配置文件的新事件通知,请设置以下事件过滤器掩码,以便应用程序从 RTC 层接收事件通知。这些事件掩码是第一个示例应用程序中设置的事件掩码的补充。

#define RTCEF_REGISTRATION_STATE_CHANGE     0x00000002
#define RTCEF_BUDDY                               0x00000100
#define RTCEF_WATCHER                             0x00000200
#define RTCEF_PROFILE                             0x00000400

注册所有事件的一种简单方法是使用 RTCEF_ALL 宏通知 RTC 层向应用程序发送所有事件。

   long lEventMask = RTCEF_ALL;

处理 RTC 事件

以下代码段显示了使用用户配置文件注册到 SIP 服务器、发送和接收出席信息和好友列表事件时需要处理的其他事件。接收到每个事件后,事件过滤器方法将使用适当的 RTC 接口处理收到的事件。

HRESULT CAVDConfDlg::OnRTCEvent(UINT message, WPARAM wParam, LPARAM lParam)
{
    IDispatch * pDisp = (IDispatch *)lParam;
    RTC_EVENT enEvent = (RTC_EVENT)wParam;
    HRESULT hr;

    // 基于 RTC_EVENT 类型,查询
    // 适当的事件接口并调用辅助
    // 方法来处理事件

    switch ( wParam )
    {
   ... .

   case RTCE_REGISTRATION_STATE_CHANGE:
            {
                IRTCRegistrationStateChangeEvent * pEvent = NULL;
                // 获取与当前会话关联的事件句柄。
   hr = pDisp->QueryInterface( IID_IRTCSessionStateChangeEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCSessionStateChangeEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }                                            
            }
   break;

   case RTCE_BUDDY:
            {
                IRTCBuddyEvent * pEvent = NULL;
    hr = pDisp->QueryInterface( IID_IRTCBuddyEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCBuddyEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }
   }
   break;

   case RTCE_WATCHER:
            {
                IRTCWatcherEvent * pEvent = NULL;
                hr = pDisp->QueryInterface( IID_IRTCWatcherEvent,
                                            (void **)&pEvent );

                if (SUCCEEDED(hr))
                {
                    OnRTCWatcherEvent(pEvent);
                    SAFE_RELEASE(pEvent);
                }            }
   break;
    }
   ... .
}

启用和检测出席信息

使用出席信息服务,用户可以跟踪联系人的出席状态、将此状态通知给联系人、通过注册服务器(维护联系人的当前位置信息)调用好友。位置可以是计算机或电话,将来还可以是移动电话、寻呼机或手持设备。

图 1:包含好友列表和出席状态的示例用户界面

下图显示了在 SIP 服务器中注册客户端并启用出席信息服务的高级步骤。

图 2:启用配置文件和出席信息服务的步骤

SIP 服务器注册

要启用出席信息服务,需要创建一个“配置文件”对象。配置文件对象是通过 IRTCClientProvisioning::CreateProfile 方法创建的。要创建配置文件对象,客户端应用程序需要创建一个符合装置架构的 XML 字符串。XML 架构的属性包括:

  • 装置设置 - 配置文件的唯一标识符。
  • 用户设置 - 用户的 URI、领域和登录帐户信息
  • 客户端设置 - 有关与通信链路无关的客户端应用程序信息。此信息是可选的。
  • 提供商设置 - 有关 Internet 电话服务提供商 (ITSP) 的信息。
  • SIP 服务器设置 - 指定可用的 SIP 服务器、SIP 服务器角色和服务器支持的会话类型。

要在 SIP 服务器上注册用户,客户端需要创建一个 XML 配置文件字符串,指示 RTC 客户端 API 如何与 SIP 服务器进行通信。创建 XML 字符串后,即可调用 IRTCClientProvisioning::CreateProfile() 方法创建一个配置文件对象。下一步是调用 IRTCClientProvisioning::EnableProfile() 方法在 RTC 服务器上注册用户,并指定配置文件应该在该服务器上注册的会话类型。注册类型可以是:允许传入计算机到计算机的会话 (RTCRF_REGISTER_INVITE_SESSIONS)、允许传入即时消息会话 (RTCRF_REGISTER_MESSAGE_SESSIONS)、允许传入观察程序 (RTCRF_REGISTER_PRESENCE) 或允许所有的注册类型 (RTCRF_REGISTER_ALL)。设置 RTCRF_REGISTER_PRESENCE 或 RTCRF_REGISTER_ALL 将通知注册服务器:客户端接受 SIP SUBSCRIBE 方法。这将允许用户通知其他用户他们的出席状态更改、获取其联系人出席状态更改的通知以及将出席信息添加到观察程序列表的其他人。

观察程序对象在好友列表中提供了用户状态。观察程序可以查询 Presentity 的状态,并得到好友列表中的用户状态更改的通知。观察程序还使用户能够通过 Presentity 的状态中断或允许其他人加入。

示例代码说明了注册用户以及启用出席信息和好友列表的步骤。

HRESULT CAVDConfDlg::DoSIPLogin(BSTR bXMLObj)
{
    HRESULT hr;

   ... .

    // 获取 RTC 客户端装置接口
    IRTCClientProvisioning * pProv = NULL;

    hr = m_pClient->QueryInterface(
            IID_IRTCClientProvisioning,
            (void **)&pProv);

    if (FAILED(hr))
    {
      // 查询接口失败
           return hr;
    }

    // 从 XML 装置文档创建 RTC 
    // 配置文件对象
    hr = pProv->CreateProfile(bXMLObj, &m_pProfile);

    SAFE_FREE_STRING(bXMLObj);

    if (FAILED(hr))
    {
        // CreateProfile 失败
        SAFE_RELEASE(pProv);
        return hr;
    }

    // 启用 RTC 配置文件对象
    hr = pProv->EnableProfile(m_pProfile, RTCRF_REGISTER_ALL);

    SAFE_RELEASE(pProv);

    if (FAILED(hr))
    {
        // EnableProfile 失败
        return hr;    
    }

    // 启用出席信息
    // 最好在启用配置文件之后立即启用出席信息,
    // 这样传入的观察程序就不会丢失。
   
   // 有关函数定义,请参阅以下 EnablePresence() 方法
    hr = EnablePresence(TRUE);

    if (FAILED(hr))
    {
        // DoEnablePresence 失败
        DoSIPLogoff();   // 有关函数示例,请参阅代码示例 AVDConf2.exe
        return hr;
    }


   return S_OK;
}

启用配置文件后,还应启用出席信息以防传入的观察程序事件丢失。EnablePresence() 方法还演示了创建好友列表的方法。

HRESULT CAVDConfDlg::EnablePresence(BOOL bEnable)
{
    IRTCClientPresence * pPresence = NULL;
    HRESULT hr;

    // 清理好友列表
    ClearBuddyList();

    // 获取 RTC 客户端出席信息接口
    hr = m_pClient->QueryInterface(
            IID_IRTCClientPresence,
            (void **)&pPresence);

    if (FAILED(hr))
    {
        // QueryInterface 失败
        return hr;
    }

    // 获取出席信息存储区的位置
    VARIANT varStorage;
    VariantInit(&varStorage);
    varStorage.vt = VT_BSTR;
    varStorage.bstrVal = SysAllocString(L"presence.xml");

    // 如果禁用出席信息,则将最新的
    // 出席信息数据副本保存到 presence.xml 文件。
    if (!bEnable && m_bPresenceEnabled)
    {
        hr = pPresence->Export(varStorage);

        if (FAILED(hr))
        {
            // 导出失败
            SAFE_RELEASE(pPresence);
            VariantClear(&varStorage);
            return hr;
        }
    }

    // 启用出席信息
    hr = pPresence->EnablePresence(
        bEnable ? VARIANT_TRUE : VARIANT_FALSE, varStorage);
    
    VariantClear(&varStorage);

    if (FAILED(hr))
    {
        // EnablePresence 失败
        SAFE_RELEASE(pPresence);
        return hr;
    }

    // 设置启用标志
    m_bPresenceEnabled = bEnable;

    // 如果禁用出席信息,则清理
    // 出席信息数据
    if (!bEnable)
    {
        // 清理好友
        IRTCEnumBuddies * pEnumBuddy = NULL;
        IRTCBuddy * pBuddy = NULL;

      if (FAILED(hr))
      {
            // 枚举好友失败
            SAFE_RELEASE(pPresence);
            return hr;
      }
        
      // 枚举用户的好友列表。      
      hr = pPresence->EnumerateBuddies(&pEnumBuddy);
   
        if (FAILED(hr))
        {
            // 枚举好友失败
            SAFE_RELEASE(pPresence);
            return hr;
        }

        while (pEnumBuddy->Next(1, &pBuddy, NULL) == S_OK)
        {
            pPresence->RemoveBuddy(pBuddy);

            SAFE_RELEASE(pBuddy);
        }

        SAFE_RELEASE(pEnumBuddy);

        // 清理观察程序
        IRTCEnumWatchers * pEnumWatcher = NULL;
        IRTCWatcher * pWatcher = NULL;

        hr = pPresence->EnumerateWatchers(&pEnumWatcher);
   
        if (FAILED(hr))
        {
            // 枚举观察程序失败
            SAFE_RELEASE(pPresence);
            return hr;
        }

        while (pEnumWatcher->Next(1, &pWatcher, NULL) == S_OK)
        {
            pPresence->RemoveWatcher(pWatcher);

            SAFE_RELEASE(pWatcher);
        }

        SAFE_RELEASE(pEnumWatcher);
    }

    SAFE_RELEASE(pPresence);

    return S_OK;
}

请注意 pPresence->EnablePresence() 函数的第二个参数,它需要一个 Variant 型的存储区来保留好友列表以及允许和禁止的观察程序列表。Variant 存储区可采用文件名 (BSTR)、DOMDocument 对象,或者支持 Istream、IsequentialStream 或 IpersistStream 的任意对象的形式。

在 RTC 服务器中注册后,应用程序接收 RTCE_REGISTRATION_STATE_CHANGE 事件,指示注册是完成还是失败。

出席信息服务可以在服务器注册您的配置文件之前启用。下图显示了注册前启用出席信息服务的方法。

这可以使用户在注册到服务器之前设置他们的状态。现在,用户可以注册到 SIP 服务器,而对观察程序可以显示为“离线”,并查看联系人状态。当用户要在不受干扰的情况下监视组的出席状态时,此功能非常有用。

图 3:注册前启用出席信息的步骤。

请注意,可以在设置出席状态后调用 CreateProfile() 方法。

下面是 XML 架构。

<?xml version="1.0" ?>
  
<Schema name="ProvisioningSchema.xml" 
        xmlns="urn:schemas-microsoft-com:xml-data" 
        xmlns:dt="urn:schemas-microsoft-com:datatypes">
     <comments>Schema Version MajorRevisionNumber = 1 
                              MinorRevisionNumber = 0
</comments> 
  
<ElementType name="provision" model="closed">
     <AttributeType name="key" required="yes" /> 
     <attribute type="key" /> 
     <AttributeType name="name" required="yes" /> 
     <attribute type="name" /> 
     <AttributeType name="expires" dt:type="dateTime.tz" /> 
     <attribute type="expires" /> 
     <element type="user" minOccurs="1" maxOccurs="1" /> 
     <element type="sipsrv" minOccurs="1" maxOccurs="*" /> 
     <element type="client" minOccurs="0" maxOccurs="1" /> 
     <element type="provider" minOccurs="0" maxOccurs="1" /> 
</ElementType>
  
<ElementType name="user" model="closed">
     <AttributeType name="uri" required="yes" /> 
     <attribute type="uri" /> 
     <AttributeType name="account" required="no" /> 
     <attribute type="account" /> 
     <AttributeType name="name" required="no" /> 
     <attribute type="name" /> 
     <AttributeType name="password" required="no" /> 
     <attribute type="password"   /> 
     <AttributeType name="realm" required="no" /> 
     <attribute type="realm" /> 
</ElementType>
  
<ElementType name="client" model="closed">
     <AttributeType name="name" required="yes" /> 
     <attribute type="name" /> 
     <AttributeType name="banner" 
                    dt:type="bool" 
                    required="no" /> 
     <attribute type="banner" /> 
     <AttributeType name="updates" 
                    dt:type="bool" 
                    required="no" /> 
     <attribute type="updates" /> 
     <AttributeType name="minver" 
                    dt:type="fixed.14.4" 
                    required="no"/>
     <attribute type="minver" /> 
     <AttributeType name="curver" 
                    dt:type="fixed.14.4" 
                    required="no"/>
     <attribute type="curver" /> 
     <AttributeType name="updateuri" 
                    dt:type="uri" 
                    required="no" />
     <attribute type="updateuri" /> 
     <element type="data" minOccurs="0" maxOccurs="1" />
</ElementType>
  
<ElementType name="data" model="open" /> 
  
<ElementType name="provider" model="closed" content="mixed">
     <AttributeType name="name" /> 
     <attribute type="name" /> 
     <AttributeType name="homepage" dt:type="uri" required="no" />
     <attribute type="homepage" /> 
     <AttributeType name="helpdesk" dt:type="uri" required="no" />
     <attribute type="helpdesk" /> 
     <AttributeType name="personal" dt:type="uri" required="no" /> 
     <attribute type="personal" /> 
     <AttributeType name="calldisplay" dt:type="uri" required="no" /> 
     <attribute type="calldisplay" /> 
     <AttributeType name="idledisplay" dt:type="uri" required="no" /> 
     <attribute type="idledisplay" /> 
     <element type="data" /> 
</ElementType>
  
<ElementType name="sipsrv" model="closed" content="mixed">
     <AttributeType name="addr" required="yes" /> 
     <attribute type="addr" /> 
     <AttributeType name="protocol" 
                    dt:type="enumeration" 
                    dt:values="TCP UDP TLS" 
                    required="yes" /> 
     <attribute type="protocol" /> 
     <AttributeType name="auth" 
                    dt:type="enumeration" 
                    dt:values="basic digest" 
                    required="no" /> 
     <attribute type="auth" /> 
     <AttributeType name="role" 
                    dt:type="enumeration" 
                    dt:values="proxy registrar" 
                    required="yes" /> 
     <attribute type="role" /> 
     <element type="session" minOccurs="0" maxOccurs="*" /> 
</ElementType>
  
<ElementType name="session" model="closed">
     <AttributeType name="party" 
                    dt:type="enumeration" 
                    dt:values="first third" /> 
     <attribute type="party" /> 
     <AttributeType name="type" 
                    dt:type="enumeration" 
                    dt:values="pc2pc pc2ph ph2ph im" /> 
     <attribute type="type" /> 
</ElementType>
  
</Schema>

创建 XML 配置文件

以下代码演示如何创建 XML 配置文件字符串。

HRESULT CSIPLogin::CreateXMLProvision(LPSTR szURI, LPSTR szSIPIP, 
                  LPSTR szTransport, BSTR *bstrBuf)
{
   ... .
    
   // 生成 XML 装置文档
      wsprintf(szBuf, "<provision key=/"AVDConf_2/" name=/"AVDConf_2/">"
           "<user uri=/"%s/" account=/"/" password=/"/" realm=/"%s/" />"
           "<sipsrv addr=/"%s/" protocol=/"%s/" %s role=/"proxy/">"
           "<session party=/"first/" type=/"pc2pc/" />"
           "<session party=/"first/" type=/"pc2ph/" />"
           "<session party=/"first/" type=/"im/" />"
           "</sipsrv>"
           "<sipsrv addr=/"%s/" protocol=/"%s/" %s role=/"registrar/"/>"
           "</provision>",
           szURIBuf, szRealm,
           szSIPIP, szTransport, bBasicAuth ? "auth=/"basic/"" : "",
          szSIPIP, szTransport, bBasicAuth ? "auth=/"basic/"" : ""
        );

   ... .

   return S_OK;
}

在本文前面显示的示例应用程序和代码片段中,无须包括帐户用户 ID 和密码,因为 SIP 服务器不需要它。但是,如果知道 SIP 服务器需要登录帐户,则可以在配置文件字符串中包括用户 ID 和密码,以便登录到 SIP 服务器。注册需要的信息包括用户的统一资源标识符 (URI)、领域或域、RTC 服务器 IP、验证方法以及用于与服务器通信的传输协议。支持的传输协议包括 TCPUDPTLS。使用的 SIP 服务器同时支持“基本”和“摘要”身份验证。如果是“基本”身份验证,则传输协议必须是 TLS(出于安全考虑)。

创建好友列表和观察程序对象

注册配置文件并启用出席信息后,向好友列表添加新用户将非常简单。使用 IRTCClientPresence 接口(提供启用出席的方法)、添加好友、删除好友、枚举观察程序、设置本地出席状态、确定应用程序处理新观察程序中订阅的方法以及设置隐私模式。本示例代码未演示如何实现隐私模式,但值得注意的是,通过该功能用户可以创建一个允许呼叫的用户的离散列表。

    // 获取 RTC 客户端出席信息接口
    IRTCClientPresence * pPresence = NULL;

    hr = m_pClient->QueryInterface(
            IID_IRTCClientPresence,
            (void **)&pPresence);

    if (FAILED(hr))
    {
        // QueryInterface 失败
      char szBuf[256];

      wsprintf (szBuf, "Failed to Query Presence Interface/nErr = 0x%x", hr );
      MessageBox ( szBuf );
        return hr;
    }

    // 添加好友
    IRTCBuddy * pBuddy = NULL;

    hr = pPresence->AddBuddy(
            bstrURI,
            bstrName,
            NULL,
            VARIANT_TRUE,
            NULL,
            0,
            &pBuddy);

    SAFE_RELEASE(pPresence);

    if (FAILED(hr))
    {
        // Addbuddy 失败          
        SAFE_RELEASE(pBuddy);
      char szBuf[256];

      wsprintf (szBuf, "Failed to Add Buddy to List./nErr = 0x%x", hr );
      MessageBox ( szBuf );
        return hr;
    }

    // 更新好友列表条目
    UpdateBuddyList(pBuddy);
    SAFE_RELEASE(pBuddy);

如果成功创建了新的好友,AddBuddy() 方法将返回一个指针,该指针指向新创建的好友列表中的 IRTCBuddy 接口。使用 IRTCBuddy 接口,客户端应用程序可以获取好友的出席 URI、好友名称、好友状态、永久类型以及与好友出席相关的私人数据。

取消注册和禁用配置文件

调用 IRTCClientProvisioning::DisableProfile() 方法取消注册 SIP 服务器中的用户。在调用 DisableProfile() 方法后,请务必释放配置文件对象。

优化性能

在前一篇文章 Integrating Rich Client Communications with the Microsoft Real-Time Communications API(英文)中,我们说明了运行示例 RTC 应用程序时 CPU 的使用情况。在基于 Pentium 4 处理器的系统中,后台任务要占用大量的资源。但是,应用程序的响应性能主要受其体系结构的影响。要解决此问题,需要创建一些线程,让它们并行并为其他需要立即关注的活动提供服务。在带有超线程技术和 Windows XP Service Pack 1 (SP1) 的 Pentium 4 处理器上运行多线程应用程序,能够大大改善应用程序的响应性能,并使应用程序能够更有效地执行多个任务。

Windows XP SP1 将带有超线程技术的 Pentium 4 处理器看作两个逻辑处理器,因而与单个逻辑 CPU 相比,Windows XP 可以承担两倍的工作。

图 4:使用超线程技术(左)和未使用超线程技术(右)运行的示例用户界面

图 4 说明了后台运行磁盘清理实用程序时,使用和不使用超线程技术的 Pentium 4 处理器的 CPU 使用情况对比。

小结

通过实时通信 (RTC) API,可以生成全功能的会议和协作工具,不管是从计算机到计算机、从计算机到电话还是从电话到电话的通信。在前一篇文章中,我们说明了如何使用即时消息和应用程序共享快速开发音频和视频会议应用程序。在本文中,我们进一步扩展到包括出席信息和好友列表功能,以创建社区和跟踪人员的可用性。结合大量的 RTC API 和 Microsoft 实时通信服务器,您可以生成复杂、有效的协作通信工具,此工具能够增加跨站点团队的工作效率。

使用 RTC API 开发并运行在 Pentium 4 处理器(带有超线程技术和 Windows XP SP1)中的通信应用程序,在同时执行多项任务时可以实现较高的通信速度和改善的响应性能。

资源

Intel® Developer Services(英文)

Intel Pentium 4 Processor with Hyper-Threading Technology(英文)

Threading(英文)提示

Integrating Rich Client Communications with the Microsoft Real-Time Communications API(英文)

Integrating Windows Real-Time Communications into Applications(英文)

Q&A:Instant Messaging Milestone for Real-Time Communications Strategy (英文),Press Pass,Microsoft Corporation,2002 年 12 月

Next Stop, "Greenwich":Enterprise IM Takes Shape with Platform Roadmap(英文),Press Pass,Microsoft Corporation,2002 年 10 月

Microsoft Platform SDK: Real-time Communications (RTC) Client(英文)

Microsoft Platform SDK:实时通信:Sample XML Profiles(英文)

你可能感兴趣的:(windows,xml,api,Microsoft,服务器,null)