ZFX
ZFX Neu
Home
Community
Neueste Posts
Chat
FAQ
IOTW
Tutorials
Bücher
zfxCON
ZFXCE
Mathlib
ASSIMP
NES
Wir über uns
Impressum
Regeln
Suchen
Mitgliederliste
Membername:
Passwort:
Besucher:
4440681
Jetzt (Chat):
19 (0)
Mitglieder:
5239
Themen:
24223
Nachrichten:
234554
Neuestes Mitglied:
-insane-
ZFX - C++ TutorialsDruckversion

Netzwerk-Programmierung mit Visual C++ (MFC)
Das C++ Tutorials von Patrick Lauffs aka Sayjionix

© Copyright 2001 [whole tutorial] by Patrick Lauffs aka Sayjionix
Die Texte der Tutorials unterliegen dem Copyright und dürfen ohne schriftliche Genehmigung vom Author weder komplett noch auszugsweise vervielfältigt, auf einer anderen Homepage verwendet oder in sonst einer Form veröffentlicht werden. Die zur Verfügung gestellten Quelltexte hingegen stehen zur freien Weiterverwendung in Software Projekten zur Verfügung.
Die Quelltexte dieses Tutorials werden auf einer "as is" Basis bereit gestellt. Der Autor übernimmt keinerlei Garantie für die Lauffähigkeit der Quelltexte. Für eventuelle Schäden die sich aus der Anwendung der Quelltexte ergeben wird keinerlei Haftung übernommen.


ZFX Tutorial 

Netzwerk-Programmierung mit Visual C++
von Patrick J. Lauffs - Greenbit Studios



Einleitung

Hallo allerseits zum Tutorial für die Netzwerkprogrammierung unter Visual C++. In diesem Tutorial lern ihr, wie man mit einfachen, selbst erstellten, C++ Klassen mit TCP/IP eine Spiele-Netzwerk-Engine erstellt. Das einzige was ihr dazu braucht, ist ein bisschen Grundwissen in Sachen Klassen und Variablen ;-)). Alle bereit ?? Dann kanns ja losgehen.


Das Projekt

Alsoooooo, ihr brauch zuerst ein neues Projekt unter MFC. MFC hat hier den großen Vorteil, dass die benötigten Header-Dateien automatisch eingebunden werden, wen man (so wie wir es machen werden) beim Projekt-Assistenten die WOSA Unterstützung für WindowsSockets aktiviert. Aber nun von vorne. Es ist am besten, ihr erstellt für dieses Tutorial eine Dialogfeldbasierte MFC-Anwendungmit dem Namen "Network". Ob ihr noch zusätzlich die 3D-Steuerelemente oder ActiveX beifügt ist egal. Nachdem also unsere Anwendung für uns erstellt wurde, können wir beginnen die nötigen Klassen zusammenzustellen, und unsere Funktionen zu schreiben.


CAsyncSocket oder CSocket

Die größte Frage ist ersteinmal, welches Netzwerk-Objekt wir zur Programmierung nehmen. MFC bietet uns hier 2 Objekte, die beide Vorteile und Nachteile mit sich bringen. Wir entscheiden uns hier für die schwierigere Variante, die uns aber mehr Möglichkeiten bietet: CAsyncSocket.
Wie man schon aus dem Namen vielleicht erraten kann, ist dieses Socket Asynchron, das heißt, dass unser Programm sofort weitermacht, nachdem die Bearbeitung gestartet wurde. Hingegen würde CSocket solange warten, bis irgendetwas mit ihm geschieht oder ein Ergebnis vorliegt.


Es geht endlich los

Endlich...nach dem ganzen blablabla wollen wir zu Taten schreiten. Das erste was wir machen müssen, ist unsere eigene, von CAsyncSocket abgeleitete Klasse zu erstellen. Dazu gehen wir in das Menü Einfügen und dann auf Neue Klasse. Die Einstellung MFC-Klasse lassen wir so wie sie ist; wir geben lediglich einen Klassennamen an. Ich nenne ihn einfach "CMySocket". Jetzt wählen wir nur noch die Basisklasse aus, also CAsyncSocket und klicken auf OK. Nun haben wir also schon unsere Klasse, und wir wollen diese ja jetzt auch implementieren. Zuerst brauchen wir eine Funktion, die uns auf die Dialogklasse zugreifen lässt. Diese macht nix anderes, als das Handle der Anwendung in unserer Klasse bekannt zu machen:

