共用方式為


建立群組聊天應用程式

本主題提供相關程式碼範例,以取得使用對等群組 API 開發聊天應用程式的主要步驟,並提供每個 API 呼叫的內容。 不包含 UI 行為和整體應用程式結構。

注意

對等 SDK 中會提供完整的對等群組聊天範例應用程式。 本主題參考範例中提供的函式。

 

初始化群組

建構聊天應用程式的第一個步驟是呼叫具有最高支援版本的 PeerGroupStartup 來初始化對等群組基礎結構。 在此情況下,應用程式標頭檔中會定義 PEER_GROUP_VERSION 。 基礎結構實際支援的版本會在 peerVersion中傳回。

    PEER_VERSION_DATA peerVersion;

    hr = PeerGroupStartup(PEER_GROUP_VERSION, &peerVersion);
    if (FAILED(hr))
    {
        return hr;
    }

建立群組

如果沒有任何群組可供加入,或應用程式使用者想要建立新的群組,聊天應用程式必須能夠建立對等群組。 若要這樣做,您必須建立 PEER_GROUP_PROPERTIES 結構,並填入群組的初始設定,包括對等群組的分類器、易記名稱、建立者的對等名稱,以及存在存留期。 填入此結構之後,您會將其傳遞至 PeerGroupCreate

