vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 3

Tag 20

Internet und Netzwerke

Immer mehr Anwendungen besitzen die Fähigkeit, mit anderen Anwendungen über Netzwerke, einschließlich des Internet, zu kommunizieren. Das ist nicht zuletzt auf die explosionsartige Zunahme der Popularität des Internet zurückzuführen. Microsoft hat mit Windows NT und Windows 95 die Netzwerkfähigkeiten in seine Betriebssysteme integriert und damit zu einer allgemeinen Verbreitung in allen Arten von Anwendungen beigetragen.

Einige Anwendungen führen einfache Netzwerkaufgaben aus. Beispielsweise prüfen sie eine Website, ob irgendwelche Updates für das Programm vorhanden sind, und bieten dann dem Benutzer die Option, das Programm mit diesen Updates auf den neuesten Stand zu bringen. Verschiedene Textverarbeitungen formatieren Dokumente als Webseiten und geben damit dem Benutzer die Möglichkeit, die Seiten auf den Webserver zu laden. Es gibt auch Computerspiele, bei denen Benutzer auf der ganzen Welt gegeneinander spielen können und nicht nur für sich selbst am Computer werkeln müssen.

Anwendungen können eine Vielzahl von Netzwerkfunktionen aufweisen, und sie sind alle um die Winsock-Schnittstelle herum aufgebaut. Wenn Sie sich mit der Winsock- Schnittstelle auskennen und wissen, wie man Programme mit der Winsock-Schnittstelle und den MFC-Winsock-Klassen erstellt, eröffnet sich Ihnen dieses wunderbare Reich der Anwendungsprogrammierung, so daß Sie Ihre Programmiermöglichkeiten beträchtlich erweitern können.

Heute lernen Sie, ...

Wie funktionieren Netzwerkübertragungen?

Die meisten Anwendungen, die über ein Netzwerk kommunizieren, ob es sich nun um das Internet oder ein kleines lokales Netzwerk handelt, arbeiten nach den gleichen Prinzipien und bauen auf der gleichen Funktionalität auf, um die Datenübertragung zu realisieren. Eine Anwendung wartet auf dem einen Computer darauf, daß eine andere Anwendung eine Verbindung öffnet. Die erste Anwendung »horcht« auf diese Verbindungsanforderung, genau wie man auf das Klingeln des Telefons hört, wenn man einen Anruf erwartet.

In der Zwischenzeit versucht eine andere Anwendung, höchstwahrscheinlich (aber nicht notwendigerweise) auf einem anderen Computer, sich mit der ersten Anwendung zu verbinden. Dieser Versuch, eine Verbindung zu öffnen, entspricht dem Anrufen eines anderen Telefonteilnehmers. Man wählt die Nummer und hofft, daß der andere das Telefonklingeln am anderen Ende der Leitung hört. Als Anrufender müssen Sie die Rufnummer des Angerufenen kennen. Wenn Sie die Nummer nicht wissen, können Sie sie im Telefonbuch anhand des Namens ermitteln. Analog dazu muß die Anwendung, die eine Verbindung zur ersten Anwendung herzustellen versucht, den Netzwerkstandort - oder die Adresse - der ersten Anwendung kennen.

Nachdem eine Verbindung zwischen den beiden Anwendungen hergestellt ist, lassen sich Nachrichten in beiden Richtungen zwischen den Anwendungen austauschen - auch hier wieder die Analogie zu einem Telefongespräch. Diese Verbindung stellt einen bidirektionalen Kommunikationskanal dar, bei dem beide Seiten Informationen senden können (siehe Abbildung 20.1).

Abbildung 20.1:
Der grundlegende Socket-Verbindungsprozeß

Schließlich beendet eine Seite (oder beide Seiten) die Konversation. Die Verbindung wird geschlossen, so wie Sie den Telefonhörer auflegen, wenn Sie das Gespräch beenden. Nachdem die Verbindung von einer der beiden Seiten geschlossen wurde, erkennt die andere Seite diesen Zustand und schließt ihrerseits die Verbindung, so wie Sie feststellen, daß der andere Telefonteilnehmer aufgelegt hat oder wenn Sie aus anderen Gründen unterbrochen wurden. Grundsätzlich laufen Netzwerkverbindungen zwischen zwei oder mehr Anwendungen nach diesem stark vereinfacht dargestellten Schema ab.

Diese grundlegende Beschreibung bezieht sich auf Netzwerkverbindungen, die nach dem TCP/IP-Protokoll arbeiten, das hauptsächlich als Netzwerkprotokoll im Internet zum Einsatz kommt. Viele andere Netzwerkprotokolle weichen etwas von dieser Beschreibung ab. Andere Protokolle wie etwa das UDP-Protokoll verhalten sich eher wie Rundfunkübertragungen, wo es keine Verbindung zwischen den beiden Anwendungen gibt. Die eine Anwendung sendet Nachrichten, und die andere ist dafür verantwortlich, den Empfang aller dieser Nachrichten sicherzustellen. Diesen Protokollen können wir uns im Rahmen der heutigen Lektion allerdings nicht widmen. Wenn Sie mehr über Netzwerkprotokolle und deren Arbeitsweise erfahren möchten, sollten Sie sich mit der einschlägigen Literatur beschäftigen. Gerade zum Thema Kommunikation gibt es zahlreiche Bücher, die sich auch mit den verschiedenartigen Internet-Anwendungen befassen und die Kommunikation über die eingerichteten Verbindungen beschreiben.

Sockets, Anschlüsse und Adressen

Das grundlegende Objekt, über das eine Anwendung den größten Teil der Netzwerkkommunikation realisiert, bezeichnet man als Socket. Sockets wurden zuerst unter Unix an der Berkeley-Universität entwickelt. Ziel war es, daß Anwendungen den Hauptteil der Netzwerkkommunikation zwischen Anwendungen in der gleichen Weise abwickeln können wie das Lesen und Schreiben von Dateien. Seit dieser Zeit haben sich Sockets natürlich etwas verändert, wobei aber die grundsätzliche Arbeitsweise gleich geblieben ist.

Während der Tage von Windows 3.x, bevor die Netzwerke in das Betriebssystem Windows integriert wurden, konnte man Netzwerkprotokolle für die Datenübertragung von zahlreichen Anbietern erwerben. Jede dieser Firmen ging etwas anders an die Netzwerkübertragung zwischen Anwendungen heran. Im Ergebnis mußte jede Anwendung, die Netzwerkverbindungen realisieren wollte, eine Liste von unterschiedlichen Netzwerkprogrammen beachten, mit denen die Anwendung arbeiten konnte. Viele Anwendungsentwickler waren nicht gerade glücklich mit dieser Situation. Das führte letztendlich dazu, daß die Netzwerkfirmen - einschließlich Microsoft - zusammenkamen und das Winsock-API (Windows Sockets) entwickelten. Damit steht allen Anwendungsentwicklern eine einheitliche Programmierschnittstelle (API - Anwendungsprogrammierschnittstelle) zur Verfügung, die unabhängig von der verwendeten Netzwerksoftware ist.

Wenn man eine Datei lesen oder schreiben möchte, verwendet man ein Dateiobjekt, das auf die Datei zeigt. Obwohl dies in den meisten Visual-C++-Anwendungen, die Sie bisher erstellt haben, vor Ihnen verborgen geblieben ist, mußten Sie für das gestern erzeugte ActiveX-Steuerelement diese Schritte zum Anlegen des Dateiobjekts zum Lesen und Schreiben durcharbeiten. Ein Socket ist damit vergleichbar. Es handelt sich um ein Objekt, das man zum Lesen und Schreiben von Nachrichten verwendet, die zwischen den Anwendungen reisen.