void CMySocket::SetParent(CDialog *pWnd)
{
    m_pWnd = pWnd;
}

Das wars schon. Die Variable m_pWnd ist eine CDialog* (Typ) Member-Variable der Klasse mit Zugriff Private.
Die nächste Funktion, die wir schreiben müssen ist die Funktion zum Akzeptieren einer Verbindung. Hierzu aber ein wenig Theorie.
Eine Netzwerkanwendung macht nix anderes, als eine Anfrage zu einer anderen zu senden. Diese andere Anwendung ist in dem Fall dann der Server (oder auch der Host, in der Spiele-Sprache). Dazu braucht der Client (also die Anwendung die anfragt) zuerst einmal die IP-Nummer oder den Computernamen des Servers. Ist die Anfrage gestartet, akzeptiert der Server die Anfrage und beide Anwendungen sind zur Kommunikation bereit. Der Client hat also "gejoint". Damit der Server die Anfrage akzeptieren kann, brauchen wir also eine Funktion.

void CMySocket::OnAccept(int nErrorCode)
{
    if (nErrorCode == 0)        // Wenn keine Fehler auftreten, die Funktion zum Akzeptieren in der Dialogklasse aufrufen
        ((CNetworkDlg*)m_pWnd)->OnAkzeptieren();
}

Also, hier die Auflösung: Wenn ein Fehler auftritt, ist nErrorCode automatisch 1. Das macht Windows so. Die Funktion wird also abgebrochen. Wenn jedoch alles gut geht, ruft die Funktion in der Dialogklasse die Funktion "OnAkzeptieren" auf. Dort erstellen wir dann die Verbindung. Jetzt brauchen wir noch einige Funktionen für die restlichen Befehle: OnConnect, OnClose, OnSend, OnReceive.

void CMySocket::OnConnect(int nErrorCode)
{
    if(nErrorCode == 0)
        ((CNetworkDlg*)m_pWnd)->OnVerbinden();
    else
        AfxMessageBox("Bitte versuchen Sie es erneut!");
}

Auch hier rufen wir die Funktion in der Dialogklasse auf, wenn kein Fehler auftritt. Ansonsten, geben wir eine Meldung aus. Ich denke, die anderen Funktionen erklären sich nun von selbst:

void CMySocket::OnSend(int nErrorCode)
{
    if (nErrorCode == 0)
        ((CNetworkDlg*)m_pWnd)->OnSenden();
}

void CMySocket::OnClose(int nErrorCode)
{
    if (nErrorCode == 0)
        ((CNetworkDlg*)m_pWnd)->OnClose();
}

void CMySocket::OnReceive(int nErrorCode)
{
    if (nErrorCode == 0)
        ((CNetworkDlg*)m_pWnd)->OnEmpfangen();
}

Puh.. das war jetzt eigentlich schon der schwerste Teil (ist schon lächerlich wie einfach das eigentlich ist) Unserer Klasse CMySocket ist jetzt schon fertig.


Die Dialogklasse

So, jetzt kommen wir zur Dialogklasse. Doch da fällt mir ein, wenn wir die Dialogklasse nicht bei CMySocket includieren, können wir auch nicht auf sie zugreifen. Also noch einmal kurz zurück zu CMySocket. Wir includieren dort die Anwendungs-Header-Datei, die Dialogklassen-Header-Datei, die Header-Datei der Klasse CMySocket und "stdafx.h"

#include "stdafx.h"
#include "Network.h"
#include "MySocket.h"
#include "NetworkDlg.h"

