|
Winsock Tutorial von c-worker.ch (Teil 3: UDP) Dieses Tutorial stammt von
www.c-worker.ch. Hinweise Falls beim kompilieren
einige "Neudefinition" Fehler kommen entfernt die "#include <winsock2.h>"
Zeile (wurde in diesem Fall schon in windows.h includiert)
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 Im ersten
Tutorial wurden diese beiden Socket Typen bereits einmal kurz vorgestellt.
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 ( Dabei verwenden wir alle
Parameter gleich wie wenn wir einen TCP Socket erstellen wollten, ausser der
Parameter type unterscheidet sich natürlich. Anstelle der Konstante
SOCK_STREAM welche für TCP Sockets ist verwenden wir hier SOCK_DGRAM
für UDP. SOCKET s; s=socket(AF_INET,SOCK_DGRAM,0); if(s==INVALID_SOCKET) { printf("Fehler: Der UDP Socket konnte nicht erstellt werden\n"); } 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 <winsock2.h> #include <stdio.h> #include <stdlib.h> //Prototypen int startWinsock(void); int main() { long rc; SOCKET s; rc=startWinsock(); if(rc!=0) { printf("Fehler: startWinsock, fehler code: %d\n",rc); 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! Das war es auch schon, denn von jetzt an können wir mit sendto() und recvfrom() Daten austauschen.
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 ( -s: Socket über
den wir die Daten senden wollen Die Funktion liefert wie bei send() die Anzahl der gesendeten Zeichen zurück oder SOCKET_ERROR bei einem Fehler. Hier ein kleines Beispiel für die verwendung von sendto() (Annahme: s ist ein UDP Socket): SOCKET s; SOCKADDR_IN addr; char buf[256]; ... // addr vorbereiten addr.sin_family=AF_INET; addr.sin_port=htons(1234); addr.sin_addr=inet_addr("127.0.0.1"); strcpy(buf,"Hallo Welt!");
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 ( -s: Socket über
welchen wir Daten empfangen wollen 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 s ein bereits erstellter UDP Socket ist: SOCKET s;
Wir erweitern unser Beispiel udpcl.c nun so, das wir einen Text eingeben können und diesen dann nach 127.0.0.1 an den Port 1234 senden. Nachdem wir die Nachricht gesendet haben warten wir bis wir eine Antwort empfangen und dann das ganze wieder von vorne. Änderungen sind fett dargestellt: int main() rc=startWinsock(); if(rc!=0) { printf("Fehler: startWinsock, fehler code: %d\n",rc); return 1; } else { printf("Winsock gestartet!\n"); } //UDP Socket erstellen s=socket(AF_INET,SOCK_DGRAM,0); if(s==INVALID_SOCKET) { printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("UDP Socket erstellt!\n"); } // addr vorbereiten addr.sin_family=AF_INET; addr.sin_port=htons(1234); addr.sin_addr.s_addr=inet_addr("127.0.0.1"); while(1) { printf("Text eingeben: "); gets(buf); rc=sendto (s,buf,strlen(buf),0,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN)); if(rc==SOCKET_ERROR) { printf("Fehler: sendto, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("%d Bytes gesendet!\n", rc); } rc=recvfrom(s,buf,256,0,(SOCKADDR*)&remoteAddr,&remoteAddrLen); if(rc==SOCKET_ERROR) { printf("Fehler: recvfrom, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("%d Bytes empfangen!\n", rc); buf[rc]='\0'; printf("Empfangene Daten: %s\n",buf); } } return 0; } Das ist eigentlich schon der fertige Client.
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 <winsock2.h> #include <stdio.h> #include <stdlib.h> //Prototypen int startWinsock(void); int main() { long rc; SOCKET s; char buf[256]; char buf2[300]; SOCKADDR_IN addr; SOCKADDR_IN remoteAddr; int remoteAddrLen=sizeof(SOCKADDR_IN); rc=startWinsock(); if(rc!=0) { printf("Fehler: startWinsock, fehler code: %d\n",rc); return 1; } else { printf("Winsock gestartet!\n"); } //UDP Socket erstellen s=socket(AF_INET,SOCK_DGRAM,0); if(s==INVALID_SOCKET) { printf("Fehler: Der Socket konnte nicht erstellt werden, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("UDP Socket erstellt!\n"); } addr.sin_family=AF_INET; addr.sin_port=htons(1234); addr.sin_addr.s_addr=ADDR_ANY; rc=bind(s,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN)); if(rc==SOCKET_ERROR) { printf("Fehler: bind, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("Socket an Port 1234 gebunden\n"); } while(1) { rc=recvfrom(s,buf,256,0,(SOCKADDR*)&remoteAddr,&remoteAddrLen); if(rc==SOCKET_ERROR) { printf("Fehler: recvfrom, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("%d Bytes empfangen!\n", rc); buf[rc]='\0'; } printf("Empfangene Daten: %s\n",buf); //Antworten sprintf(buf2,"Du mich auch %s",buf); rc=sendto(s,buf2,strlen(buf2),0,(SOCKADDR*)&remoteAddr,remoteAddrLen); if(rc==SOCKET_ERROR) { printf("Fehler: sendto, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("%d Bytes gesendet!\n", rc); } } return 0; } int startWinsock(void) { WSADATA wsa; 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 Socket aufruft stellt er auch KEINE Verbindung her ! In wirklichkeit wird damit einfach der Standard-Zielrechner angegeben. Somit wird bei 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 rc=connect(s,(SOCKADDR*)&addr,sizeof(SOCKADDR_IN)); while(1) //rc=recvfrom(s,buf,256,0,(SOCKADDR*)&remoteAddr,&remoteAddrLen); rc=recv(s,buf,256,0); if(rc==SOCKET_ERROR) { //printf("Fehler: recvfrom, fehler code: %d\n",WSAGetLastError()); printf("Fehler: recv, fehler code: %d\n",WSAGetLastError()); return 1; } else { printf("%d Bytes empfangen!\n", rc); buf[rc]='\0'; printf("Empfangene Daten: %s\n",buf); } } return 0; }
Auch dieses Tutorial ist
hiermit zu Ende. Die beiden Dateien können hier heruntergeladen werden:
udpcl.c, udpsrv.c
|