die "#include " Zeile (wurde in diesem Fall schon in windows.h includiert) Manche Leute berichten auch, dass Sie den Fehler beheben können indem sie winsock2.h vor windows.h includieren. Falls der Compiler INADDR_ANY nicht finden kann verwendet ADDR_ANY (oder umgekert, eins von beiden geht schon) Das Progamm muss gegen ws2_32.lib gelinkt werden. Falls man Visual Studio verwendet muss man bei den Projekteigenschaften unter Linker ws2_32.lib zu den Libraries hinzufügen. Eventuell schafft aber auch folgende Zeile am Anfang des Quellcodes Abhilfe: #pragma comment( lib, "ws2_32.lib" ) 1. Einleitung -- 7. Verwendung von connect() bei UDP 1. Einleitung Im ersten Tutorial wurden ausschliesslich TCP Sockets behandelt. Dieses Tutorial soll nun auf UDP Sockets eingehen. Es ist empfehlenswert zuerst das erste Tutorial zu lesen, weil dieses am ersten anknüpft und Kenntnisse aus dem ersten Tutorial voraussetzt. 2. Unterschied zwischen TCP und UDP Sockets -- bestimmten Server her, tauschen dann Daten aus, und schliessen die Verbindung am Schluss wieder. Das ist bei UDP Sockets alles nicht nötig. Nun werden sich einige vielleicht fragen: "Wohin sendet er dann die Daten, wenn er nicht zu einem bestimmten Server verbunden ist ?". Ganz einfach, bei UDP Sockets verwendet man nicht send() sondern eine Funktion sendto(). Bei dieser Funktion kann man als Parameter übergeben wohin die Daten gesendet werden sollen. Auch würde bei UDP Sockets ein recv() nicht viel Sinn machen. Man würde zwar die Daten bekommen, jedoch möchte man meistens noch darauf antworten (zumindest im Falle des Servers). Wie weiss man nun aber wem man antworten möchte ? Hier steht für UDP auch wieder eine spezielle Funktion zu verfügung: recvfrom(). recvfrom() übernimmt zusätzlich 2 Pointer welche dann mit den Informationen über den ursprünglichen -- werden wir auch hier wieder zwei kleine Programme entwickeln: einen Client und einen Server. 3. Einen UDP Socket erstellen Natürlich müssen wir erst wieder Winsock starten, dies wurde aber bereits im ersten Tutorial behandelt, und wird deshalb hier kommentarlos aufgeführt. Zum erstellen eines UDP Sockets verwendet man gleich wie bei TCP Sockets die Funktion socket(), welcher hier nochmal aufgeführt ist: SOCKET socket ( -- Natürlich läuft das so alleine noch nicht, da noch ein WSAStartup() erforderlich ist. 4. Erstellen eines UDP Clients Wir schreiben nun einen UDP Client. Erst muss einmal Winsock gestartet werden, und ein UDP Socket (wie oben gezeigt) erstellt werden. Die Funktion startWinsock() stammt aus dem ersten Tutorial und wird hier kommentarlos aufgeführt. Hier der entsprechende Code: #include #include -- Ich habe die Datei mal udpcl.c gennant und mit bcc32 C:\udpcl.c kompiliert. Wenn man das Beispiel nun ausführt sollte es folgende Ausgabe liefern: Winsock gestartet! UDP Socket erstellt! -- 5. Daten austauschen mit sendto und recvfrom Wie oben bereits erwähnt benutzten wir für den Datentransfer bei UDP sendto() und recvfrom(). Die beiden Funktionen werden hier kurz vorgestellt. sendto() sendet die Daten an einen bestimmen Host, diesen müssen wir mit der bereits bekannten SOCKADDR_IN Struktur angeben. Die Funktionsdefinition: int sendto ( SOCKET s, const char FAR * buf, int len, -- const struct sockaddr FAR * to, int tolen ); -s: Socket über den wir die Daten senden wollen -buf: Pointer auf einen Buffer der die zu sendenden Daten enthält -len: Wieviele Zeichen von buf gesendet werden sollen -flags: benötigen wir nicht, auf 0 setzten -to: in unserem Falle ein Pointer auf eine SOCKADDR_IN Struktur die Informationen über den Zielrechner enthält -tolen: länge von to, in userem Fall sizeof(SOCKADDR_IN) Die Funktion liefert wie bei send() die Anzahl der gesendeten Zeichen -- Und nun zum empfangen von Daten: recvfrom() hat wie sendto() 2 zusätzliche Parameter. Diese haben jedoch nicht die gleiche Verwendung. Der erste zusätzliche Parameter ist ein Pointer auf eine SOCKADDR Struktur, in welche Informationen über den Rechner gespeichert werden von welchem wir die Daten empfangen haben. Wir müssen diese Stuktur nicht initialisieren, sondern sie wird von recvfrom() für uns abgefüllt. Der zweite zusätzliche Parameter ist ein Pointer auf ein int, welcher die grösse des ersten zusätzlichen Parameters, also die grösse einer SOCKADDR_IN Struktur in unserem Falle, enthält. Dieser int muss von uns initialisiert werden, muss jedoch als Pointer übergeben werden damit recvfrom() die benötigte grösse rein schreiben kann, falls sie zu klein ist. Aber nun zur Funktionsdefinition: int recvfrom ( SOCKET s, -- int FAR* fromlen ); -s: Socket über welchen wir Daten empfangen wollen -buf: Pointer auf einen Buffer in dem die Daten gespeichert werden -len: Grösse von buf (oder die maximale anzahl Zeichen die in buf gespeichert werden sollen) -flags: benötigen wir nicht, auf 0 setzten -from: Optionaler Pointer in welchem die Informationen über den sender der Daten für uns gespeichert werden -fromlen: Optionaler Pointer in welchen wir die grösse von from speichern müssen Die Funktion liefert wie recv() die anzahl der empfangenen Zeichen zurück oder 0 falls die Verbindung geschlossen wurde oder SOCKET_ERROR bei einem Fehler. Hier wieder ein kleines Beispiel, es wird wieder davon ausgegangen das -- 6. Erstellen eines UDP Servers Wie bereits erwähnt unterscheiden sich Client und Server von den Winsock API aufrufen her nur sehr gering. Der einzige Unterschied des Server ist, das er bind() aufrufen muss, um nicht einen zufälligen Port zu bekommen, sondern genau den den wir wollen. Und da unser Client seine Daten zu Port 1234 sendet, ist das logischerweise der Port 1234. bind() wurde bereits im ersten Tutorial behandelt. Auch sendto() und recvfrom() wurden oben bereits durchgenommen. Der einzige Unterschied beim Server: beim Client haben wir beim recvfrom() aufruf die letzten beiden Parameter (die ja optional sind) theoretisch umsonst übergeben, da wir sie gar nicht verwendeten. Beim Server werden diese jedoch verwendet, sie sind sogar sehr wichtig, damit wir dem richtigen Rechner antworten können. Der Server Unterscheidet sich auch noch dadurch das er erst recvfrom() aufruft und erst dann sendto(), er muss ja erst etwas empfangen um antworten zu können. Das Beispiel ist unten ohne weitere Kommentare aufgefürt, da ja ansich nichts neues darin ist. Er macht übrigens das gleiche wie im ersten Tutorial: er setzt einfach "Du mich auch " vorne hin. Ich habe die Datei mal udpsrv.c genannt: #include -- return WSAStartup(MAKEWORD(2,0),&wsa); } Nun kann man in der einen Konsole den Server starten und in der anderen den Client (den Server zuerst!), und die beiden sollten problemlos ihre Daten austauschen. 7. Verwendung von connect() bei UDP Ich habe vorhin nur die halbe Wahrheit erzählt: Man muss bei UDP nicht zwingend sendto() und recvfrom() verwenden, man kann auch einfach send() und recv() nehmen. Das setzt jedoch voraus das wir vorher einen connect() aufruf auf unseren UDP Socket gemacht haben. Einige werden sich nun Fragen: "Aber heisst es nicht, UDP sei NICHT verbindungsorientiert ?". Ja ! Das ist auch weiterhin so ! UDP ist NICHT verbindungsorientiert. Und wenn man nun connect() auf einen UDP -- UDP mit recv() und send() einfach der mit connect() festgelegte Standardrechner als Ziel verwendet. Wenn man nun aber nicht an den Standardrechner senden bzw. von ihm empfangen möchte kann man immernoch sendto() und recvfrom() verwenden. Damit wir bei unserem Client nicht immer sendto() und recvfrom() verwenden müssen, werden wir ihn nun so umschreiben, das er per connect() das Standardziel festlegt und immer dorthin sendet. Änderungen sind fett hervorgehoben, und gelöschte Zeile sind fett auskommentiert: ... // addr vorbereiten -- Auch dieses Tutorial ist hiermit zu Ende. Die beiden Dateien können hier heruntergeladen werden: udpcl.c, udpsrv.c Mir ist klar das der Quellcode recht unsauber ist, WSACleantup(), closesocket(), etc. wurden extra weggelassen um Platz zu sparen, und das while(1) ist auch etwas unschön, aber es soll ja nur ein kleines BEISPIEL sein.