Nun aber! Die neue Klasse ist automatisch schon in der Dialogklasse includiert, so dass wir nun die beiden Hauptobjekte erstellen können, nämlich m_ConnectSocket und m_ListenSocket. Über m_ConnectSocket wird die Verbindung für den Client erstellt und kommuniziert, und über m_ListenSocket hört der Server auf Verbindungs-Anfragen. Beides sind Member-Variablen der Dialogklasse vom Typ unserer selbsterstellten Klasse CMySocket. Auch diese beiden Variablen sin Private. Nun verwenden wir die Funktion OnInitDialog(), um die "Parents zu setzen":

BOOL CNetworkDlg::OnInitDialog()
{
    CDialog::OnInitDialog();

    m_ConnectSocket.SetParent(this);
    m_ListenSocket.SetParent(this);
...
}

Wir rufen dazu nur die Funktion auf, die wir vorhin geschrieben haben und übergeben den Wert "this", also "dieses Fenster".
Endlich, endlich kommen wir zum spannenden Part unserer Anwendung: das Verbinden. Dazu kann man einen Button einfügen, oder die Funktion einfach so aufrufen, vom Programmtext aus. Wir müssen allerdings vorher Wissen, ob unsere Anwendung ein Server ist, oder ein Client. Dazu bauen wir die booleanische Variable "server" ein. Diese kann man dann nach belieben auf 1 (Server) oder 0 (kein Server) setzen. Hier aber die Funktion:

void VerbindungAufbauen()
{
    if(server == 0)    // Wir sind ein Client
    {
        m_ConnectSocket.Create();    // Wir erstellen den Socket
        m_ConnectSocket.Connect(m_ip, m_port);    // Wir senden die Anfrage zu einer IP-Adresse
    }
    if(server == 1)    // Wir sind ein Server
    {
        m_ListenSocket.Create(m_Port);    // Socket erstellen
        m_ListenSocket.Listen();    // Auf Anfragen warten
    }
}

Sooo, also, wie sag ichs am besten? Wir entscheiden zuerst, ob die Anwendung ein Server ist oder als Client läuft. Wenn wir ein Client sind, dann ertellen wir das Verbindungs-Socket und versuchen anschließend, zu einer bestimmten IP-Adresse (kann auch der unter Windows angegebene Computername sein) zu kontaktieren. Der Server der IP-Adresse wird dann die Anfrage akzeptieren. Jetzt fragt ihr euch vielleicht, was das für Variablen sind. Nun, m_ip ist eine Member-Variable vom Typ CString und m_port ist eine Member-Variable vom Typ int. Am besten ist es, wenn ihr die beiden Variablen gleich zum Anfang der Anwendung initialisiert (z.B. in OnInitDialog()). Aber was ist denn nun der Port in m_port?? Der Port ist eine Zahl, die einem jeden Netzwerk zugewiesen ist. Bei Heimnetzwerken verwendet man meistens die Zahl 4000. (Also initialisiert die Variable m_port mit 4000). Eigentlich hätten wir gar keine Variable dazu gebraucht, wir hätten auch einfach nur die Zahl in die Funktion einstetzen können, aber mit Variable siehts schöner aus. WICHTIG: Das Netzwerk funktioniert nur wenn Server und Client den gleichen Port verwenden. Darum verwenden wir bei m_ListenSocket.Create() auch wieder die gleiche Variable. Mit m_ListenSocket.Listen() wartet der Server auf eingehende Anfragen. Puh.. geschafft. TIP: Wenn ihr eure Anwendung ausprobieren wollt, dann startet sie einfach zweimal am gleichen PC und gebt als IP-Adresse "loopback" ein. Dann verbindet ihr zu euch selbst.

Jetzt brauchen wir noch die Funktionen, die unsere Klasse CMySocket aufruft, wenn etwas passiert.

void OnVerbinden();    // Wird aufgerufen, wenn der Client die Anfrage sendet
{
    AfxMessageBox("Anfrage abgeschickt !");
}

void OnAkzeptieren();    // Wird aufgerufen, wenn der Server eine Anfrage akzeptiert
{
    m_ListenSocket.Accept(m_ConnectSocket);    // Akzeptiert den Clienten als m_ConnectSocket
    AfxMessageBox("Client wurde akzeptiert!");
}

