Erstellen einer Gruppenchatanwendung
Dieses Thema enthält relevante Codebeispiele für die hauptschritte der Entwicklung einer Chatanwendung mit der Peer Grouping-API und stellt einen Kontext für jeden API-Aufruf bereit. Benutzeroberflächenverhalten und allgemeine Anwendungsstruktur sind nicht enthalten.
Hinweis
Die vollständige Peergruppenchat-Beispielanwendung wird im Peer SDK bereitgestellt. Dieses Thema verweist auf Funktionen, die im Beispiel bereitgestellt werden.
Initialisieren einer Gruppe
Der erste Schritt beim Erstellen einer Chatanwendung besteht im Initialisieren der Peergruppierungsinfrastruktur durch Aufrufen von PeerGroupStartup mit der höchsten unterstützten Version. In diesem Fall wird PEER _ GROUP _ VERSION in der Anwendungsheaderdatei definiert. Die version, die tatsächlich von der Infrastruktur unterstützt wird, wird in peerVersion zurückgegeben.
PEER_VERSION_DATA peerVersion;
hr = PeerGroupStartup(PEER_GROUP_VERSION, &peerVersion);
if (FAILED(hr))
{
return hr;
}
Erstellen einer Gruppe
Eine Chatanwendung muss in der Lage sein, eine Peergruppe zu erstellen, wenn keine Gruppe für den Beitritt verfügbar ist oder wenn der Anwendungsbenutzer eine neue erstellen möchte. Hierzu müssen Sie eine PEER _ GROUP _ PROPERTIES-Struktur erstellen und mit den anfänglichen Einstellungen für die Gruppe auffüllen, einschließlich des Klassifizierers für die Peergruppe, des Benutzernamens, des Peernamens des Erstellers und der Anwesenheitslebensdauer. Nachdem diese Struktur aufgefüllt wurde, übergeben Sie sie an 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;
}
Ausstellen von Einladungen
Beim Ausstellen einer Einladung müssen die GMCs der eingeladenen Personen erhalten werden. Diese können durch Aufrufen von PeerIdentityGetXML für die eingeladene Person mit ihrem Identitätsnamen ermittelt werden. Bei Erfolg werden die Identitätsinformationen (der XML-Code, der das Base64-codierte Zertifikat mit dem öffentlichen RSA-Schlüssel enthält) an einen externen Speicherort (in diesem Beispiel eine Datei) geschrieben, an dem der einladende Benutzer es abrufen und zum Erstellen einer Einladung verwenden kann.
An diesem Punkt muss ein Mechanismus für die Übermittlung der Einladung an den eingeladenen Benutzer eingerichtet werden. Dies kann eine E-Mail oder eine andere sichere Methode für den Dateiaustausch sein. Im folgenden Beispiel wird die Einladung in eine Datei geschrieben, die dann auf den Computer des Eingeladenen übertragen werden kann.
//-----------------------------------------------------------------------------
// 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;
}
Einladungen wie Identitäten werden auch extern ausgegeben. In diesem Beispiel wird die Einladung in eine Datei mit fputws geschrieben, wo der Eingeladene sie abrufen und verwenden kann, wenn sie PeerGroupJoin aufruft.
//-----------------------------------------------------------------------------
// 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;
}
Beitreten zu einer Peergruppe
Wenn der Peer versucht, einer Von einem anderen Peer erstellten Peergruppe bei beitritt, benötigt er eine Einladung von diesem Peer. Einladungen werden von einem externen Prozess oder einer externen Anwendung an den Eingeladenen übermittelt und in einer lokalen Datei gespeichert, die im folgenden Beispiel als pwzFileName angegeben ist. Das EINLADUNGS-XML-Blob wird aus der Datei in wzInvitation gelesen und an PeerGroupJoin übergeben.
//-----------------------------------------------------------------------------
// 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;
}
Registrieren für Peerereignisse
Bevor Sie eine Verbindung herstellen, sollten Sie sich für jedes Peerereignis registrieren, das für die Anwendung relevant ist. Im folgenden Beispiel registrieren Sie sich für die folgenden Ereignisse:
- DER _ _ PEERGRUPPENEREIGNISDATENSATZ _ _ WURDE GEÄNDERT. Da Datensätze verwendet werden, um öffentliche Chatnachrichten zu enthalten, muss die Anwendung jedes Mal benachrichtigt werden, wenn eine neue hinzugefügt wird. Wenn dieses Peerereignis empfangen wird, machen die Ereignisdaten den Datensatz mit der Chatnachricht verfügbar. Anwendungen sollten sich nur für die Datensatztypen registrieren, die sie direkt verarbeiten möchten.
- _ _ PEERGRUPPENEREIGNISMITGLIED _ _ GEÄNDERT. Die Anwendung muss benachrichtigt werden, wenn Mitglieder der Peergruppe beitreten oder diese verlassen, damit die Liste der Teilnehmer entsprechend aktualisiert werden kann.
- DER _ _ PEERGRUPPENEREIGNISSTATUS _ _ WURDE GEÄNDERT. Änderungen am Peergruppenstatus müssen der Anwendung übermittelt werden. Ein Peergruppenmitglied gilt nur dann als innerhalb der Peergruppe verfügbar, wenn sein Status angibt, dass es mit der Gruppe verbunden ist, mit der Peergruppendatensatz-Datenbank synchronisiert ist und aktiv auf Aufzeichnungsupdates lausiert.
- PEER _ GROUP _ EVENT _ DIRECT _ CONNECTION. Private Nachrichten zwischen zwei Mitgliedern und der Datenaustausch müssen über eine direkte Verbindung durchgeführt werden, sodass die Anwendung in der Lage sein muss, direkte Verbindungsanforderungen zu verarbeiten.
- EINGEHENDE _ DATEN ZU _ _ _ PEERGRUPPENEREIGNIS. Nachdem eine direkte Verbindung initiiert wurde, warnt dieses Peerereignis die Anwendung, dass eine private Nachricht empfangen wurde.
//-----------------------------------------------------------------------------
// 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;
}
Herstellen einer Verbindung mit einer Peergruppe
Nachdem Sie die Gruppe erstellt oder beigetreten und sich für die entsprechenden Ereignisse registriert haben, können Sie online gehen und eine aktive Chatsitzung starten. Rufen Sie hierzu PeerGroupConnect auf, und übergeben Sie das Gruppenhandl aus PeerGroupCreate oder PeerGroupJoin. Nach dem Aufrufen dieses Aufrufs werden Chatnachrichten als PEER _ GROUP _ EVENT RECORD _ _ CHANGED-Ereignisse empfangen.
Abrufen einer Liste von Peergruppenmitgliedern
Das Abrufen einer Liste von Mitgliedern, die mit der Peergruppe verbunden sind, ist einfach: Rufen Sie PeerGroupEnumMembers auf, um die Liste der Gruppenmitglieder abzurufen, und rufen Sie dann PeerGetNextItem iterativ auf, bis alle Mitglieder abgerufen wurden. Sie sollten PeerFreeData aufrufen, nachdem Sie die einzelnen Memberstrukturen verarbeitet haben, und Sie müssen die Enumeration schließen, indem Sie PeerEndEnumeration aufrufen, wenn die Verarbeitung abgeschlossen ist.
//-----------------------------------------------------------------------------
// 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);
}
}
Senden einer Chatnachricht
In diesem Beispiel wird eine Chatnachricht gesendet, indem sie im Datenfeld einer PEER _ RECORD-Struktur platziert wird. Der Datensatz wird den Peergruppendatensätzen durch Aufrufen von PeerGroupAddRecordhinzugefügt, wodurch er veröffentlicht und das PEER GROUP EVENT RECORD CHANGED-Ereignis für alle _ _ _ Peers, die an der Peergruppe teilnehmen, aufgerufen _ wird.
//-----------------------------------------------------------------------------
// 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;
}
Empfangen einer Chatnachricht
Um die Chatnachricht zu empfangen, erstellen Sie eine Rückruffunktion für das PEER GROUP EVENT RECORD CHANGED-Ereignis, das eine Funktion aufruft, die _ _ der folgenden _ _ ähnelt. Der Datensatz wird durch Aufrufen von PeerGroupGetRecord für die Ereignisdaten ermittelt, die durch einen vorherigen Aufruf von PeerGroupGetEventData in der Rückruffunktion empfangen wurden. Die Chatnachricht wird im Datenfeld dieses Datensatzes gespeichert.
//-----------------------------------------------------------------------------
// 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;
}
}