Seku
HomeProjekteTutorialsToolsGFXLinksForumGästebuchKontaktImpressumLogin
Stoppt die Vorratsdatenspeicherung - www.vorratsdatenspeicherung.de

tutorial anzeigen

Durch lesen dieses Artikels akzeptieren Sie folgende Nutzungsbedingungen:
Dieser Artikel und alle verwendeten Materialien (Bilder, Quelltexte, etc.) unterliegen dem Copyright des Autors, hier Seku. Das kopieren dieses Artikels, Auszügen oder verwendeten Materialien und Einfügen auf anderen Seiten als dieser (seku.info) ist nur mit einer ausdrücklichen, schriftlichen Einverständniserklärung des Autors erlaubt. Weder Autor noch der Betreiber dieser Seite haften für eventuelle Schäden, die durch diesen Artikel oder herunterladbare Objekte ( > Downloads ) verursacht wurden. Die Richtigkeit dieses Textes ist nicht garantiert.

Einleitung

Guten Tag.
In diesem Dokument lernen wir, wie man Berkley Sockets unter Windows benutzt. Es werden keine C# oder MFC Klassen genutzt sondern die funktionsorientierte WinSock-Bibliothek. Das und die Tatsache, dass wir so wenig WSA***-Funktionen wie möglich benutzen werden, ermöglicht das Portieren auf andere Betriebssysteme wie Linux, FreeBSD, Solaris und andere. Das Tutorial ist für C++ Benutzer geschrieben und funktioniert wahrscheinlich nicht auf Win95-Maschinen, da WinSock 2 genutzt wird. Auch werde ich das TCP/IP Protokoll benutzen. Dieses stellt sicher, dass alle gesendeten Pakete vollständig und in der richtigen Reihenfolge ankommen.

WinSock

