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.
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.
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.
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.
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.
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
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.
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 CString
s 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
}
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();
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.
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:
.
.
.
}
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.)
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
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.
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.
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
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.
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: }
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
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
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
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.
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.
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?
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.