Um eine Socket-Verbindung zu einer anderen Anwendung herzustellen, sind andere Informationen erforderlich als beim Öffnen einer Datei. Wenn man eine Datei öffnen möchte, muß man den Dateinamen und den Standort der Datei kennen. Bei einer Socket-Verbindung muß man wissen, auf welchem Computer die andere Anwendung läuft und über welchen Anschluß die Anwendung hört. Ein Anschluß entspricht etwa einem Telefonanschluß, während die Computeradresse mit der Telefonnummer vergleichbar ist. In gleicher Weise werden Anschlüsse benutzt, um Netzwerkübertragungen weiterzuleiten (siehe Abbildung 20.2). Wie für eine Telefonnummer gibt es auch Mittel, um die Anschlußnummer zu ermitteln, wenn man sie nicht bereits kennt. Das erfordert aber, daß der Computer mit den Angaben konfiguriert ist, über welchen Anschluß die verbindende Anwendung hört. Wenn man die falsche Computeradresse - oder Anschlußnummer - spezifiziert, erhält man gegebenenfalls eine Verbindung zu einer anderen Anwendung. Das entspricht dem vom Telefon bekannten »falsch verbunden«. Vielleicht erhalten Sie aber auch überhaupt keine Antwort, wenn es am anderen Ende der Leitung keine Anwendung gibt, die hört.

Abbildung 20.2:
Anschlüsse dienen dazu, die Netzwerkverbindungen zur richtigen Anwendung weiterzuleiten.

Nur eine Anwendung kann auf einem einzelnen Computer an einem bestimmten Anschluß hören. Obwohl auch mehrere Anwendungen auf ein und demselben Computer gleichzeitig auf Verbindungsgesuche warten können, müssen diese Anwendungen dazu verschiedene Anschlüsse verwenden.

Einen Socket erstellen

Wenn Sie Anwendungen mit Visual C++ erstellen, können Sie auf die MFC-Winsock- Klassen zurückgreifen, um die Fähigkeiten zur Netzwerkübertragung ziemlich einfach zu realisieren. Die Basisklasse CAsyncSocket stellt vollständige, ereignisgesteuerte Verbindungen bereit. Sie können Ihre eigene abgeleitete Socket-Klasse erstellen, die jedes dieser Ereignisse abfangen und darauf antworten kann.

Diese Behandlung der Socket-Kommunikation geht davon aus, daß Sie im Anwendungs-Assistenten die Optionen für das Hinzufügen von Windows Sockets einschalten. Damit wird die unterstützende Funktionalität in die Anwendung aufgenommen, auf die wir hier nicht eingehen.

Um einen Socket zu erstellen, den Sie in Ihrer Anwendung einsetzen können, müssen Sie zunächst eine Variable von CAsyncSocket (oder Ihrer davon abgeleiteten Klasse) als Klassenelement für eine der Hauptanwendungsklassen deklarieren:

class CMyDlg : public CDialog
{
.
.
.
private:
CAsyncSocket m_sMySocket;
};

Bevor Sie das Socket-Objekt verwenden können, müssen Sie dessen Create-Methode aufrufen. Diese Methode erstellt den eigentlichen Socket und bereitet ihn zum Einsatz vor. Wie Sie die Methode Create aufrufen, hängt von der Verwendungsart des Sokkets ab. Wollen Sie - als Client - einen Ruf an eine andere Anwendung absetzen, um eine Verbindung zu dieser Anwendung herzustellen, dann brauchen Sie der Create- Methode keinerlei Parameter übergeben:

if (m_sMySocket.Create())
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

Wenn allerdings der Socket auf den Verbindungswunsch einer anderen Anwendung hören soll, um sich mit dieser Anwendung zu verbinden, und dabei - als Server - auf den Ruf wartet, dann müssen Sie zumindest die Nummer des Anschlusses übergeben, auf dem der Socket hören soll:

if (m_sMySocket.Create(4000))
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

In den Aufruf der Create-Methode können Sie weitere Parameter einbinden, wie etwa den Typ des zu erzeugenden Sockets, die Ereignisse, auf die der Socket reagieren soll, und die Adresse, die der Socket abhören soll (falls der Computer über mehrere Netzwerkkarten verfügt). Alle diese Optionen erfordern ein etwas tiefergehendes Verständnis der Sockets, als wir heute behandeln können.

Eine Verbindung herstellen

Nachdem Sie einen Socket erstellt haben, können Sie damit eine Verbindung öffnen. Das Öffnen einer einzelnen Verbindung verläuft in drei Schritten. Zwei davon finden auf dem Server - der Anwendung, die auf die Verbindung prüft - statt, und der dritte Schritt läuft auf dem Client ab, der den Anruf ausführt.

Auf der Client-Seite ist einfach die Methode Connect aufzurufen. Der Client muß zwei Parameter an die Methode übergeben: den Computernamen oder die Netzwerkadresse und den Anschluß der Anwendung, mit der die Verbindung herzustellen ist. Die Methode Connect läßt sich auf zwei Arten verwenden. Die erste sieht folgendermaßen aus:

if (m_sMySocket.Connect("thatcomputer.com", 4000))
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

Die zweite Form lautet:

if (m_sMySocket.Connect("178.1.25.82", 4000))
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

Nachdem die Verbindung hergestellt ist, wird ein Ereignis ausgelöst, um Ihre Anwendung wissen zu lassen, daß sie verbunden ist oder daß es Probleme gegeben hat und sich die Verbindung nicht einrichten läßt. (Auf diese Ereignisse geht der Abschnitt »Socket-Ereignisse« weiter hinten in dieser Lektion näher ein.)

Auf der Server-Seite oder dem Hörer der Verbindung muß die Anwendung zuerst den Socket anweisen, auf hereinkommende Verbindungen zu prüfen. Dazu ist die Methode Listen aufzurufen. Die Methode Listen übernimmt nur ein einziges Argument, das Sie nicht bereitstellen müssen. Dieser Parameter legt die Anzahl der anhängigen Verbindungen fest, die in eine Warteschlange eingereiht werden können und auf den Abschluß der Verbindung warten. Per Vorgabe ist dieser Wert 5, der das Maximum darstellt. Die Methode Listen läßt sich folgendermaßen aufrufen:

if (m_sMySocket.Listen())
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

Immer dann, wenn eine andere Anwendung versucht, sich mit der hörenden Anwendung in Verbindung zu setzen, wird ein Ereignis ausgelöst, um der Anwendung mitzuteilen, daß eine Verbindungsanforderung vorliegt. Die hörende Anwendung muß die Verbindungsanforderung durch Aufruf der Methode Accept entgegennehmen. Diese Methode erfordert eine zweite CAsyncSocket-Variable, die mit der anderen Anwendung verbunden ist. Nachdem ein Socket in den Hörermodus gebracht wurde, bleibt er in diesem Modus. Immer wenn ein Verbindungsgesuch empfangen wird, erzeugt der hörende Socket einen weiteren Socket, der mit der anderen Anwendung verbunden wird. Für diesen zweiten Socket sollte man nicht die Methode Create aufrufen, da die Methode Accept den Socket erstellt. Die Methode Accept läßt sich folgendermaßen aufrufen:

if (m_sMySocket.Accept(m_sMySecondSocket))
{
// Fortfahren
}
else
// Hier Fehlerbehandlung durchführen