Wie bereits angekündigt werden wir WinSock verwenden. WinSock ist eine API für das Prinzip der Berkley Sockets. Ein Socket ist nichts weiter, als ein Ende einer Verbindungsschnittstelle zweier Programme, die über ein Nezwerk miteinander kommunizieren (Ich denke diesen Satz muss man zweimal lesen um ihn zu begreifen ). Zunächst müssen wir WinSock einfügen (#include <WinSock2.h>). Es kann aber sein, dass WinSock2.h bei der Windows.h schon eingefügt wird. Zudem müssen wir beachten die Bibliothek "ws2_32.lib" zu linken. Nach diesen Einstellungen können wir endlich WinSock starten. Dafür gibt es die Funktion WSAStartUp():

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

  • wVersionRequested verlangt die Version von WinSock die wir benutzen wollen. Dabei ist das Low Order Byte die Majorversionsnummer und das High Order Byte die Minor-. Zum Thema High Order und Low Order: Der Variablentyp WORD besteht aus zwei Bytes. Das High Order Byte ist dabei eins dieser beiden, genauer gesagt das mit dem höheren Wert. Mit dieser Formel könnte man also Version 1.6 anfordern: WORD wVersion = (6 << 8) + 1. Es wird also die 6 um 8 Bits (= 1Byte) nach "links" verschoben. Allerdings gibt es da auch das Makro MakeWord(BYTE Low, BYTE High);
  • lpWSAData ist ein Pointer auf eine WSADATA-Struktur. Diese enthält dann Informationen über die Version. Für uns ist diese Struktur aber nicht von Belang.
  • Der Rückgabewert ist 0, wenn alles ohne Probleme verlief.

So wie es eine Funktion zum Starten von WinSock gibt, gibt es auch eine zum Herrunterfahren: WSACleanup. Sie erwartet keine Parameter

Erstellen eines Sockets

Wir werden hier einen ClientSocket erstellen. Der Unterschied ist der, dass ein ClientSocket sich mit einem ServerSocket verbindet und Daten sendet bzw. empfängt. Ein ServerSocket wartet dagegen auf Clients und diese ggf. an.

SOCKET MeinSocket;
MeinSocket = socket(AF_INET, SOCK_STREAM, 0);
// socket(int af, int type, int protocol);

Hier erstellen wir einen Socket mittels der Funktion socket. Ich will schnell den Prototyp erläutern.
  • af ist die Adressfamilie der Verbindung. Da wir das Protokoll TCP/IP benuten, sollten wir hier AF_INET angeben.
  • type ist der SocketTyp. Für einen TCP/IP-Socket geben wir hier SOCK_STREAM an.
  • protocol ist für uns uninteressant und deshalb 0.
  • Der Rückgabewert ist unser Socket vom Typ SOCKET. Man sollte prüfen, ob dieser den Wert INVALID_SOCKET hat. Ist das der Fall, kann man mit WSAGetLastError() die Fehlernummer ermitteln

Einen Socket können wir mit closesocket(SOCKET Socket); wieder schließen. Zuvor sollte man aber noch die Verbindung trennen. Das geht mit shutdown(SOCKET Socket, int how); Bei how sollte man SD_BOTH angeben, damit verbieten wir das lesen und empfangen. Bei manchen Compilern ist SD_BOTH nicht definiert. Der Wert dahinter ist 0x02.

Verbindung zu einem Server aufnhemen

Nachdem wir einen Socket erstellt haben, können wir uns mit einem Server verbinden. Dafür gibt es die Funktion connect();

int connect(SOCKET Socket, const SOCKADDR* pSocketAddress, int iSize);

  • Socket: Das ist der Socket, den wir verbinden wollen - den wir zuvor erstellt haben
  • pSocketAddress ist ein Pointer auf eine SOCKADDR Struktur. Wir benutzen aber eine etwas andere, die SOCKADDR_IN (siehe unten)
  • iSize ist die Größe von pSocketAddress. Damit kann WinSock unterscheiden, ob wir SOCKADDR oder SOCKADDR_IN benutzen.

Kommen wir nochmal zurück zu der Struktur SOCKADDR_IN. Man sollte sie am Anfang mit ZeroMemory() auf 0 setzen. Gehen wir mal die einzelnen Variablen durch

struct SOCKADDR_IN
{
    short      sin_family;
    u_short    sin_port;
    in_addr    sin_addr;
    char       sin_zero[8];
};

  • sin_family: Hier müssen wir wieder die Adressfamilie angeben. Da wir beim Erstellen des Sockets AF_INET angegeben haben, muss logischerweise auch hier wieder AF_INET hin.
  • sin_port: Ein WORD-Wert (0-65535), der den Port angibt, auf den wir konnektieren möchten. HTTP läuft z.B. auf Port 80. Dieser Wert muss aber in der Netzwork Byte Order angegeben sein. Das heißt, wir müssen die Bytes von Zahlen andersherum speichern. Dafür gibt es die Funktion: u_short htons(u_short hostshort);. Nur am Rande: Es gibt noch htonl für 4 Byte-Werte.
  • sin_addr ist eine sehr komplizierte Struktur, welche die IP des Servers enthält. Die für uns interessante Variable in dieser Struktur ist s_addr. Zum Glück gibt es die Funktion inet_addr, welche uns eben diese liefert.

    unsigned long inet_addr(char* pcIPA);

    Der Parameter ist die IP-Adresse in Form eines Strings. Der Rückgabewert ist obige Struktur.
  • sin_zero: Dieser Parameter ist nicht genutzt und sollte mit 0 gefüllt sein - Dafür ZeroMemory().

Senden und Empfangen von Daten

Jetzt endlich kommen wir zum Senden und Empfangen, weswegen wir diese ganze Sache überhaupt machen. In neueren Windowsversionen kann man auch manche Dateisystemsfunktionen benutzen, allerdings rate ich davon ab, da das nicht kompatibel mit älteren Versionen ist. Stattdessen sollte man die beiden Funktionen send() und recv() benutzen. Jedoch sind beides Blocking Calls auch wenn man das bei send() nicht unbedingt merkt. Das heißt, die Funktionen warten solange, bis sie die Daten erhalten bzw. sie gesendet haben. Wie man das umgehen kann, zeige ich in einem späteren Tutorial. Als Stichwort will ich hier nur mal Asynchroner Modus nennen.

send()

Mit dieser Funktion können wir Daten über einen Socket versenden.

int send(SOCKET Socket,
         const char* pcData,
         int iLength,
         int iFlags);

  • Socket ist der Socket über den wir Daten versenden wollen.
  • pcData ist ein Pointer auf die zu versendenden Daten
  • iLength ist die Größe (in Bytes) von pcData
  • iFlags ist für uns uninteressant und deshalb 0
  • Der Rückgabewert ist die Anzahl der tatsächlich gesendeten Bytes oder SOCKET_ERROR bei einem Fehler oder 0, wenn die Verbindung unterbrochen wurde.

recv

recv() ist das Gegenstück zu send(). Mit recv() können wir Daten über einen Socket empfangen.

int recv(SOCKET Socket,
         char* pcBuffer,
         int iLength,
         int iFlags

Diese Funktion weißt diverse Ähnlichkeiten zu send() auf .
  • Socket ist der Socket über den wir empfangen wollen
  • pcBuffer ist der Pointer auf den zu füllenden Speicher
  • iLength ist die Größe von pcBuffer, damit es nicht zu Laufzeitfehlern kommt
  • iFlags ist auch hier wieder uninteressant und daher 0
  • Der Rückgabewert ist tatsächliche Anzahl an empfangenen Bytes oder SOCKET_ERROR bei einem Fehler oder 0, wenn die Verbindung unterbrochen wurde.

Einen ServerSocket erstellen

Bisher haben wir ja nur einen Client gebaut. Jetzt kommt der zugehörige Server. Zuerst erstellen wir wie bei einem Client einen Socket. Allerdings müssen wir hier den Port festlegen. Bei einem Client ist das egal - er bekommt einen vom Betriebssystem zugewiesen. Beim Server jedoch spielt das eine wichtige Rolle, da man ihn genau wissen muss um darauf konnektieren zu können. Im Fachjargon redet man vom "binden" eines Ports an einen Socket. Wie der Zufall so will gibt es dafür die Funktion bind().

int bind(SOCKET Socket, const SOCKADDR* pSocketAddress, int iSize);

Bei dieser Funktion ist alles gleich wie bei connect(). Jedoch wird pSocketAddress leicht anders verwendet. Statt den sin_port Parameter anzugeben um zu konnektieren geben wir ihn hier an um ihn festzulegen. Hier brauchen wir auch wieder die Funktion htons(), wegen der Network Byte Order. Dazu kommt noch, dass wir die IP-Addresse (sin_addr.s_addr) auf INADDR_ANY setzen müssen. Hierbei müssen wir auf die Network Byte Order achten und da es sich nicht um ein WORD sondern um einen DWORD handelt brauchen wir nicht htons sondern htonl. Bei einem Fehler ist der Rückgabewert SOCKET_ERROR.

Nachdem der Port gebindet ist, muss der Socket auf Verbindungen warten. Auch hier gibt es wieder eine Funktion: listen(). Sie schaltet den Socket in den Modus in dem der Socket auf Verbindungen wartet.

int listen(SOCKET Socket, int iBacklog);

Socket ist der Socket, den wir in den Verbindungswartemodus setzen sollen - der Server. iBacklog ist die Anzahl der Clients die gleichzeitig konnektieren können. Wir geben einfach mal 10 an. Bei einem Fehler ist der Rückgabewert SOCKET_ERROR

So.. noch eine letzte Funktion und wir haben es endlich! Nachdem wir in den Wartemodus gewechselt haben starten wir die Funktion accept(). Sie ist auch wieder ein Blocking Call. Diese Funktion wartet also auf einen Client, der sich anmelden will.

SOCKET accept(SOCKET Socket, SOCKADDR* pSocketAddress, int iSize);

  • Der erste Paramter ist der Socket der Warten soll. Logischerweise ist das unser Server
  • pSocketAddress ist für uns egal (-> NULL)
  • iSize ist für uns egal (-> NULL)
  • Der Rückgabewert ist der Socket auf den Client oder INVALID_SOCKET bei einem Fehler.
Wer genau aufgepasst hat, hat gemerkt, dass der ServerSocket nur dazu da ist, um auf Verbindungen zu warten. Kommuniziert wird dann über den Socket den wir von accept() erhalten, er repräsentiert den Client.

Zwei Beispielprogramme

So, endlich... endlich haben wir alles initialisiert und alles bereitgestellt um miteinander zu kommunizieren. Die folgenden Programm sind ein Server und ein Client. Der Client wird auf den Server konnektieren und ihm eine Nachricht senden auf die der Server antwortet. Natürlich sollte man den Server zuerst starten. Beides sind der Einfachheit halber Konsolenanwendungen. Man sollte auch mal auf diese Zeile hier achten: acBuffer[iResult] = ''; iResult ist der Rückgabewert von recv(). acBuffer ist der empfangene Wert. Da wir normalerweise einen Buffer haben, der zu groß ist, würden bei einer Ausgabe am Ende noch ein Haufen merkwürdiger Zeichen sein. So brechen wir aber den String genau da ab, wo er auch tatsächlich aufhört. Achja.. nicht vergessen ws2_32.lib zu linken!

Der Server



/* =============================================
   =                                           =
   =   Datei: Server.cpp                       =
   =   =================                       =
   =   Zweck: Testserver für WinSockTutorial   =
   =   Autor: Seku                             =
   =                                           =
   ============================================= */

#include <Windows.h>
#include <StdIO.h>

// =============================================
// Prototypen
BOOL        StartUp();             // Startet WinSock
BOOL        CreateSocket();        // Erstellt den Socket
void        CleanUp();             // Räumt alles auf

// =============================================
// Globale Variablen
SOCKET        g_Socket;            // Der Socket
SOCKET        g_ClientSocket;      // Der Socket des Clients

// =============================================
// Die Hauptfunktion
int main()
{
        // Das Programm wurde gestartet
        printf("Server wird gestartet...");
        if(!StartUp())
                return FALSE;        // Fehler!

        // Den Socket erstellen
        if(!CreateSocket())
                return FALSE;        // Fehler!

        // Jetzt können wir miteinander reden
        char acBuffer[32];
        int iResult = recv(g_ClientSocket, acBuffer, 32, 0);
        acBuffer[iResult] = '';
        printf("Der Client sagt '%s'", acBuffer);

        send(g_ClientSocket, "Moin^^", 6, 0);
        printf("Wir sagen 'Moin^^'");

        // Aufräumen
        CleanUp();

        // Alles OK!
        printf("");
        system("PAUSE");
        return TRUE;
}

// =============================================
// Startet WinSock
BOOL StartUp()
{
        WSADATA        WSAData;

        // Wir starten WinSock
        if(WSAStartup(MAKEWORD(2, 0), &WSAData) != 0)
        {
                // Fehler!
                printf("Fehler beim Initialisieren von WinSock");
                system("PAUSE");
                return FALSE;
        }
        else
        {
                // Alles OK!
                printf("WinSock wurde erfolgreich initialisiert");
                return TRUE;
        }
}

// =============================================
// Erstellt den Socket
BOOL CreateSocket()
{
        // Wir erstellen einen Socket
        g_Socket = socket(AF_INET, SOCK_STREAM, 0);
        if(g_Socket == INVALID_SOCKET)
        {
                // Fehler!
                printf("Fehler beim Erstellen des Sockets");
                system("PAUSE");
                return FALSE;
        }
        printf("Der Socket wurde erfolgreich erstellt");

        // Wir binden den Socket an einen Port
        SOCKADDR_IN                SocketAddress;
        ZeroMemory(&SocketAddress, sizeof(SOCKADDR_IN));
        SocketAddress.sin_family       = AF_INET;
        SocketAddress.sin_port         = htons(54321);
        SocketAddress.sin_addr.s_addr  = htonl(INADDR_ANY);
        if(bind(g_Socket, (SOCKADDR*)&SocketAddress, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
        {
                // Fehler!
                printf("Fehler beim Binden des Sockets auf Port 54321");
                system("PAUSE");
                return FALSE;
        }
        printf("Der Socket wurde erfolgreich auf den Port 54321 gebindet");

        // Wir starten den Listenmodus
        if(listen(g_Socket, 10) == SOCKET_ERROR)
        {
                // Fehler!
                printf("Fehler beim Starten des Listen-Modus");
                system("PAUSE");
                return FALSE;
        }
        printf("Der Server ist nun im Listen-Modus");

        // Jetzt warten wir auf Verbindungen
        printf("Beginnen mit Warten auf einen Client");
        g_ClientSocket = accept(g_Socket, NULL, NULL);
        if(g_ClientSocket == INVALID_SOCKET)
        {
                // Fehler!
                printf("Fehler beim Anmelden eines Clients");
                system("PAUSE");
                return FALSE;
        }
        printf("Ein Client wurde erfolgreich angemeldet");

        // Alles OK!
        return TRUE;
}

// =============================================
// Räumt alles auf
void CleanUp()
{
        // Aufräumen, zuerst der ClientSocket
        shutdown(g_ClientSocket, 0x02);
        closesocket(g_ClientSocket);
        printf("Der Client wurde erfolgreich abgemeldet");

        // Dann der Server
        shutdown(g_Socket, 0x02);
        closesocket(g_Socket);
        printf("Der Socket wurde erfolgreich geschlossen");

        // Dann WinSock selbst
        WSACleanup();
        printf("WinSock wurde erfolgreich heruntergefahren");
}

Der Client



/* =============================================
   =                                           =
   =   Datei: Client.cpp                       =
   =   =================                       =
   =   Zweck: Testckuebt für WinSockTutorial   =
   =   Autor: Seku                             =
   =                                           =
   ============================================= */

#include <Windows.h>
#include <StdIO.h>

// =============================================
// Prototypen
BOOL        StartUp();            // Startet WinSock
BOOL        CreateSocket();       // Erstellt den Socket
void        CleanUp();            // Räumt alles auf

// =============================================
// Globale Variablen
SOCKET        g_Socket;           // Der Socket

// =============================================
// Die Hauptfunktion
int main()
{
        // Das Programm wurde gestartet
        printf("Server wird gestartet...");
        if(!StartUp())
                return FALSE;        // Fehler!

        // Den Socket erstellen
        if(!CreateSocket())
                return FALSE;        // Fehler!

        // Jetzt können wir miteinander reden
        send(g_Socket, "Hallo", 5, 0);
        printf("Wir sagen 'Hallo'");

        char acBuffer[32];
        int iResult = recv(g_Socket, acBuffer, 32, 0);
        acBuffer[iResult] = '';
        printf("Der Server sagt '%s'", acBuffer);

        // Aufräumen
        CleanUp();

        // Alles OK!
        printf("");
        system("PAUSE");
        return TRUE;
}

// =============================================
// Startet WinSock
BOOL StartUp()
{
        WSADATA        WSAData;

        // Wir starten WinSock
        if(WSAStartup(MAKEWORD(2, 0), &WSAData) != 0)
        {
                // Fehler!
                printf("Fehler beim Initialisieren von WinSock");
                system("PAUSE");
                return FALSE;
        }
        else
        {
                // Alles OK!
                printf("WinSock wurde erfolgreich initialisiert");
                return TRUE;
        }
}

// =============================================
// Erstellt den Socket
BOOL CreateSocket()
{
        // Wir erstellen einen Socket
        g_Socket = socket(AF_INET, SOCK_STREAM, 0);
        if(g_Socket == INVALID_SOCKET)
        {
                // Fehler!
                printf("Fehler beim Erstellen des Sockets");
                system("PAUSE");
                return FALSE;
        }
        printf("Der Socket wurde erfolgreich erstellt");

        // Wir verbinden uns mit dem Server
        // Die IP 127.0.0.1 ist immer der eigene Computer.
        SOCKADDR_IN                SocketAddress;
        ZeroMemory(&SocketAddress, sizeof(SOCKADDR_IN));
        SocketAddress.sin_family      = AF_INET;
        SocketAddress.sin_port        = htons(54321);
        SocketAddress.sin_addr.s_addr = inet_addr("127.0.0.1");
        if(connect(g_Socket, (SOCKADDR*)&SocketAddress, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
        {
                // Fehler!
                printf("Fehler beim Verbinden zum Server - ist er online?");
                system("PAUSE");
                return FALSE;
        }
        printf("Wir haben uns erfolgreich mit dem Server verbunden");

        // Alles OK!
        return TRUE;
}

// =============================================
// Räumt alles auf
void CleanUp()
{
        // Aufräumen, zuerst den Socket
        shutdown(g_Socket, 0x02);
        closesocket(g_Socket);
        printf("Wir haben uns erfolgreich abgemeldet");

        // Dann WinSock selbst
        WSACleanup();
        printf("WinSock wurde erfolgreich heruntergefahren");
}

Ausgabe

Nun, wenn alles glatt läuft wird folgendes ausgegeben...
Server
Server wird gestartet...
WinSock wurde erfolgreich initialisiert
Der Socket wurde erfolgreich erstellt
Der Socket wurde erfolgreich auf den Port 54321 gebindet
Der Server ist nun im Listen-Modus
Beginnen mit Warten auf einen Client
Ein Client wurde erfolgreich angemeldet
Der Client sagt 'Hallo'
Wir sagen 'Moin^^'
Der Client wurde erfolgreich abgemeldet
Der Socket wurde erfolgreich geschlossen
WinSock wurde erfolgreich heruntergefahren


Drücken Sie eine beliebige Taste . . .

Client
Server wird gestartet...
WinSock wurde erfolgreich initialisiert
Der Socket wurde erfolgreich erstellt
Wir haben uns erfolgreich mit dem Server verbunden
Wir sagen 'Hallo'
Der Server sagt 'Moin^^'
Wir haben uns erfolgreich abgemeldet
WinSock wurde erfolgreich heruntergefahren


Drücken Sie eine beliebige Taste . . .

Ende

So. Endlich haben wir unsere WinSock-Demo und sie ähnelt fast schon einem Chat. Ich denke es wäre mal eine hübsche Aufgabe. Aber die Demo ist noch nicht perfekt. Der Server unterstützt nur einen einzigen Client. Aber ich denke für den Anfang ist es doch ganz ordentlich. Sollte es noch Fragen geben, könnt ihr mir eine Mail schicken.

cu
Seku




Alle Inhalte stehen, wenn nicht anders angegeben, unter dem Copyright von Seku.
Für die Inhalte externer Seiten ist der jeweilige Betreiber verantwortlich.
seku.info v.3.2, Benötigte Zeit: 0,306 Sekunde(n)
Ausgeführte mySQL-Anfragen: 0
17:11