Вы находитесь на странице: 1из 13

Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.

html

Winsock Tutorial von c-worker.ch (Teil 1: Grundlagen und TCP Sockets)

Dieses Tutorial stammt von www.c-worker.ch.


Es darf auf einer eigenen Seite veröffentlicht werden sofern es unverändert bleibt, und die Seite www.c-worker.ch als Quelle
erwähnt wird.
Für die Richtigkeit aller in diesem Dokument beschriebenen Angaben und für eventuelle Schäden die durch dieses
Dokument entstehen könnten wird nicht gehaftet.

Hinweise

Falls beim kompilieren einige "Neudefinition" Fehler kommen entfernt die "#include <winsock2.h>" Zeile (wurde in diesem
Fall schon in windows.h includiert)
Falls der Compiler ADDR_ANY nicht finden kann verwendet INADDR_ANY.

1. Einleitung
2. Was sind Sockets ?
3. Winsock starten
4. Typen von Sockets
5. Client Socket erstellen
6. Verbindung zu einem Server herstellen
7. Server Socket erstellen
8. Daten austauschen
9. Aufräumen

1. Einleitung

Dieses Dokument soll ein Tutorial darstellen wie man eine Winsock Anwendung programmiert. Dabei werden nicht
irgendwelche MFC Klassen benutzt, sondern die Windows Socket API. Ebenfalls wird hier nicht auf alle Dinge der
Socketprogrammierung eingegangen. Vorallem werden keine WSAxx Funktionen verwendet (nur die nötigen), das hat
jedoch auch den Vorteil das man gleich die Standard Socketfunktionen lernt, und das Wissen fasst 1:1 unter Linux, etc.
einsetzten kann. Das Tutorial ist in einige Schritte unterteilt und während des Tutorials werden 2 kleine
Konsolenanwendungen geschrieben. Ein Client Programm und ein Server Programm. Ebenfalls wird hier Winsock 2
verwendet, falls du Windows 95 installiert hasst kann es sein, dass du nicht über die Version 2 verfügst. In diesem Fall
findest du Informationen und ein entsprechendes Update hier.
Dieses Tutorial ist für die Programmiersprache C (geht natürlich auch mit C++) bestimmt, um das Tutorial einigermassen zu
verstehen solltest du zumindest eine Ahnung von C haben. Kenntnisse der Windows API sind keine Voraussetzung denn wir
werden hier sowieso nur Konsolenanwendungen entwickeln. Weiterhin ist natürlich ein Compiler erforderlich um die Beispiele
zu kompilieren. Wie ihr an so einen kommt sieht ihr unter http://www.c-worker.ch/compiler/index.htm. Ich empfehle den von
Borland, welcher gratis ist. (falls du mit der Installation mühe hasst lies das Readme und falls es immer noch nicht geht:
http://www.c-worker.ch/tuts/bcchowto.txt).

Noch ein wichtiger Hinweis: einige Socket Funktionen sind sogenannte "Blocking Calls". Das heisst eine Funktion wird nicht
zurückkehren bis ihre Aufgabe erfüllt ist. So bleibt das Programm zB bei einem recv() oder accept() stehen bist Daten
empfangen wurden (recv()) bzw. eine Verbindung akzeptiert wurde (accept()). Das sorgt vorallem am Anfang für Verwirrung.
Häufig arbeitet man mit Threads um mit diesen Blocking Calls gut arbeiten zu können.

2. Was sind Sockets ?

Eins mal vorweg: Der Inhalt dieses Abschnittes wurde erst kürzlich eingefügt. Wenn du gerade erst mit der Winsock
Programmierung anfängst, ist es eventuell besser du überspringst ihn gleich mal, weil er vielleicht mehr Verwirrung
als Klarheit schaft. Den Inhalt dieses Abschnittes zu begreiffen ist relativ unwichtig für den Anfang.
Theoretisch gesehen ist ein Socket der Endpunkt einer Verbindung. Zwei Sockets definieren somit eine Verbindung. Ein
Socket kann durch eine IP UND eine Portnummer eindeutig identifiziert werden.
In der Praxis ist ein Socket ein File Desriptor (eine Nummer, also ganz einfach ein Integer). Wie man normale Files lesen und
schreiben kann, ermöglicht es einem das Betriebssystem auch Sockets wie eine Datei zu lesen (Daten empfangen) und zu
schreiben (Daten senden). Das sieht man u.A. auch daran das man unter Unix mit read() und write() Daten über Sockets
senden und empfangen kann. Unter Windows kann man nur mit Windows NT/2000/XP per ReadFile() und WriteFile() Daten
über Sockets senden und empfangen. Wie auch immer, es ist sowieso empfehlenswert send() und recv() zum senden und
empfangen der Daten zu verwenden (das geht dann auch unter allen Windows Versionen), aber mehr dazu später. Also
wenn du jetzt verwirrt bist, vergiss das mit den Dateien gleich wieder.

3. Winsock starten

1 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

Damit die Winsock Funktionen uns überhaupt bekannt sind, müssen wir erst mal die benötigten Header einbinden:

#include <windows.h>
#include <winsock2.h>

und damit wir noch etwas in die Konsole ausgeben können fügen wir noch ein

#include <stdio.h>

ein.

Bevor eine Anwendung überhaupt Windows Socket Funktionen verwenden kann muss man als allererstes die Funktion
WSAStartup aufrufen. So lange die Funktion WSAStartup nicht erfolgreich aufgerufen wurde, kann die entsprechende
Anwendung keine Socket Funktionen verwenden und jede Funktion wird den Fehler WSANOTINITIALISED zurückgeben.
Aber nun zu der Funktion WSAStartup, diese ist folgendermassen definiert:

int WSAStartup (

WORD wVersionRequested,

LPWSADATA lpWSAData

);

Zu den beiden Parametern:

-wVersionRequested: Mit diesem Parameter legt man die Winsock Version fest die man verwenden möchte. Dabei muss im
High Order Byte die Minor Version und im Low Order Byte die Major Versionsnummer angegeben werden. Das klingt
eventuell für einige recht verwirrend.
Erstmal zu WORD, WORD ist ein 2 Byte Datentyp und nichts anderes als ein unsigned short. Definiert ist WORD in windef.h,
und zwar folgendermassen:

typedef unsigned short WORD;

Eben, wie schon gesagt hat ein WORD 2 Bytes, dabei ist das High Order das linke und das Low Order das Rechte Byte:

[ 7 6 5 4 3 2 1 0 ] [ 7 6 5 4 3 2 1 0 ]

[ WORD ]

[ High Order Byte ] [ Low Order Byte ]

Allerdings sind "rechts" und "links" keine besonders gute Beschreibungen. Einfach gesagt ist das High Order Byte das Byte
mit dem grössten Wert (Ziffern die mehr Rechts sind haben ja einen höherern Wert) und das Low Order Byte das Byte mit
dem niedrigsten Wert.
Also wenn wir zB Version 1.2 wollen, müssen wir ins Hight Order Byte 2 und ins Low Order Byte 1 schreiben.
Da könnte man z.B. so machen:

WORD version;

version=(2<<8)+1;

Das wäre eine Möglichkeit, wir verwenden jedoch ein Makro der Win32 Api welches genau dasselbe macht: MAKEWORD.
MAKEWORD übernimmt 2 Parameter: Als ersten Parameter das Low Order Byte als zweiten das Hight order Byte:

WORD MAKEWORD(

BYTE bLow, // low-order byte

BYTE bHigh // high-order byte

);

2 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

Hier noch schnell einige Beispiele:


für Version 1.2: MAKEWORD(1,2);
für Version 2.0: MAKEWORD(2,0);
etc..

Aber nun zurück zur Funktion WSAStartup. Als zweiten Parameter (lpWSAData) muss man einen Pointer auf eine Struktur
vom Typ WSADATA übergeben, diese Struktur wird dann mit Informationen zu der Winsock Version gefüllt, diese sind für
uns aber nicht wichtig, und wird übergeben einfach einen Pointer und belassen es dabei.

Nun schreiben wir uns eine kleine Funktion die Winsock startet:

int startWinsock()

WSADATA wsa;

return WSAStartup(MAKEWORD(2,0),&wsa);

WSAStartup gibt 0 zurück wenn kein Fehler auftauchte. Unsere Funktion gibt einfach den Rückgabewert von WSAStartup
zurück, diesen werden wird dann in main prüfen, wir erweitern also unsere Datei folgendermassen:

#include <windows.h>

#include <winsock2.h>

#include <stdio.h>

//Prototypen

int startWinsock(void);

int main()

long rc;

rc=startWinsock();

if(rc!=0)

printf("Fehler: startWinsock, fehler code: %d\n",rc);

return 1;

else

printf("Winsock gestartet!\n");

return 0;

int startWinsock(void)

WSADATA wsa;

3 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

return WSAStartup(MAKEWORD(2,0),&wsa);

Ich habe die Datei mal sock.c genannt und habe sie folgendermassen kompiliert:

C:\borland\Bin>bcc32 C:\sock.c

Es sollte eigentlich 0 Fehler und 0 Warnungen geben.


Hinweis: Falls man Visual Studio verwendet muss man bei den Projekteigenschaften unter Linker ws2_32.lib zu den
Libraries hinzufügen.

Wenn man das Programm dann von der Konsole aus startet hat man hoffentlich folgende Ausgabe:

Winsock gestartet!

Falls nicht ist eventuell die angeforderte Winsock Version nicht korrekt installiert oder bei lpWSAData wurde ein ungültiger
Pointer übergeben..

4. Typen von Sockets

Ok, Winsock ist nun für unsere Anwendung gestartet und wir können alle Socket Funktionen verwenden. Bevor wird aber
einen Socket erstellen ist es eventuell noch hilfreich zu wissen was es alles für Socket Typen gibt. Dabei wird hier nur auf die
zwei wichtigsten kurz eingegangen: TCP Sockets und UDP Sockets:
TCP Sockets werden wohl am häufigsten verwendet. TCP Sockets arbeiten mit Verbindungen: Es muss eine Verbindung
aufgebaut werden, dann können Daten hin und her übertragen werden, und wenn man damit fertig ist trennt man die
Verbindung wieder. Hier arbeitet man auch mit dem sogenannten Client-Server Modell.
UDP Sockets dagegen sind nicht verbindungsorientiert. Sie senden einfach Daten an einen anderen Rechner und können
Daten von anderen Rechnern empfangen, ohne eine Verbindung aufbauen zu müssen. In diesem Beispiel wird ein TCP
Socket verwendet.

5. Client Socket erstellen

Da TCP Sockets wie gesagt verbindungsorientiert sind, müssen wir nun entscheiden ob unser Socket ein Client Socket (Also
ein Socket der Verbindung zu einem Server Socket aufbaut) oder ein Server Socket (ein Socket der auf Verbindungen von
Client Sockets wartet und diese ggf. annimmt) sein soll. Als erstes erstellen wir mal einen Client Socket, da das wesentlich
einfacher ist. Nun bleibt die Frage zu welchem Server wir eine Verbindung aufbauen wollen ? Vorläufig noch zu gar keinem,
wir werden nachher einen entwickeln.
Als erstes definieren wir mal einen Socket:

SOCKET s;

Mit diesem Socket können wir natürlich noch gar nichts anfangen, wir haben erst eine Variable s vom Typ SOCKET kreiert,
nun müssen wir dein eigentlichen Socket noch erstellen. Dies geschieht mit der Funktion socket(), die folgendermassen
definiert ist:

SOCKET socket (
int af,
int type,
int protocol
);

-af: Hier muss die Adressfamilie übergeben werden, da wird TCP/IP benutzten verwenden wir hier die Konstante AF_INET
-type: hier muss der Socket Typ angegeben werden (siehe Kapitel 4). Da wir einen TCP Socket wollen geben wir hier
SOCK_STREAM an.
-protocol: hier muss man noch das Protkoll angeben, momentan sollte man hier einfach 0 verwenden

Falls der Socket nicht erstellt werden konnte, gibt die Funktion INVALID_SOCKET zurück, und mit WSAGetLastError() kann
der Fehlercode abgefragt werden. Die meisten Socket Funktionen (ausser WSAStartup) geben nicht einen Fehlercode
zurück, sondern SOCKET_ERROR oder einen anderen Wert. Der eigentliche Fehlercode muss dann immer mit der Funktion
WSAGetLastError() abgefragt werden.

Gut, nun ergänzen wir main() um folgenden Code (fett) um einen Socket zu erstellen:

4 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

long rc;

SOCKET s;

rc=startWinsock();

.....

s=socket(AF_INET,SOCK_STREAM,0);

if(s==INVALID_SOCKET)

printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastError

return 1;

else

printf("Socket erstellt!\n");

return 0;

Nun kann man das ganze wieder kompilieren und ausführen, wenn alles glatt ging sollte man folgende Ausgabe sehen:

Winsock gestartet!

Socket erstellt!

6. Verbindung zu einem Server herstellen

Mit unserem erstellten Socket wollen wir nun auch eine Verbindung zu einem anderen Server Socket herstellen.
Dazu verwendet man die Funktion connect:

int connect (

SOCKET s,

SOCKADDR* name,

int namelen

);

-s: hier muss man den Socket angeben den man verbinden möchte, logischerweise nehmen wir den vorhin erstellten Socket
-name: hier muss ein Pointer auf eine gefüllte SOCKADDR Struktor gegeben werden, diese wird unten erklärt
-namelen: hier muss die Grösse der SOCKADDR Struktur angegeben werden, also sizeof(SOCKADDR);

Der Rückgabewert von connect ist SOCKET_ERROR falls ein Fehler auftrat.

Nun zu der SOCKADDR Struktur: Da wir TCP/IP benutzten, verwenden wir nicht die SOCKADDR Struktur sondern die

5 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

SOCKADDR_IN Struktur, welche mit SOCKADDR kompatibel ist. _IN steht dabei wohl für Internet. SOCKADDR_IN ist
folgendermassen definiert (SOCKADDR und SOCKADDR_IN sind einfach typedef's für struct sockaddr, bzw. struct
sockaddr_in):

struct sockaddr_in{

short sin_family;

unsigned short sin_port;

struct in_addr sin_addr;

char sin_zero[8];

};

- sin_family: Hier müssen wir erneut die Adressfamilie angeben, da wir diese Struktur mit einem Socket mit der
Adressfamilie AF_INET verwenden, müssen wir hier natürlich wieder AF_INET angeben.
- sin_port: Hier müssen wir den Port angeben zu dem wir ein Verbindung herstellen wollen (HTTP ist 80, FTP 21, etc..),
allerdings muss dieser in Network Byte Order angegeben werden. Im Netzwerk sind alle Bytes von Zahlen verkehrt (Im
Vergleich zu Windows) angeordnet. Das umordnen übernimmt die Funktion

u_short htons (u_short hostshort);

glücklicherweise für uns. Man übergibt einfach eine Nummer und htons gibt die selbe Nummer in Network Byte Order zurück.
htons steht für Host To Network Short. Es gibt auch noch htonl wenn man einen long (also 4 Byte) in Network Byte Order
bringen möchte. Da sin_port jedoch ein short ist, verwenden wir logischerweise htons.

- sin_addr: Das ist wohl der komplizierteste Teil der Struktur, hier muss die IP Adresse des Servers angegeben werden.
sin_addr selbst ist vom Typ in_addr, welcher so aussieht:

struct in_addr {
union {
struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;
struct { u_short s_w1,s_w2; } S_un_w;
u_long s_addr;
} S_un;

Zum Glück gibt es aber auch hierfür eine Hilfsfunktion um diese Struktur zu füllen:

unsigned long inet_addr(char* cp );

Diese übernimmt einen String der eine IP Adresse enthält und gibt einen long zurück. Diesen Wert schreiben wir dann
einfach in das s_addr Mitglied der in_addr Struktur.

- sin_zero[8]: Wird nicht verwendet und sollte mit 0 gefüllt sein

Nun ergänzen wir main() wiefolgt (Ergänzungen sind fett):

long rc;

SOCKET s;

SOCKADDR_IN addr;

rc=startWinsock();

.....

memset(&addr,0,sizeof(SOCKADDR_IN)); // zuerst alles auf 0 setzten

addr.sin_family=AF_INET;

6 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

addr.sin_port=htons(12345); // wir verwenden mal port 12345

addr.sin_addr.s_addr=inet_addr("127.0.0.1"); // zielrechner ist unser eigener

rc=connect(s,(SOCKADDR*)&addr,sizeof(SOCKADDR));

if(rc==SOCKET_ERROR)

printf("Fehler: connect gescheitert, fehler code: %d\n",WSAGetLastError());

return 1;

else

printf("Verbunden mit 127.0.0.1..\n");

return 0;

Die Ausgabe sollte nun etwa so aussehen:

Fehler: connect gescheitert, fehler code: 10061

Natürlich gibt es einen Fehler beim verbinden, da wahrscheinlich auf unserem eigenen Rechner kein Server auf port 12345
läuft.
(Aber soweit ich weiss benutzt Backorifice Port 12345, wenn also eine Verbindung zustande kommt seit ihr ev. infisziert :-)

7. Server Socket erstellen

Im Gegensatz zu einem Client der Verbindungen aufbaut, ist die Aufgabe des Servers eine oder mehrere Verbindungen
anzunehmen, und diese zu verwalten.
Dabei ist zu beachten: Für jede Verbindung wird ein Socket benötigt und das auf jeder Seite, also einer beim Client als auch
einer beim Server. Also ist es nicht möglich, dass über einen Socket mehrere Verbindungen laufen. Ein Server benötigt
zusätzlich noch einen Socket über den er Verbindungen annimmt. Dieser Socket macht nichts anderes als die ganze Zeit auf
Verbindungen zu warten und diese anzunehmen. Die eigentliche Verbindung zum Client besteht dann über einen anderen
Socket. Das sieht etwa so aus:

-------

| |

v |

Client Socket -----------------------> Server Socket: listen() -> accept() -

\ connect() |

\ |

7 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

\ v

\------------------------------------------------------> Socket

eigentliche Verbindung zwischen den beiden

Der eigentliche Server Socket ist also dauernd in der Funktion accept. Sobald ein Client ein connect() zu ihm macht gibt
accept() als Rückgabewert einen Socket, der die eigentliche Verbindung zum Client darstellt, zurück. Der Socket der
Verbindungen akzeptiert wird nun erneut mit einem accept()-Aufruf in den accept Modus gebracht. Darin bleibt er auch bis
wieder eine neue Verbindung kommt, usw...

Ok, nun erstellen wir eine neue Datei socksrv.c welche den Server darstellt. Die ersten Schritte sind gleich wie beim Client
Socket, ausser den Name des Sockets habe ich zur besseren Übersicht in acceptSocket geändert:

#include <windows.h>

#include <winsock2.h>

#include <stdio.h>

//Prototypen

int startWinsock(void);

int main()

long rc;

SOCKET acceptSocket;

SOCKADDR_IN addr;

// Winsock starten

rc=startWinsock();

if(rc!=0)

printf("Fehler: startWinsock, fehler code: %d\n",rc);

return 1;

else

printf("Winsock gestartet!\n");

// Socket erstellen

acceptSocket=socket(AF_INET,SOCK_STREAM,0);

if(acceptSocket==INVALID_SOCKET)

printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastErr

return 1;

8 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

else

printf("Socket erstellt!\n");

return 0;

int startWinsock(void)

WSADATA wsa;

return WSAStartup(MAKEWORD(2,0),&wsa);

Ok, ab nun unterscheidet sich der Server vom Client. Welchen Port der Client von vorhin wirklich benutzt hat wissen wir
eigentlich gar nicht. Wir wissen nur das der Client eine Verbindung zum Port 12345 herstellt. Der Socket selbst benötigt
jedoch auch einen Port, und dieser wurde durch Windows für ihn gewählt. Also eine zufällige Portnummer. Bei einem Server
geht das natürlich nicht, wir wollen wissen welchen Port unser Socket verwendet um Verbindungen anzunehmen. Deshalb
müssen wir den Server Socket der die Verbindungen annimmt erst mal an einen Port "binden". Dies geschieht mit der
Funktion bind():

int bind (

SOCKET s,

const struct sockaddr FAR* name,

int namelen

);

ev. hat jemand gemerkt, dass es sich um die selben Parameter wie bei connect() handelt. Hier werden sie jedoch zT. etwas
anders verwendet.
-s: Den socket den wir an einen Port binden wollen
-name: Hier muss die Adressfamilie natürlich wieder auf AF_INET sein, sin_port wird verwendet um den Port anzugeben an
den wir den Socket binden wollen, und die IP Adresse können wir auf ADDR_ANY setzten.
-namelen: Grösse der Struktur im 2. Parameter.

Auch bind() gibt im Fehlerfalle SOCKET_ERROR zurück

Wir ergänzen main wiefolgt:

...

memset(&addr,0,sizeof(SOCKADDR_IN));

addr.sin_family=AF_INET;

9 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

addr.sin_port=htons(12345);

addr.sin_addr.s_addr=ADDR_ANY;

rc=bind(acceptSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN));

if(rc==SOCKET_ERROR)

printf("Fehler: bind, fehler code: %d\n",WSAGetLastError());

return 1;

else

printf("Socket an port 12345 gebunden\n");

return 0;

Auch das sollte wieder fehlerfrei kompilierbar sein. Als nächsten Schritt ist nun listen() aufzurufen damit der Socket auf
Verbindungen wartet:

int listen (
SOCKET s,
int backlog
);

-s: Der Socket welcher auf Verbindungen warten soll


-backlog: nicht von grosser Bedeutung, legt fest wieviele Verbindungen maximal ausstehend sein dürfen. Wir verwenden
mal 10

Listen liefert SOCKET_ERROR zurück falls etwas schief ging.

Wir ergänzen also den Quellcode:

....

rc=listen(acceptSocket,10);

if(rc==SOCKET_ERROR)

printf("Fehler: listen, fehler code: %d\n",WSAGetLastError());

return 1;

else

printf("acceptSocket ist im listen Modus....\n");

Und nun fehlt nur noch accept(), damit unser Socket auch Verbindungen akzeptiert. accept() ist folgendermassen definiert:

10 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

SOCKET accept (
SOCKET s,
struct sockaddr FAR* addr,
int FAR* addrlen
);

-s: Der Socket der Verbindungen akzeptieren soll (muss vorher mit listen() in den entsprechenden Modus gebracht werden),
in unserem Fall acceptSocket
-addr: optional, ein Pointer auf eine SOCKADDR Strucktur in welchem die IP Adresse, etc. des Clients, der eine Verbindung
hergestellt hatt, gespeichert werden, verwenden wir hier nicht.
-addrlen: optional, Pointer in welchem die Länge von addr gespeichert wird, verwenden wir hier nicht.

Falls accept fehlschlägt wird INVALID_SOCKET zurückgegeben.

accept() ist nun ein sogenannter "Blocking Call" (siehe Einleitung), und wird nicht zurückkehren bevor nicht eine Verbindung
akzeptiert wurde oder sonst ein Fehler auftrat.

Wir ergänzen also den Code wiefolgt (fett):

long rc;
SOCKET acceptSocket;
SOCKET connectedSocket;
SOCKADDR_IN addr;

....

connectedSocket=accept(acceptSocket,NULL,NULL);

if(connectedSocket==INVALID_SOCKET)

printf("Fehler: accept, fehler code: %d\n",WSAGetLastError());

return 1;

else

printf("Neue Verbindung wurde akzeptiert!\n");

Wenn man den Server nun kompiliert und ausführt so wird das Pogramm bei der Zeile

acceptSocket ist im listen Modus....

stehen bleiben. Das ist auch logisch da, accept() ja ein "Blocking Call" ist.
Unterdessen kann man nun in einer zweiten Konsole das Programm von vorhin, also den Client, starten und wenn ihr alles
richtig gemacht habt sollten folgende Ausgaben erscheinen (immer zuerst den Server starten!!):

Client:

Winsock gestartet!
Socket erstellt!

11 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

Verbunden mit 127.0.0.1..

Server:

Winsock gestartet!
Socket erstellt!
Socket an port 12345 gebunden
acceptSocket wartet auf Verbindungen.....
Neue Verbindung wurde akzeptiert!

Nun das war soeben der erste Erfolg, wir haben mit unserem Client Programm eine Verbindung zum Server hergestellt. Nur
leider werden beide Programme danach beendet und die Verbindung ist wieder weg. Deshalb werden wir im nächsten
Kapitel Daten zwischen den Beiden austauschen.

8. Daten austauschen

Das Senden und Empfangen von Daten ist beim Server und Client wieder identisch. Dazu stehen die folgenden beiden
Funktionen zur Verfügung:

send:

int send (
SOCKET s,
const char FAR * buf,
int len,
int flags
);

-s: Socket über den wir die Daten senden wollen


-buf: Pointer auf einen Buffer in dem die zu sendenden Daten enthalten sind
-len: Wieviele Bytes von buf gesendet werden sollen
-flags: Brauchen wir nicht

Rückgabewert: Anzahl der gesendeten Bytes oder SOCKET_ERROR bei einem Fehler.

recv:

int recv (
SOCKET s,
char FAR* buf,
int len,
int flags
);

-s: Socket über den wir Daten empfangen wollen


-buf: Pointer zu einem Buffer in dem die empfangenen Daten gespeichert werden sollen
-len: Grösse des Buffers (oder wieviele Daten im Buffer gespeichert werden sollen)
-flags: benötigen wir nicht

Rückgabewert: Anzahl der empfangenen Bytes, 0 falls die Verbindung vom Partner getrennt wurde oder SOCKET_ERROR
bei einem Fehler.

Hier noch schnell je ein Beispiel (gehört nicht zu sock.c oder socksrv.c, Annahme: s ist ein verbundener Socket):

char buf[256];

SOCKET s;

long rc;

...

12 von 13 14.11.2005 16:00


Winsock Tutorial http://www.c-worker.ch/tuts/wstut_op.html

strcpy(buf,"Hallo wie gehts?");

rc=send(s,buf,9,0);

Das würde die ersten 9 Zeichen von buf senden, in diesem Falle "Hallo wie", rc enthält die Anzahl der gesendeten Zeichen,
falls alles glatt ging sollte das auch wieder 9 sein.

char buf[256];

SOCKET s;

long rc;

....

rc=recv(s,buf,256,0);

Hier werden maximal 256 Zeichen empfangen, es können auch weniger empfangen werden, bei recv dient der dritte
Parameter nur dazu die grösse des Buffers im zweiten Parameter anzugeben. Wieviele Zeichen wirklich empfangen wurden
sieht man im Rückgabewert (in diesem Falle rc).

Wie man sieht sind sich die beiden Funktionen recht ähnlich. Nur dass buf bei send() zum lesen der zu sendenden Daten
und bei recv() zum speichern der empfangenen Daten verwendet wird. Aber in beiden Fällen muss buf auf einen gültigen
Buffer im Speicher zeigen, und len darf nicht grösser als die Grösse des Buffers sein. Es sind auch beides Blocking Calls.
Nur wird man das bei send() nicht gross merken, weil die Daten relativ schnell gesendet sind und die Funktion dann
zurückkehrt. recv() jedoch wartet bis Daten ankommen, und falls der Partner nichts sendet, wartet er ewig.

Wir erweitern nun beide Programme folgendermassen: Der Client sendet einen vom Benutzer eingegebenen String an den
Server, und dieser macht nicht anderes als mit "Du mich auch " + der Nachricht die er empfangen hat, zu antworten. Unten
sind beide Programme nochmals vollständig aufgeführt.

Noch ein kleiner Hinweis: Wenn man mit Strings arbeitet hat man mehrere Möglichkeiten:
- Entweder sendet man das terminierende \0 gleich mit, und so kann der andere den String gleich ausgeben
oder die Variante die ich gewählt habe (ist etwas sicherer)
- Man prüft zuerst den Rückgabewert von recv, ist es kein Fehler macht man folgendes:

buf[rc]='\0';

(Annahme: buf ist der Buffer der die Daten speichert, und rc der Rückgabewert)
Da recv() ja die Anzahl der empfangenen Bytes zurück gibt wird so automatisch hinter dem letzten empfangenen Zeichen ein
\0 angefügt.

Hier die beiden Dateien: sock.c, socksrv.c


Diese beiden Beispiele sind etwas umfänglich geschrieben und fangen auch keine fehlerhaften Eingaben ab, aber das war
auch nie die Absicht, es soll ja nur ein kleines Beispiel sein.

9. Aufräumen

Falls ihr die beiden fertigen Dateien angesehen habt ist wohl schon klar wie man am Schluss jeder Winsock Anwendung
noch aufräumt:
Jeder Socket muss mit closesocket() geschlossen werden, und ganz am Ende muss ein WSACleanup() Aufruf erfolgen.
Natürlich müsste auch nach jedem "return 1;" im Quellcode alles aufgeräumt werden, aber das wurde hier bewusst
weggelassen um Platz zu sparen.

13 von 13 14.11.2005 16:00

Вам также может понравиться