Zu diesem Zeitpunkt ist die verbindende Anwendung mit dem zweiten Socket der hörenden Anwendung verbunden.

Nachrichten senden und empfangen

Das Senden und Empfangen von Nachrichten über eine Socket-Verbindung ist wenig spektakulär. Da man Sockets einsetzen kann, um alle Arten von Daten zu senden, und sich die Sockets nicht darum kümmern, welche Daten das sind, erwarten die Funktionen zum Senden und Empfangen von Daten einen Zeiger auf einen generischen Puffer. Zum Senden von Daten sollte dieser Puffer die zu sendenden Daten enthalten. Beim Empfangen von Daten nimmt dieser Puffer die empfangenen Daten auf. Solange Sie Strings und Text senden und empfangen, können Sie ziemlich einfache Umwandlungen der Pufferinhalte über CStrings vornehmen.

Um eine Nachricht über eine Socket-Verbindung zu versenden, rufen Sie die Methode Send auf. Diese Methode erfordert zwei Parameter und einen dritten optionalen Parameter, mit denen man steuern kann, wie die Nachricht zu versenden ist. Der erste Parameter ist ein Zeiger auf den Puffer, der die zu sendenden Daten enthält. Wenn Ihre Nachricht in einer CString-Variablen steht, können Sie die CString-Variable mit Hilfe des Operators LPCTSTR als Puffer übergeben. Der zweite Parameter gibt die Länge des Puffers an. Die Methode liefert die Anzahl der Daten zurück, die an die andere Anwendung gesendet wurden. Wenn ein Fehler auftritt, liefert die Funktion Send den Wert SOCKET_ERROR zurück. Die Methode Send rufen Sie folgendermaßen auf:

CString strMyMessage;
int iLen;
int iAmtSent;
.
.
.
iLen = strMyMessage.GetLength();
iAmtSent = m_sMySocket.Send(LPCTSTR(strMyMessage), iLen);
if (iAmtSent == SOCKET_ERROR)
{
// Hier Fehlerbehandlung durchführen
}
else
{
// Alles OK
}

Wenn von einer anderen Anwendung empfangene Daten verfügbar sind, wird in der empfangenden Anwendung ein Ereignis ausgelöst. Damit wird der Anwendung mitgeteilt, daß sie die Nachricht empfangen und verarbeiten kann. Um die Nachricht zu erhalten, ist die Methode Receive aufzurufen. Diese Methode übernimmt die gleichen Parameter wie die Methode Send, mit einem kleinen Unterschied. Der erste Parameter ist ein Zeiger auf einen Puffer, in den die Nachricht kopiert werden kann. Der zweite Parameter gibt die Größe des Puffers an. Aus diesem Wert weiß der Socket, wie viele Daten zu kopieren sind (falls mehr empfangen wurden, als in den Puffer passen). Wie bei der Methode Send liefert die Methode Receive die Anzahl der in den Puffer kopierten Daten zurück. Wenn ein Fehler auftritt, gibt die Methode ebenfalls den Wert SOCKET_ERROR zurück. Wenn die Nachricht, die Ihre Anwendung empfängt, eine Textnachricht ist, können Sie sie direkt in eine CString-Variable kopieren. Damit läßt sich die Methode Receive wie folgt aufrufen:

char *pBuf = new char[1025];
int iBufSize = 1024;
int iRcvd;
CString strRecvd;

iRcvd = m_sMySocket.Receive(pBuf, iBufSize);
if (iRcvd == SOCKET_ERROR)
{
// Hier Fehlerbehandlung durchführen
}
else
{
pBuf[iRcvd] = NULL;
strRecvd = pBuf;
// Nachricht weiterverarbeiten
}

Beim Empfang einer Textnachricht empfiehlt es sich immer, eine NULL unmittelbar nach dem letzten empfangenen Zeichen in den Puffer zu schreiben, wie es das obige Beispiel zeigt. Im Puffer können noch willkürliche Zeichen stehen, die Ihre Anwendung vielleicht als Teil der Nachricht interpretiert, wenn Sie den String nicht mit NULL abtrennen.

Die Verbindung schließen

Nachdem die Anwendung den Datenaustausch mit der anderen Anwendung beendet hat, kann sie die Verbindung durch Aufruf der Methode Close schließen. Die Methode Close übernimmt keinerlei Parameter und läßt sich folgendermaßen aufrufen:

m_sMySocket.Close();

Die Methode Close gehört zu den wenigen Methoden von CAsyncSocket, die keinen Statuscode zurückgibt. Bei allen bisher untersuchten Member- Funktionen können Sie anhand des Rückgabewerts prüfen, ob ein Fehler aufgetreten ist.

Socket-Ereignisse

Der Hauptgrund, daß Sie Ihre eigene von CAsyncSocket abgeleitete Klasse erstellen, liegt darin, die Ereignisse abzufangen, die zum Beispiel beim Empfang einer Nachricht oder beim Beenden der Verbindung auftreten. Die Klasse CAsyncSocket verfügt über eine Reihe von Funktionen, die für die jeweiligen Ereignisse aufgerufen werden. Die Funktionen verwenden alle die gleiche Definition - nur der Funktionsname unterscheidet sich - und sind dafür vorgesehen, in den abgeleiteten Klassen überschrieben zu werden. Die Funktionen sind als geschützte (protected) Elemente der Klasse CAsyncSocket deklariert. Auch in Ihren abgeleiteten Klassen sollten Sie diese Funktionen als geschützt deklarieren. Die Funktionen haben alle einen einzelnen Integer-Parameter, der einen Fehlercode darstellt und den Sie auswerten sollten, um gegebenenfalls auf Fehler zu reagieren. Tabelle 20.1 listet die Ereignisfunktionen zusammen mit den auslösenden Ereignissen auf.

Tabelle 20.1: Überschreibbare Benachrichtigungsfunktionen der Klasse CAsyncSocket

Funktion

Ereignisbeschreibung

OnAccept

Diese Funktion wird auf einem hörenden Socket aufgerufen, um zu signalisieren, daß eine Verbindungsanforderung von einer anderen Anwendung auf die Annahme wartet.

OnClose

Diese Funktion wird auf einem Socket aufgerufen, um zu signalisieren, daß die Anwendung am anderen Ende der Verbindung ihren Socket geschlossen hat oder die Verbindung unterbrochen wurde. Daraufhin sollte man den Sokket schließen, der diese Nachricht erhalten hat.

OnConnect

Diese Funktion wird auf einem Socket aufgerufen, um zu signalisieren, daß die Verbindung mit einer anderen Anwendung eingerichtet wurde und daß die Anwendung jetzt Nachrichten über den Socket senden und empfangen kann.

OnReceive

Diese Funktion wird aufgerufen, um zu signalisieren, daß Daten über die Socket-Verbindung empfangen wurden und die Daten über die Funktion Receive zum Abruf bereitliegen.

OnSend

Diese Funktion wird aufgerufen, um zu signalisieren, daß der Socket für das Senden von Daten bereit und verfügbar ist. Die Funktion wird unmittelbar im Anschluß an die erfolgreiche Einrichtung der Verbindung aufgerufen. Ansonsten ruft man die Funktion gewöhnlich auf, wenn die Anwendung der Funktion Send mehr Daten übergeben hat, als in ein einziges Paket passen. In diesem Fall ist das ein Signal, daß alle Daten gesendet wurden, und die Anwendung den nächsten mit Daten gefüllten Puffer übertragen kann.