//-----------------------------------------------------------------------------
// Function: CreateGroup
//
// Purpose:  Creates a new group with the friendly name.
//
// Returns:  HRESULT
//
HRESULT CreateGroup(PCWSTR pwzName, PCWSTR pwzIdentity)
{
    HRESULT hr = S_OK;
    PEER_GROUP_PROPERTIES props = {0};

    if (SUCCEEDED(hr))
    {
        if ((NULL == pwzName) || (0 == *pwzName))
        {
            hr = E_INVALIDARG;
            DisplayHrError(L"Please enter a group name.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        CleanupGroup( );

        props.dwSize = sizeof(props);
        props.pwzClassifier = L"SampleChatGroup";
        props.pwzFriendlyName = (PWSTR) pwzName;
        props.pwzCreatorPeerName = (PWSTR) pwzIdentity;

        hr = PeerGroupCreate(&props, &g_hGroup);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to create a new group.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = PrepareToChat( );
    }

    return hr;
}

發出邀請

發出邀請時,必須取得受邀者的 GMC。 您可以在受邀者上以其身分識別名稱呼叫 PeerIdentityGetXML 來取得這些專案。 如果成功,則身分識別資訊 (包含具有 RSA 公開金鑰之 base-64 編碼憑證的 XML,) 寫入檔案 (外部位置,在此範例中) 邀請者可取得憑證,並使用它來建立邀請。

此時,必須建立邀請者傳遞邀請的機制。 這可以是電子郵件或其他安全的檔案交換方法。 在下列範例中,邀請會寫入檔案,然後傳送至受邀者的電腦。

//-----------------------------------------------------------------------------
// Function: SaveIdentityInfo
//
// Purpose:  Saves the information for an identity to a file.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT SaveIdentityInfo(PCWSTR pwzIdentity, PCWSTR pwzFile)
{
    PWSTR pwzXML = NULL;
    HRESULT hr = PeerIdentityGetXML(pwzIdentity, &pwzXML);

    if (FAILED(hr))
    {
        DisplayHrError(L"Unable to retrieve the XML data for the identity.", hr);
    }
    else
    {
        FILE *fp = NULL;
        errno_t err = 0;

        err = _wfopen_s(&fp, pwzFile, L"wb");
        if (err != 0)
        {
            hr = E_FAIL;
            DisplayHrError(L"Please choose a valid path", hr);
        }
        else
        {
            if (fputws(pwzXML, fp) == WEOF)
            {
                hr = E_FAIL;
                DisplayHrError(L"End of file error.", hr);
            }
            fclose(fp);
        }

        PeerFreeData(pwzXML);
    }

    return hr;
}

身分識別等邀請也會在外部發出。 在此範例中,邀請會寫入具有 fputws 的 檔案,其中受邀者可以取得它,並在呼叫 PeerGroupJoin時使用它。

//-----------------------------------------------------------------------------
// Function: CreateInvitation
//
// Purpose:  Creates an invitation file for an identity.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT CreateInvitation(PCWSTR wzIdentityInfoPath, PCWSTR wzInvitationPath)
{
    HRESULT hr = S_OK;
    WCHAR wzIdentityInfo[MAX_INVITATION] = {0};
    PWSTR pwzInvitation = NULL;
    errno_t  err  = 0;
    FILE *file = NULL;
        
    err = _wfopen_s(&file, wzIdentityInfoPath, L"rb");
    if (err != 0)
    {
        hr = E_FAIL;
        DisplayHrError(L"Please choose a valid path to the identity information file.", hr);
    }
    else
    {
        fread(wzIdentityInfo, sizeof(WCHAR), MAX_INVITATION, file);
        if (ferror(file))
        {
            hr = E_FAIL;
            DisplayHrError(L"File read error occurred.", hr);
        }
        fclose(file);
    }

    if (SUCCEEDED(hr))
    {
        ULONGLONG ulExpire; // adjust time using this structure
        GetSystemTimeAsFileTime((FILETIME *)&ulExpire);

        // 15days in 100 nanoseconds resolution
        ulExpire += ((ULONGLONG) (60 * 60 * 24 * 15)) * ((ULONGLONG)1000*1000*10);

        hr = PeerGroupCreateInvitation(g_hGroup, wzIdentityInfo, (FILETIME*)&ulExpire, 1, (PEER_ROLE_ID*) &PEER_GROUP_ROLE_MEMBER, &pwzInvitation);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to create the invitation.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        err = _wfopen_s(&file, wzInvitationPath, L"wb");
        if (err != 0)
        {
            hr = E_FAIL;
            DisplayHrError(L"Please choose a valid path to the invitation file.", hr);
        }
        else
        {
            if (fputws(pwzInvitation, file) == WEOF)
            {
                hr = E_FAIL;
                DisplayHrError(L"End of file error.", hr);
            }
            fclose(file);
        }
    }

    PeerFreeData(pwzInvitation);
    return hr;
}

加入對等群組

如果對等嘗試加入另一個對等所建立的對等群組,則需要來自該對等的邀請。 邀請是由外部進程或應用程式傳遞至受邀者,並儲存至本機檔案,在下列範例中指定為 pwzFileName。 邀請 XML Blob 會從檔案讀取到 wzInvitation, 並傳遞至 PeerGroupJoin

//-----------------------------------------------------------------------------
// Function: JoinGroup
//
// Purpose:  Uses the invitation to join a group with a specific identity.
//           Displays a message if there was an error.
//
// Returns:  HRESULT
//
HRESULT JoinGroup(PCWSTR pwzIdentity, PCWSTR pwzFileName)
{
    HRESULT hr = S_OK;
    WCHAR wzInvitation[MAX_INVITATION] = {0};
    FILE        *file = NULL;
    errno_t     err;

    err = _wfopen_s(&file, pwzFileName, L"rb");
    if (err !=  0)
    {
        hr = E_FAIL;
        DisplayHrError(L"Error opening group invitation file", hr);
        return hr;
    }
    else
    {
        fread(wzInvitation, sizeof(WCHAR), MAX_INVITATION, file);
        if (ferror(file))
        {
            hr = E_FAIL;
            DisplayHrError(L"File read error occurred.", hr);
        }
        fclose(file);

        hr = PeerGroupJoin(pwzIdentity, wzInvitation, NULL, &g_hGroup);
        if (FAILED(hr))
        {
            DisplayHrError(L"Failed to join group.", hr);
        }
    }

    if (SUCCEEDED(hr))
    {
        hr = PrepareToChat( );
    }

    return hr;
}

註冊對等事件

連線之前,您應該註冊與應用程式相關的每個對等事件。 在下列範例中,您會註冊下列事件:

  • PEER_GROUP_EVENT_RECORD_CHANGED。 由於記錄將用來包含公用聊天訊息,因此每次新增新的記錄時,都必須通知應用程式。 收到此對等事件時,事件資料會公開具有聊天訊息的記錄。 應用程式應該只註冊他們想要直接處理的記錄類型。
  • PEER_GROUP_EVENT_MEMBER_CHANGED。 當成員加入或離開對等群組時,必須通知應用程式,以便據以更新參與者清單。
  • PEER_GROUP_EVENT_STATUS_CHANGED。 對等群組狀態的變更必須傳達給應用程式。 只有在對等群組成員的狀態指出已連線到群組、與對等群組記錄資料庫同步處理,以及主動接聽記錄更新時,才會將對等群組成員視為可在對等群組內使用。
  • PEER_GROUP_EVENT_DIRECT_CONNECTION。 兩個成員與資料交換之間的私人訊息必須透過直接連線進行,因此應用程式必須能夠處理直接連線要求。
  • PEER_GROUP_EVENT_INCOMING_DATA。 起始直接連線之後,此對等事件會警示應用程式已收到私人訊息。
//-----------------------------------------------------------------------------
// Function: RegisterForEvents
//
// Purpose:  Registers the EventCallback function so it will be called for only
//           those events that are specified.
//
// Returns:  HRESULT
//
HRESULT RegisterForEvents(void)
{
    HRESULT hr = S_OK;
    PEER_GROUP_EVENT_REGISTRATION regs[] = {
        { PEER_GROUP_EVENT_RECORD_CHANGED, &RECORD_TYPE_CHAT_MESSAGE },
        { PEER_GROUP_EVENT_MEMBER_CHANGED, 0 },
        { PEER_GROUP_EVENT_STATUS_CHANGED, 0 },
        { PEER_GROUP_EVENT_DIRECT_CONNECTION, &DATA_TYPE_WHISPER_MESSAGE },
        { PEER_GROUP_EVENT_INCOMING_DATA, 0 },
    };

    g_hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (g_hEvent == NULL)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }
    else
    {
        hr = PeerGroupRegisterEvent(g_hGroup, g_hEvent, celems(regs), regs,  &g_hPeerEvent);
    }

    if (SUCCEEDED(hr))
    {
        if (!RegisterWaitForSingleObject(&g_hWait, g_hEvent, EventCallback, NULL, INFINITE, WT_EXECUTEDEFAULT))
        {
            hr = E_UNEXPECTED;
        }
    }

    return hr;
}

連線到對等群組

建立或加入群組並註冊適當的活動之後,就可以上線並開始使用中的聊天會話。 若要這樣做,您可以呼叫 PeerGroupConnect ,並傳入從 PeerGroupCreatePeerGroupJoin取得的群組控制碼。 呼叫此專案之後,聊天訊息將會以PEER_GROUP_EVENT_RECORD_CHANGED事件的形式接收。

取得對等群組成員的清單

取得連線到對等群組的成員清單很簡單:呼叫 PeerGroupEnumMembers 以擷取群組成員清單,然後反復呼叫 PeerGetNextItem ,直到擷取所有成員為止。 處理每個成員結構之後,您應該呼叫 PeerFreeData ,而且必須在處理完成時呼叫 PeerEndEnumeration 來關閉列舉。

//-----------------------------------------------------------------------------
// Function: UpdateParticipantList
//
// Purpose:  Update the list of partipants.
//
// Returns:  nothing
//
void UpdateParticipantList(void)
{
    HRESULT          hr = S_OK;
    HPEERENUM        hPeerEnum = NULL;
    PEER_MEMBER   ** ppMember = NULL;

    ClearParticipantList( );
    if (NULL == g_hGroup)
    {
        return;
    }

    // Retrieve only the members currently present in the group.
    hr = PeerGroupEnumMembers(g_hGroup, PEER_MEMBER_PRESENT, NULL, &hPeerEnum);
    if (SUCCEEDED(hr))
    {
        while (SUCCEEDED(hr))
        {
            ULONG cItem = 1;
            hr = PeerGetNextItem(hPeerEnum, &cItem, (PVOID **) &ppMember);
            if (SUCCEEDED(hr))
            {
                if (0 == cItem)
                {
                    PeerFreeData(ppMember);
                    break;
                }
            }

            if (SUCCEEDED(hr))
            {
                if (0 != ((*ppMember)->dwFlags & PEER_MEMBER_PRESENT))
                {
                    AddParticipantName((*ppMember)->pwzIdentity, (*ppMember)->pCredentialInfo->pwzFriendlyName);
                }
                PeerFreeData(ppMember);
            }
        }

        PeerEndEnumeration(hPeerEnum);
    }
}

傳送聊天訊息

在此範例中,聊天訊息會藉由將它放在PEER_RECORD結構的資料欄位中來傳送。 記錄會藉由呼叫 PeerGroupAddRecord新增至對等群組記錄,這會發佈該記錄,並在參與對等群組的所有對等上引發PEER_GROUP_EVENT_RECORD_CHANGED事件。

//-----------------------------------------------------------------------------
// Function: AddChatRecord
//
// Purpose:  This adds a new chat message record to the group.
//
// Returns:  HRESULT
//
HRESULT AddChatRecord(PCWSTR pwzMessage)
{
    HRESULT     hr = S_OK;
    PEER_RECORD record = {0};
    GUID        idRecord;
    ULONGLONG   ulExpire;
    ULONG cch = (ULONG) wcslen(pwzMessage);

    GetSystemTimeAsFileTime((FILETIME *) &ulExpire);

    // Calculate a 2 minute expiration time in 100 nanosecond resolution
    ulExpire += ((ULONGLONG) 60 * 2) * ((ULONGLONG)1000*1000*10);

    // Set up the record
    record.dwSize = sizeof(record);
    record.data.cbData = (cch+1) * sizeof(WCHAR);
    record.data.pbData = (PBYTE) pwzMessage;
    memcpy(&record.ftExpiration, &ulExpire, sizeof(ulExpire));

    PeerGroupUniversalTimeToPeerTime(g_hGroup, &record.ftExpiration, &record.ftExpiration);

    // Set the record type GUID
    record.type = RECORD_TYPE_CHAT_MESSAGE;

    // Add the record to the database
    hr = PeerGroupAddRecord(g_hGroup, &record, &idRecord);
    if (FAILED(hr))
    {
        DisplayHrError(L"Failed to add a chat record to the group.", hr);
    }

    return hr;
}

接收聊天訊息

若要接收聊天訊息,請為呼叫類似以下函式的 PEER_GROUP_EVENT_RECORD_CHANGED 事件建立回呼函式。 記錄的取得方式是在回呼函式中呼叫PeerGroupGetEventData 之前呼叫 PeerGroupGetEventData所收到的事件資料上呼叫PeerGroupGetRecord。 聊天訊息會儲存在此記錄 的資料 欄位中。

//-----------------------------------------------------------------------------
// Function: ProcessRecordChanged
//
// Purpose:  Processes the PEER_GROUP_EVENT_RECORD_CHANGED event.
//
// Returns:  nothing
//
void ProcessRecordChanged(PEER_EVENT_RECORD_CHANGE_DATA * pData)
{
    switch (pData->changeType)
    {
        case PEER_RECORD_ADDED:
            if (IsEqualGUID(&pData->recordType, &RECORD_TYPE_CHAT_MESSAGE))
            {
                PEER_RECORD * pRecord = {0};
                HRESULT hr = PeerGroupGetRecord(g_hGroup, &pData->recordId, &pRecord);
                if (SUCCEEDED(hr))
                {
                    DisplayChatMessage(pRecord->pwzCreatorId, (PCWSTR) pRecord->data.pbData);
                    PeerFreeData(pRecord);
                }
            }
            break;

        case PEER_RECORD_UPDATED:
        case PEER_RECORD_DELETED:
        case PEER_RECORD_EXPIRED:
            break;

        default:
            break;
    }
}