建立群組聊天應用程式
本主題提供相關程式碼範例,以取得使用對等群組 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 ,並傳入從 PeerGroupCreate 或 PeerGroupJoin取得的群組控制碼。 呼叫此專案之後,聊天訊息將會以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;
}
}