Fehler erkennen

Wenn eine Member-Funktion von CAsyncSocket einen Fehler zurückgibt, entweder FALSE bei den meisten Funktionen oder SOCKET_ERROR bei den Funktionen Send und Receive, können Sie mit der Methode GetLastError den Fehlercode ermitteln. Die Funktion liefert nur Fehlercodes zurück, und Sie müssen sich selbst um eine Übersetzung in Klartext kümmern. Alle Winsock-Fehlercodes sind mit Konstanten definiert, so daß Sie die Konstanten in Ihrem Code verwenden können, um die anzuzeigende Fehlermeldung (falls vorhanden) für den Benutzer zu bestimmen. Die Funktion GetLastError läßt sich wie folgt verwenden:

int iErrCode;

iErrCode = m_sMySocket.GetLastError();
switch (iErrCode)
{
case WSANOTINITIALISED:
.
.
.
}

Eine Netzwerkanwendung erstellen

Mit der heutigen Beispielanwendung erstellen Sie eine einfache dialogbasierte Anwendung, die entweder als Client oder als Server in einer Winsock-Verbindung agieren kann. Damit können Sie für jedes Ende der Verbindung zwei Kopien derselben Anwendung starten, entweder auf demselben Computer, oder Sie kopieren die Anwendung auf einen anderen Computer, so daß Sie die beiden Kopien auf getrennten Computern ausführen und die Nachrichtenübertragung über das Netzwerk verfolgen können. Nachdem die Anwendung eine Verbindung mit einer anderen Anwendung eingerichtet hat, können Sie Textnachrichten eingeben und sie an die andere Anwendung verschicken. Wenn die Nachricht gesendet wurde, kommt sie in eine Liste der gesendeten Nachrichten. Jede empfangene Nachricht wird in eine andere Liste aller empfangenen Nachrichten kopiert. Damit können Sie sich die vollständige Liste aller gesendeten und empfangenen Nachrichten ansehen. Außerdem können Sie daraus ersehen, was eine der Anwendungskopien gesendet und was die andere empfangen hat. (Die beiden Listen sollten gleich sein.)

Das Anwendungsgerüst erstellen

Um die heutige Beispielanwendung einfach zu halten, erstellen Sie eine dialogbasierte Anwendung. Alles, was in der heutigen Anwendung zu tun ist, läßt sich genauso einfach auch in einer SDI- oder MDI-Anwendung realisieren. Durch den Einsatz einer dialogbasierten Anwendung halten wir alles, was sonst von der grundlegenden Socket- Funktionalität ablenkt (beispielsweise Fragen, ob die Socket-Variable zur Dokument- oder zur Ansichtsklasse gehört, welche Funktionen der Anwendung in welche der beiden Klassen gehören usw.) von der Beispielanwendung fern.

Erstellen Sie zunächst mit dem MFC-Anwendungs-Assistenten ein Projekt, etwa mit dem Namen Sock. Im ersten Dialogfeld des Anwendungs-Assistent wählen Sie die Option Dialogfeldbasierend. Im zweiten Schritt des Assistenten schalten Sie das Kontrollkästchen für die Unterstützung von Windows-Sockets ein (siehe Abbildung 20.3). Bei den übrigen Dialogfeldern des Anwendungs-Assistenten können Sie die Standardeinstellungen übernehmen.

Abbildung 20.3:
Unterstützung für Sockets einbinden

Fenstergestaltung und Funktionalität beim Start

Nachdem Sie über das Anwendungsgerüst verfügen, können Sie das Hauptdialogfeld für Ihre Anwendung entwerfen. In diesem Dialogfeld brauchen Sie eine Gruppe von Optionsfeldern, um festzulegen, ob die Anwendung als Client oder als Server läuft. Außerdem ist eine Reihe von Eingabefeldern für den Computernamen und den Anschluß, den der Server abhören soll, erforderlich. Als nächstes brauchen Sie eine Schaltfläche, um das Hören der Anwendung auf dem Socket zu starten oder die Verbindung zum Server zu öffnen, sowie eine Schaltfläche, um die Verbindung zu schließen. Weiterhin sind ein Eingabefeld für die zu verschickenden Nachricht an die andere Anwendung und eine Schaltfläche zum Senden der Nachricht erforderlich. Schließlich brauchen wir eine Reihe von Listenfeldern, in die sich die gesendeten und empfangenen Nachrichten eintragen lassen. Diese Steuerelemente plazieren Sie im Dialogfeld, wie es Abbildung 20.4 zeigt. Die Eigenschaften der Steuerelemente legen Sie gemäß Tabelle 20.2 fest.

Tabelle 20.2: Eigenschaftseinstellungen der Steuerelemente

Objekt

Eigenschaft

Einstellung

Gruppenfeld

ID

Titel

IDC_STATICTYPE

Socket-Typ

Optionsfeld

ID

Titel

Gruppe

IDC_RCLIENT

&Client

eingeschaltet

Optionsfeld

ID

Titel

IDC_RSERVER

&Server

Text

ID

Titel

IDC_STATICNAME

Server-&Name:

Eingabefeld

ID

IDC_ESERVERNAME

Text

ID

Titel

IDC_STATICPORT

Server-&Anschluß:

Eingabefeld

ID

IDC_ESERVPORT

Schaltfläche

ID

Titel

IDC_BCONNECT

&Verbinden

Schaltfläche

ID

Titel

Deaktiviert

IDC_BCLOSE

Schlie&ßen

eingeschaltet

Text

ID

Titel

Deaktiviert

IDC_STATICMSG

Nachrich&t:

eingeschaltet

Eingabefeld

ID

Deaktiviert

IDC_EMSG

eingeschaltet

Schaltfläche

ID

Titel

Deaktiviert

IDC_BSEND

Sen&den

eingeschaltet

Text

ID

Titel

IDC_STATIC

&Gesendet:

Listenfeld

ID

Tabstopp

Sortieren

Auswahl

IDC_LSENT

ausgeschaltet

ausgeschaltet

Keine

Text

ID

Titel

IDC_STATIC

Empfangen:

Listenfeld

ID

Tabstopp

Sortieren

Auswahl

IDC_LRECVD

ausgeschaltet

ausgeschaltet

Keine

Abbildung 20.4:
Das Layout des Hauptdialogfelds

Nachdem Sie das Dialogfeld fertig entworfen haben, öffnen Sie den Klassen-Assistenten, um die Variablen gemäß Tabelle 20.3 mit den Steuerelementen im Dialogfeld zu verbinden.

Tabelle 20.3: Variablen der Steuerelemente

Objekt

Name

Kategorie

Typ

IDC_BCONNECT

m_ctlConnect

Control

CButton

IDC_EMSG

m_strMessage

Wert

CString

IDC_ESERVNAME

m_strName

Wert

CString

IDC_ESERVPORT

m_iPort

Wert

int

IDC_LRECVD

m_ctlRecvd

Control

CListBox

IDC_LSENT

m_ctlSent

Control

CListBox

IDC_RCLIENT

m_iType

Wert

int

Damit man die Schaltfläche Verbinden zweifach nutzen kann, um auch den Server in den Hörermodus zu versetzen, fügen Sie für das Klickereignis der beiden Optionsfelder eine Funktion hinzu, die den Text auf der Schaltfläche in Abhängigkeit von der momentan ausgewählten Option ändert. Um diese Funktionalität in die Anwendung einzubauen, fügen Sie für die Nachricht BN_CLICKED der Steuerelement-ID IDC_RCLIENT eine Funktion namens OnRType hinzu. Die gleiche Funktion fügen Sie für die Nachricht BN_CLICKED der Steuerelement-ID IDC_RSERVER hinzu. In die Funktion übernehmen Sie den Code aus Listing 20.1.