Die erste Funktion erklärt sich von alleine. Sie dient einfach nur als Überwachung, dass der Client auch brav Anfragen abschickt. Die zweite wird aufgerufen, sobald der Server eine Anfrage erhält. Dann wird diese angenommen und der Server kann zukünftig mit dem Clienten über m_ConnectSocket kommunizieren. Erstellt einfach beide Funktionen als Member-Funktionen in eurer Dialogklasse mit Attribut Private. Nun fehlen noch die Funktionen OnSenden, OnEmpfangen und OnClose:

void OnSenden()
{
    // AfxMessageBox("Nachricht abgesendet!");
}

void OnEmpfangen()
{
    char *Buffer = new char[1025];
    int BufferSize = 1024;
    int Rcvd;
    CString strRecvd;

    // Nachricht abholen
    Rcvd = m_ConnectSocket.Receive(Buffer, BufferSize);

    // Ende der Nachricht abschneiden
    Buffer[Rcvd] = NULL;
    // Nachricht in die CString-Variable verweisen
   strRecvd = pBuf;
    AfxMessageBox(strRecvd);
}

void OnClose()
{
    m_ListenSocket.Close();
    m_ConnectSocket.Close();
}

Auch diese 3 Funktionen sind Member unsrer Dialogklasse und haben das Attribut Private. Und auch sie werden als Art Kontrolle von der Klasse CMySocket aufgerufen. OnSenden() kann leer bleiben (Wir brauchen keine Bestätigung, dass wir etwas abgesendet haben), in OnClose() schließen wir die Sockets, und in OnEmpfangen() können wir die empfangenen Daten verarbeiten, indem wir sie in einem char speichern, das Ende der Nachricht abschneiden (die nicht belegten Zeichen sind lauter "i"s) und diese dann mit Hilfe einer CString Variable anzeigen.
Jetzt fehlen uns insgesamt nur noch 2 Funktionen. Einmal die Funktion, in der wir die Daten senden, und zum Zweiten, die Funktion in der wir unsere Anwendung beenden. Ich beginne zuerst mit der zweiten, da diese wirklich sehr simpel ist:

void Beenden()
{
    OnClose();
    DestroyWindow();
}

Nun, mit OnClose() rufen wir die oben besprochene Funktion auf, und mit DestroyWindow() beenden wir unsere Anwendung. Doch nun zur ersten Funktion, das Absenden:

void DatenSenden()
{
    CString Data = "Hallo Server! Hallo Client!";        // Unsere Daten
    int Len = Data.GetLenght();        // Die Länge der Daten

    m_ConnectSocket.Send(Data, Len);    // Absenden der Daten
}

Zuerst ermitteln wir die Anzahl der Zeichen unserer Daten, und dann senden wir beides zu unserem verbundenen Socket (Der andere Computer).

Und nun ??? Das wars !! Geschafft !! Wir haben nun zwischen beiden Computern eine funktionierende Netzwerkverbindung. Man kann das Ganze natürlich noch nicht als perfekte Netztwerkengine bezeichnen, aber mit Hilfe der entwickelten Funktionen, kann man auch Positionsdaten und so weiter versenden... Die hier erklärten Vorgehensweisen bilden lediglich das Grundgerüst. Ihr könnt die Funktionen und Befehle selbstverständlich umbenennen und eurer Programmstruktur anpassen. Es gibt auch noch tausende von Möglichkeiten, Fehler abzufragen oder andere Funktionen einzubinden, diese bilden allerdings nur eine Erweiterung.
Viel Spaß noch beim Programmieren und beim Netzwerken ..

Author: Patrick J. Lauffs
E-Mail: patrick.lauffs@gmx.net
Internet: www.greenbit.soft-ware.de


Informationen: " ); ?> " ); ?> " ); ?> " ); ?>
WWW :$wwwtitel
Data:Projektdateien
Mail:$author ($email)
Nick:$nick