Listing 20.1: Die Funktion OnRType der Klasse CSockDlg

1: void CSockDlg::OnRType()
2: {
3: // TODO: Code für die Behandlungsroutine der Steuerelement- ÂBenachrichtigung hier einfügen
4: // Steuerelemente mit Variablen synchronisieren
5: UpdateData(TRUE);
6: // Welcher Modus ist eingestellt?
7: if (m_iType == 0) // Entsprechenden Text auf der Schaltfläche festlegen
8: m_ctlConnect.SetWindowText("&Verbinden");
9: else
10: m_ctlConnect.SetWindowText("&Hören");
11: }

Wenn Sie die Anwendung jetzt kompilieren und ausführen, sollte sich der Text auf der Schaltfläche ändern und die jeweilige Rolle der Anwendung wiedergeben, je nachdem, welches der beiden Optionsfelder ausgewählt ist (siehe Abbildung 20.5).

Abbildung 20.5:
Der Schaltflächentext ändert sich je nach gewählter Option

Von der Klasse CAsyncSocket erben

Damit Sie die Socket-Ereignisse abfangen und darauf reagieren können, erstellen Sie Ihre eigene Klasse, die von CAsyncSocket abgeleitet ist. Diese Klasse benötigt sowohl ihre eigenen Versionen der Ereignisfunktionen als auch die Mittel, das Ereignis an das Dialogfeld weiterzuleiten, zu dem das Objekt gehört. Um jedes dieser Ereignisse auf der Ebene der Dialogfeldklasse übergeben zu können, fügen Sie einen Zeiger auf die übergeordnete Dialogfeldklasse als Elementvariable Ihrer Socket-Klasse hinzu. Über diesen Zeiger rufen Sie Ereignisfunktionen, die Elementfunktionen des Dialogfelds sind, für die Socket-Ereignisse auf, nachdem Sie - selbstverständlich - geprüft haben, daß keine Fehler aufgetreten sind.

Um diese Klasse in Ihrer Anwendung zu erzeugen, wählen Sie den Befehl Einfügen / Neue Klasse. Im Dialogfeld Neue Klasse übernehmen Sie den Vorgabewert für den Klassentyp MFC-Klasse. Geben Sie einen Namen wie etwa CMySocket für die Klasse ein, und wählen Sie CAsyncSocket aus der Liste der verfügbaren Basisklassen. Mehr können Sie im Dialogfeld Neue Klasse nicht festlegen. Klicken Sie also auf OK, um diese neue Klasse in Ihre Anwendung einzubinden.

Nachdem Sie die Socket-Klasse erzeugt haben, nehmen Sie in die Klasse eine Member-Variable auf, die als Zeiger auf das übergeordnete Dialogfeld dient. Legen Sie den Variablentyp als CDialog*, den Variablennamen mit m_pWnd und den Zugriff als Privat fest. Weiterhin müssen Sie der Klasse eine Methode hinzufügen, um den Zeiger zu setzen. Nehmen Sie also in die neue Socket-Klasse eine Member-Funktion auf. Legen Sie den Funktionstyp als void, die Deklaration als SetParent(CDialog* pWnd) und den Zugriff als Public fest. In dieser Funktion setzen Sie den als Parameter übergebenen Zeiger auf den Member-Variablenzeiger, wie es Listing 20.2 zeigt.

Listing 20.2: Die Funktion SetParent der Klasse CMySocket

1: void CMySocket::SetParent(CDialog *pWnd)
2: {
3: // Elementzeiger setzen
4: m_pWnd = pWnd;
5: }

Das einzige, was Sie noch in Ihre Socket-Klasse aufnehmen müssen, sind die Ereignisfunktionen, über die Sie ähnlich benannte Funktionen in der Dialogfeldklasse aufrufen. Um eine Funktion für das Ereignis OnAccept aufzunehmen, fügen Sie Ihrer Sokket-Klasse eine Member-Funktion hinzu. Legen Sie den Funktionstyp als void, die Funktionsdeklaration als OnAccept(int nErrorCode) und den Zugriff als Protected fest. Schalten Sie außerdem das Kontrollkästchen Virtual ein. In die Funktion schreiben Sie den Code aus Listing 20.3.

Listing 20.3: Die Funktion OnAccept der Klasse CMySocket

1: void CMySocket::OnAccept(int nErrorCode)
2: {
3: // Sind Fehler aufgetreten?
4: if (nErrorCode == 0)
5: // Nein, OnAccept-Funktion des Dialogfelds aufrufen
6: ((CSockDlg*)m_pWnd)->OnAccept();
7: }

Analoge Funktionen fügen Sie der Socket-Klasse für die Funktionen OnConnect, OnClose , OnReceive und OnSend hinzu, die gleichnamige Funktionen in der Dialogfeldklasse aufrufen. (Die Funktionen der Dialogfeldklasse realisieren wir später.) Nachdem Sie diese Funktionen erstellt haben, müssen Sie die Header-Datei für das Anwendungsdialogfeld in Ihre Socket-Klasse einbinden, wie es Zeile 7 von Listing 20.4 zeigt.

Listing 20.4: Die einzubindenden Header-Dateien für die Klasse CMySocket

1: // MySocket.cpp: Implementierungsdatei
2: //
3:
4: #include "stdafx.h"
5: #include "Sock.h"
6: #include "MySocket.h"
7: #include "SockDlg.h"

Nachdem Sie die Ereignisfunktionen in Ihre Socket-Klasse eingebunden haben, nehmen Sie eine Variable Ihrer Socket-Klasse in die Dialogfeldklasse auf. Für die Server- Funktionalität sind zwei Variablen in der Dialogfeldklasse erforderlich - eine zum Hören auf Verbindungsgesuche und die andere zum Herstellen einer Verbindung zur anderen Anwendung. Da Sie zwei Socket-Objekte brauchen, nehmen Sie zwei Member- Variablen in die Dialogfeldklasse (CSockDlg) auf. Legen Sie den Typ der beiden Variablen mit Ihrer Socket-Klasse (CMySocket) und den Zugriff als Privat fest. Die Variable, die für das Hören auf Verbindungswünsche verwendet wird, nennen Sie m_sListenSocket, die andere Variable, die für die Übertragung der Nachrichten in beide Richtungen verantwortlich ist, m_sConnectSocket.

Nachdem Sie die Socket-Variablen hinzugefügt haben, bauen Sie noch den Initialisierungscode für alle Variablen ein. Als Vorgabe setzen Sie den Anwendungstyp auf Client, den Servernamen auf loopback und den Anschluß auf 4000. Zusammen mit diesen Variablen setzen Sie die Zeiger des übergeordneten Dialogfelds in Ihren zwei Socket-Objekten, so daß sie auf die Dialogfeldklasse zeigen. Dazu nehmen Sie den Code aus Listing 20.5 in die Funktion OnInitDialog der Dialogfeldklasse auf.

Der Computername loopback ist ein spezieller Name, der im Netzwerkprotokoll TCP/IP den Computer kennzeichnet, auf dem Sie arbeiten. Es handelt sich um einen internen Computernamen, der in die Netzwerkadresse 127.0.0.1 aufgelöst wird. Diesen Computernamen und diese Adresse verwenden gewöhnlich Anwendungen, die eine Verbindung zu anderen Anwendungen, die auf demselben Computer laufen, herstellen müssen.

Listing 20.5: Die Funktion OnInitDialog der Klasse CSockDlg

1: BOOL CSockDlg::OnInitDialog()
2: {
3: CDialog::OnInitDialog();
4:
5: // Hinzufügen des Menübefehls "Info..." zum Systemmenü.
6:
.
.
.
26: SetIcon(m_hIcon, FALSE); // Kleines Symbol verwenden
27:
28: // ZU ERLEDIGEN: Hier zusätzliche Initialisierung einfügen
29: // Steuerelementvariablen initialisieren
30: m_iType = 0;
31: m_strName = "loopback";
32: m_iPort = 4000;
33: // Steuerelemente aktualisieren
34: UpdateData(FALSE);
35: // Socket-Zeiger auf Dialogfeld setzen
36: m_sConnectSocket.SetParent(this);
37: m_sListenSocket.SetParent(this);
38:
39: return TRUE; // Geben Sie TRUE zurück, außer ein Steuerelement soll den ÂFokus erhalten
40: }

Die Anwendung verbinden

Wenn der Benutzer auf die Schaltfläche Verbinden klickt, deaktivieren Sie alle Steuerelemente im oberen Teil des Dialogfelds. Zu diesem Zeitpunkt soll der Benutzer nicht annehmen, daß er die Einstellungen des Computers, der gerade eine Verbindung aufbaut, oder die Art und Weise, wie die Anwendung hört, ändern kann. Sie rufen die Funktion Create auf der jeweiligen Socket-Variablen auf, je nachdem, ob die Anwendung als Client oder als Server läuft. Schließlich rufen Sie entweder die Funktion Connect oder die Funktion Listen auf, um die Anwendungsseite der Verbindung zu initialisieren. Um diese Funktionalität in Ihre Anwendung aufzunehmen, öffnen Sie den Klassen-Assistenten und fügen eine Funktion für das Ereignis BN_CLICKED der Schaltfläche Verbinden (IDC_BCONNECT) hinzu. In die Funktion übernehmen Sie den Code aus Listing 20.6.

Listing 20.6: Die Funktion OnBconnect der Klasse CSockDlg

1: void CSockDlg::OnBconnect()
2: {
3: // TODO: Code für die Behandlungsroutine der Steuerelement- ÂBenachrichtigung hier einfügen
4: // Variablen mit Steuerelementen synchronisieren
5: UpdateData(TRUE);
6: // Steuerelemente für Verbindung und Typ deaktivieren
7: GetDlgItem(IDC_BCONNECT)->EnableWindow(FALSE);
8: GetDlgItem(IDC_ESERVNAME)->EnableWindow(FALSE);
9: GetDlgItem(IDC_ESERVPORT)->EnableWindow(FALSE);
10: GetDlgItem(IDC_STATICNAME)->EnableWindow(FALSE);
11: GetDlgItem(IDC_STATICPORT)->EnableWindow(FALSE);
12: GetDlgItem(IDC_RCLIENT)->EnableWindow(FALSE);
13: GetDlgItem(IDC_RSERVER)->EnableWindow(FALSE);
14: GetDlgItem(IDC_STATICTYPE)->EnableWindow(FALSE);
15: // Läuft Anwendung als Client oder Server?
16: if (m_iType == 0)
17: {
18: // Client, einen Standard-Socket erzeugen
19: m_sConnectSocket.Create();
20: // Verbindung zum Server öffnen
21: m_sConnectSocket.Connect(m_strName, m_iPort);
22: }
23: else
24: {
25: // Server, einen Socket für den angegebenen Anschluß erzeugen
26: m_sListenSocket.Create(m_iPort);
27: // Auf Verbindungsgesuche hören
28: m_sListenSocket.Listen();
29: }
30: }

Um die Verbindung zu vervollständigen, fügen Sie in die Dialogfeldklasse die Socket- Ereignisfunktionen für die Ereignisfunktionen OnAccept und OnConnect hinzu. Diese Funktionen ruft Ihre Socket-Klasse auf. Es sind keine Parameter erforderlich, und die Funktionen liefern auch keine Rückgabewerte. In der Funktion OnAccept, die für den hörenden Socket aufgerufen wird, wenn eine andere Anwendung eine Verbindung herstellen will, rufen Sie die Funktion Accept des Socket-Objekts auf und übergeben dabei die Socket-Variable für die Verbindung. Nachdem Sie die Verbindung akzeptiert haben, aktivieren Sie die Aufforderung und das Eingabefeld, damit der Benutzer Nachrichten an die andere Anwendung eingeben und abschicken kann.

Fügen Sie zu diesem Zweck der Dialogfeldklasse (CSockDlg) hinzueine Member-Funktion. Legen Sie den Funktionstyp als void, die Deklaration mit OnAccept und den Zugriff als Privat fest. In die Funktion übernehmen Sie den Code aus Listing 20.7.

Listing 20.7: Die Funktion OnAccept der Klasse CSockDlg

1: void CSockDlg::OnAccept()
2: {
3: // Verbindungsanforderung annehmen
4: m_sListenSocket.Accept(m_sConnectSocket);
5: // Steuerelemente für Text und Nachrichten aktivieren
6: GetDlgItem(IDC_EMSG)->EnableWindow(TRUE);
7: GetDlgItem(IDC_BSEND)->EnableWindow(TRUE);
8: GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE);
9: }

Nachdem die Verbindung vollständig eingerichtet ist, bleibt auf der Client-Seite nichts weiter zu tun, als die Steuerelemente für die Eingabe und das Senden der Nachrichten zu aktivieren. Weiterhin aktivieren Sie die Schaltfläche Schließen, damit sich die Verbindung von der Client-Seite (nicht jedoch von der Server-Seite) schließen läßt. Dazu fügen Sie der Dialogfeldklasse (CSockDlg) eine weitere Member-Funktion hinzu. Legen Sie den Funktionstyp als void, die Funktionsdeklaration als OnConnect und den Zugriff als Public fest. In die Funktion schreiben Sie den Code aus Listing 20.8.

Listing 20.8: Die Funktion OnConnect der Klasse CSockDlg

1: void CSockDlg::OnConnect()
2: {
3: // Steuerelemente für Text und Nachrichten aktivieren
4: GetDlgItem(IDC_EMSG)->EnableWindow(TRUE);
5: GetDlgItem(IDC_BSEND)->EnableWindow(TRUE);
6: GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE);
7: GetDlgItem(IDC_BCLOSE)->EnableWindow(TRUE);
8: }

Falls sich die Anwendung jetzt schon kompilieren und ausführen ließe, könnten Sie zwei Kopien der Anwendung starten, eine davon in den Hörermodus setzen und zu dieser mit der anderen Anwendung eine Verbindung herstellen. Leider können Sie wahrscheinlich noch nicht einmal die Anwendung kompilieren, da die Socket-Klasse nach verschiedenen Funktionen in Ihrer Dialogfeldklasse sucht, die Sie noch nicht hinzugefügt haben. Nehmen Sie die noch fehlenden drei Member-Funktionen in die Dialogfeldklasse (CSockDlg) auf. Legen Sie alle drei Funktionen als void mit Zugriffsstatus Public fest. Die Deklaration der ersten Funktion geben Sie mit OnSend, die der zweiten Funktion mit OnReceive und die der dritten mit OnClose an. Nun sollte sich die Anwendung kompilieren lassen.

Nachdem Sie die Anwendung erfolgreich kompiliert haben, starten Sie zwei Kopien und plazieren Sie nebeneinander auf dem Bildschirm. Die eine Anwendung legen Sie als Server fest und klicken auf die Schaltfläche Hören, um sie in den Hörermodus zu versetzen. Die andere Anwendung belassen Sie als Client und klicken auf die Schaltfläche Verbinden. Die Verbindungssteuerelemente sollten nun deaktiviert und die Steuerelemente zum Senden der Nachrichten aktiviert sein, nachdem die Verbindung hergestellt ist, wie es Abbildung 20.6 zeigt.

Abbildung 20.6:
Die beiden Anwendungen verbinden

Stellen Sie sicher, daß die Server-Anwendung bereits hört, bevor Sie versuchen, die Verbindung mit der Client-Anwendung herzustellen. Wenn Sie mit dem Client eine Verbindung aufbauen wollen und der Server noch nicht hört, wird die Verbindung zurückgewiesen. Ihre Anwendung erkennt aber nicht, daß die Verbindung abgelehnt wurde, da Sie bisher keinerlei Fehlerbehandlung für dieses Ereignis vorgesehen haben.

Um die Anwendungen ausführen und verbinden zu können, muß TCP/IP auf Ihrem Computer laufen. Diese Bedingung ist wahrscheinlich schon erfüllt, wenn Ihr Computer mit einer Netzwerkkarte ausgestattet ist. Haben Sie keine Netzwerkkarte und bauen Verbindungen zum Internet über ein Modem auf, müssen Sie sich wahrscheinlich mit dem Internet verbinden, wenn Sie die Anwendungen ausführen und testen. Falls Sie die Verbindung zum Internet per Modem abwickeln, startet Ihr Computer gewöhnlich TCP/IP, nachdem die Internet-Verbindung hergestellt ist. Wenn Sie weder über eine Netzwerkkarte verfügen, noch eine Möglichkeit haben, sich mit dem Internet oder einem anderen Netzwerk zu verbinden, das Ihnen die Ausführung von Netzwerkanwendungen gestattet, können Sie die heutigen Beispielanwendungen nicht auf Ihrem Computer ausführen und testen.

Senden und Empfangen

Wenn Sie die beiden laufenden Anwendungen verbinden können, müssen Sie nun noch die Funktionalität hinzufügen, um Nachrichten zu senden und zu empfangen. Nachdem die Verbindung zwischen den Anwendungen eingerichtet ist, kann der Benutzer Textnachrichten in das Eingabefeld in der Mitte des Dialogfelds eingeben und auf die Schaltfläche Senden klicken, um die Nachricht an die andere Anwendung abzuschicken. Nachdem die Nachricht gesendet wurde, erscheint sie im Listenfeld der gesendeten Nachrichten. Die Anwendung hat also zu prüfen, ob beim Klicken auf die Schaltfläche Senden eine zu sendende Nachricht vorhanden ist. In diesem Fall muß die Anwendung die Länge der Nachricht ermitteln, die Nachricht senden und sie dann in das Listenfeld der gesendeten Nachrichten eintragen. Um diese Funktionalität in Ihrer Anwendung zu realisieren, fügen Sie mit dem Klassen-Assistenten eine Funktion für das Klickereignis der Schaltfläche Senden (IDC_BSEND) hinzu. In die Funktion schreiben Sie den Code gemäß Listing 20.9.

Listing 20.9: Die Funktion OnBsend der Klasse CSockDlg

1: void CSockDlg::OnBsend()
2: {
3: // TODO: Code für die Behandlungsroutine der Steuerelement- ÂBenachrichtigung hier einfügen
4: int iLen;
5: int iSent;
6:
7: // Steuerelemente mit Variablen synchronisieren
8: UpdateData(TRUE);
9: // Zu sendende Nachricht vorhanden?
10: if (m_strMessage != "")
11: {
12: // Länge der Nachricht ermitteln
13: iLen = m_strMessage.GetLength();
14: // Nachricht senden
15: iSent = m_sConnectSocket.Send(LPCTSTR(m_strMessage), iLen);
16: // Konnte die Nachricht gesendet werden?
17: if (iSent == SOCKET_ERROR)
18: {
19: }
20: else
21: {
22: // Nachricht in Listenfeld hinzufügen.
23: m_ctlSent.AddString(m_strMessage);
24: // Variablen mit Steuerelementen synchronisieren
25: UpdateData(FALSE);
26: }
27: }
28: }

Wenn die Ereignisfunktion OnReceive ausgelöst wird, weil eine Nachricht angekommen ist, rufen Sie die Nachricht aus dem Socket über die Funktion Receive ab. Dann konvertieren Sie die Nachricht in einen CString und fügen sie dem Listenfeld der empfangenen Nachrichten hinzu. Um diese Funktionalität zu realisieren, übernehmen Sie den Code aus Listing 20.10 in die Funktion OnReceive.

Listing 20.10: Die Funktion OnReceive der Klasse CSockDlg

1: void CSockDlg::OnReceive()
2: {
3: char *pBuf = new char[1025];
4: int iBufSize = 1024;
5: int iRcvd;
6: CString strRecvd;
7:
8: // Nachricht empfangen
9: iRcvd = m_sConnectSocket.Receive(pBuf, iBufSize);
10: // Wurde etwas empfangen?
11: if (iRcvd == SOCKET_ERROR)
12: {
13: }
14: else
15: {
16: // Ende der Nachricht abschneiden
17: pBuf[iRcvd] = NULL;
18: // Nachricht in einen CString kopieren
19: strRecvd = pBuf;
20: // Nachricht in Listenfeld der empfangenen Nachrichten eintragen
21: m_ctlRecvd.AddString(strRecvd);
22: // Variablen mit den Steuerelementen synchronisieren
23: UpdateData(FALSE);
24: }
25: }

Jetzt sollten Sie die Anwendung kompilieren und zwei Kopien ausführen können, wobei Sie die beiden Anwendungen - wie bereits weiter oben geschehen - miteinander verbinden. Nachdem Sie die Verbindung eingerichtet haben, können Sie eine Nachricht in die eine Anwendung eingeben und sie an die andere Anwendung schicken, wie es Abbildung 20.7 verdeutlicht.

Abbildung 20.7:
Nachrichten zwischen den Anwendungen senden

Die Verbindung beenden

Um die Verbindung zwischen den beiden Anwendungen zu schließen, kann der Benutzer der Client-Anwendung auf die Schaltfläche Schließen klicken, um die Verbindung zu beenden. Die Server-Anwendung empfängt daraufhin das Socket-Ereignis OnClose. Das gleiche muß in beiden Fällen passieren. Der verbundene Socket ist zu schließen, und die Steuerelemente für das Senden von Nachrichten sind zu deaktivieren. Auf der Client-Seite können die Verbindungssteuerlemente aktiviert bleiben, da der Client beispielsweise die Informationen ändern und eine Verbindung zu einer anderen Server-Anwendung öffnen kann. In der Zwischenzeit horcht die Server-Anwendung weiter den Anschluß ab, für den sie zu diesem Zweck konfiguriert wurde. Um die beschriebene Funktionalität in Ihrer Anwendung umzusetzen, bearbeiten Sie die Funktion OnClose und übernehmen den Code aus Listing 20.11.

Listing 20.11: Die Funktion OnClose der Klasse CSockDlg

1: void CSockDlg::OnClose()
2: {
3: // Den verbundenen Socket schließen
4: m_sConnectSocket.Close();
5: // Steuerelemente zum Senden von Nachrichten deaktivieren
6: GetDlgItem(IDC_EMSG)->EnableWindow(FALSE);
7: GetDlgItem(IDC_BSEND)->EnableWindow(FALSE);
8: GetDlgItem(IDC_STATICMSG)->EnableWindow(FALSE);
9: GetDlgItem(IDC_BCLOSE)->EnableWindow(FALSE);
10: // Läuft Anwendung im Client-Modus?
11: if (m_iType == 0)
12: {
13: // Ja, Steuerelemente zum Konfigurieren der Verbindung aktivieren
14: GetDlgItem(IDC_BCONNECT)->EnableWindow(TRUE);
15: GetDlgItem(IDC_ESERVNAME)->EnableWindow(TRUE);
16: GetDlgItem(IDC_ESERVPORT)->EnableWindow(TRUE);
17: GetDlgItem(IDC_STATICNAME)->EnableWindow(TRUE);
18: GetDlgItem(IDC_STATICPORT)->EnableWindow(TRUE);
19: GetDlgItem(IDC_RCLIENT)->EnableWindow(TRUE);
20: GetDlgItem(IDC_RSERVER)->EnableWindow(TRUE);
21: GetDlgItem(IDC_STATICTYPE)->EnableWindow(TRUE);
22: }
23: }

Schließlich rufen Sie für die Schaltfläche Schließen die Funktion OnClose auf. Fügen Sie dazu mit dem Klassen-Assistenten eine Funktion für das Klickereignis der Schaltfläche Schließen (IDC_BCLOSE) hinzu. In die Funktion schreiben Sie den Code aus Listing 20.12.

Listing 20.12: Die Funktion OnBclose der Klasse CSockDlg

1: void CSockDlg::OnBclose()
2: {
3: // TODO: Code für die Behandlungsroutine der Steuerelement- ÂBenachrichtigung hier einfügen
4: // OnClose-Funktion aufrufen
5: OnClose();
6: }

Wenn Sie die Anwendung kompilieren und ausführen, können Sie die Client-Anwendung mit dem Server verbinden, Nachrichten in beiden Richtungen austauschen und die Verbindung des Clients trennen, indem Sie auf die Schaltfläche Schließen klicken. In beiden Anwendungen werden die Steuerelemente zum Senden von Nachrichten deaktiviert, wie es Abbildung 20.8 zeigt. Eine erneute Verbindung des Clients mit dem Server ist möglich, indem Sie wieder auf die Schaltfläche Verbinden klicken und dann weitere Nachrichten zwischen den beiden austauschen, so als ob sie noch gar nicht verbunden gewesen wären. Wenn Sie eine dritte Kopie der Anwendung starten, deren Anschlußnummer ändern, sie als Server bekanntmachen und in den Hörermodus setzen, können Sie Ihren Client abwechselnd mit beiden Servern verbinden. Dazu schließen Sie die eine Verbindung, ändern die Anschlußnummer und stellen dann die Verbindung zum anderen Server her.

Abbildung 20.8:
Die Verbindung zwischen den Anwendungen schließen

Zusammenfassung

Heute haben Sie gelernt, wie man Anwendungen mit MFC-Winsock-Klassen erstellt, um mit anderen Anwendungen über ein Netzwerk oder das Internet kommunizieren zu können. Die Klasse CAsyncSocket wurde näher beleuchtet, und Sie haben gesehen, wie man davon eigene Klassen ableitet, um in einer Anwendung die ereignisgesteuerte Kommunikation zu realisieren. Es wurde gezeigt, wie man eine Server-Anwendung erstellt, die auf Verbindungen mit anderen Anwendungen prüft und die Verbindung herstellt. Weiterhin haben Sie gelernt, wie man eine Client-Anwendung erstellt, die eine Verbindung mit einem Server herstellen kann. Die Lektion ist ebenfalls darauf eingegangen, wie sich Nachrichten über eine Socket-Verbindung zwischen zwei Anwendungen senden und empfangen lassen. Schließlich haben Sie erfahren, wie man die Verbindung schließt und wie man erkennt, daß die Verbindung geschlossen wurde.

Fragen und Antworten

Frage:
Wie arbeiten Internet-Anwendungen?

Antwort:
Die meisten Internet-Anwendungen bauen auf der gleichen Funktionalität auf, die Sie mit der heutigen Beispielanwendung realisiert haben. Der Hauptunterschied besteht darin, daß die Anwendungen mit Nachrichtenskripten arbeiten, die in beiden Richtungen übertragen werden. Die Nachrichten bestehen aus einem Befehl und den Daten, die für diesen Befehl erforderlich sind. Der Server liest den Befehl, verarbeitet die Daten entsprechend und schickt einen Statuscode zurück, aus dem der Client den Erfolg oder das Scheitern des Befehls ablesen kann. Wenn Sie mehr über diese Abläufe in Internet-Anwendungen erfahren möchten, sollte Sie sich mit der einschlägigen Literatur befassen.

Frage:
Wie behandelt eine Server-Anwendung eine größere Anzahl gleichzeitiger Client- Verbindungen?

Antwort:
Bei einem Vollserver sind die Verbindungs-Sockets nicht als Klassenvariablen deklariert. Der Server verwendet statt dessen eine Art dynamischer Zuweisung von Sockets in einem Array oder einer verknüpften Liste, um Sockets für die Clients zu erzeugen, sobald Verbindungsanforderungen hereinkommen. Eine andere von Servern oftmals verwendete Lösung besteht darin, einen eigenen Thread für jede Verbindungsanforderung einzurichten. Damit kann die Anwendung eine einzige Socket-Verbindung pro Thread behandeln, was es wesentlich erleichtert, die Sockets zu verfolgen. In allen Fällen arbeiten Server-Anwendungen normalerweise nicht nur mit einer einzigen Variablen für die Verbindungs-Sockets.

Workshop

Kontrollfragen

1. Welche zwei Dinge muß eine Client-Anwendung kennen, damit sie sich mit einer Server-Anwendung verbinden kann?

2. Welche CAsyncSocket-Funktion kommt zum Einsatz, um eine Server-Anwendung in die Lage zu versetzen, Verbindungsgesuche von Client-Anwendungen zu erkennen?

3. Welche CAsyncSocket-Elementfunktion wird aufgerufen, um zu signalisieren, daß Daten über eine Socket-Verbindung angekommen sind?

4. Welche Funktion wird aufgerufen, um zu signalisieren, daß eine Verbindung eingerichtet wurde?

5. Welche Funktion verwenden Sie, um eine Nachricht über eine Socket-Verbindung zur Anwendung am anderen Ende der Verbindung zu senden?

Übung

Die Server-Anwendung, die Sie heute geschrieben haben, kann nur eine einzelne Verbindung zu einem Zeitpunkt behandeln. Wenn eine zweite Anwendung versucht, eine Verbindung zum Server zu öffnen, während der Server mit einer anderen Anwendung in Verbindung steht, stürzt die Server-Anwendung ab. Der Server versucht, die zweite Verbindung in den Socket, der bereits mit der ersten Client-Anwendung verbunden ist, zu übernehmen. Fügen Sie Ihrer Anwendung ein drittes Socket-Objekt hinzu, das die zusätzlichen Client-Verbindungen zurückweist, bis der erste Client die Verbindung schließt.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbackKapitelanfangnächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Visual C++ 6 in 21 Tagen, ISBN: 3-8272-2035-1