000
26.03.2004, 11:41 Uhr
typecast
aka loddab (Operator)
|
Servus
da ich mich in letzter Zeit ein bischen damit beschäftigt habe, habe ich mir mal gedacht, dass ich mal einen kleinen Beitrag dazu in die FAQ stelle.
Mit diesem Programm kann man eine TCP/IP Verbindung zu einem Server aufbauen und von diesem Daten lesen. Der Client ist in C geschrieben (getestet mit dem gcc), sollte aber auch mit einem C++ Compiler kompiliert werden (getestet mit g++)
C++: |
#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <stdio.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <string.h>
|
Diese hier eingebundenen Headerdateien sind quasi für alle Netzwerkanwendungen pflicht und sollten auch so eingebunden werden. Dabei ist auch auf die Reihenfolge zu achten. Z.B. muss sys/types.h vor sys/socket.h eingebunden werden, da sonst alle möglichen Fehler auftreten. Sollten unerklärliche Fehler bei einer Anwendung auftreten, dann sollte die Reihenfolge der Header überprüft werden.
C++: |
#define MAXLINE 255
int main(int argc, char* argv[]) { int s, n; struct sockaddr_in servaddr; struct hostent *hp; char readLine[MAXLINE]; int port;
if (argc != 3){ fprintf(stderr, "Usage: %s <hostname> <portnummer>\n", argv[0]); exit(1); }
|
Der Client wird mit zwei Argumenten aufgerufen. Dem Namen des Hosts zu dem verbunden werden soll und der Port über den die Verbindung läuft.
C++: |
if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0){ strerror(errno); exit(1); }
|
Als erstes wird ein Socket erzeugt, mit dessen Hilfe man auf Funktionen wie read() und write() zugreifen kann. Der Parameter AF_INET zeigt an, dass es sich hierbei um eine Verbindung handelt, die IP4 verwendet. Durch SOCK_STREAM wird angezeigt, dass es sich um eine TCP-Verbindung handeln soll. Um eine UDP-Verbindung aufzubauen, muss SOCK_STREAM durch SOCK_DGRAM ersetzt werden.
C++: |
memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; port = atoi(argv[2]); servaddr.sin_port = htons(port);
|
Hier wird der Server angeben, zu dem eine Verbindung aufgebaut werden soll. Dazu dient eine Variable des structs sockaddr_in. Dieses wird zuerst mit 0 initialisiert und anschließend mit den Werten gefüllt. Als erstes wird als Protokollfamiliy AF_INET angegeben. Damit wird angegeben, als Kommunikationsgrundlage IP verwendet werden soll. Als nächstes wird der Port angegben. Dieser wird aus dem zweiten Kommandozeilenargument gebildet. Der Aufruf der Funktions htons() ist nötig, da nicht alle Computer eine Zahl gleich interpretieren (wer mehr darüber wissen will, kann mal unter www.google.de nach den Begriffen host byteorder suchen. bin gerade zu faul das selber zu machen).
C++: |
hp = NULL; if ((hp = gethostbyname(argv[1])) == NULL){ fprintf(stderr, "Konnte host leider nicht finden\n"); exit(1); } memcpy(&servaddr.sin_addr, hp->h_addr, hp->h_length);
|
Hier wird nun nach dem Host gesucht. Informationen über den host werden in einer Variablen vom Typ struct hostent gespeichert. Mit Hilfe der Funktion gethostbyname() wird versucht Informationen über den als Parameter angegebenen Host zu finden. Ein Beispiel für einen Aufruf wäre gethostbyname("localhost"); (holt sich Informationen über den eigenen Rechner) oder gethostbyname("gmx.de"); Der Rückgabewert ist ein Pointer auf struct hostent, oder NULL wenn der Host nicht gefunden werden konnte. In diesem Fall können wir das Programm abbrechen, weil es dann nichts zum verbinden gibt :-) Haben wir die nötigen Informationen über den host werden sie mit Hilfe der memcpy() Funktion an die richtige Stelle kopiert.
C++: |
if (connect(s, (struct sockaddr*) &servaddr, sizeof(servaddr)) < 0){ fprintf(stderr, "Fehler bei connect\n"); exit(1); }
|
So da wir jetzt alles was für eine Verbindung notwendig ist haben, können wir die Verbindung jetzt aufnehmen. Dies geschieht mit Hilfe der Funktion connect. Als Parameter bekommt sie den Socket und die Serverinformationen mit. Da es sich bei sockaddr_in um eine Spezialform von Serverinformationen handelt und connect() auch andere structs aufnehmen kann (für andere Verbindungsarten, glaube ich) muss noch eine Konvertierung nach struct sockaddr* vorgenommen werden.
C++: |
while ((n = read(s, readLine, MAXLINE)) > 0){ readLine[n] = 0; printf("%s\n", readLine); }
|
Die Verbindung steht jetzt und wir können mit Hilfe von read() anfangen Daten vom Server zu lesen. Dabei ist zu beachten, dass das Programm solange auf Daten vom Server wartet, bis a) ein Fehler aufgetreten ist (rückgabewert von read == -1) b) die Verbindung vom Server geschlossen wurde (Rückgabewert von read == 0) c) Daten ankommen (Rückgabewert von read == (Anzahl der Empfangen Bytes)) Wie man ein blocken der Funktion vermeidet werde ich irgendwann später schreiben.
Ist der Rückagbewert also größer Null, dann gehen wir auf Nummer sicher und beenden den string mit einer 0. Man könnte auch schreiben readLine[n] = '\0'. Anschließend geben wir den Emfangenen String aus. Wird die Schleife beendet, räumen wir noch ein bischen auf.
So und das wars auch schon. Fertig ist der erste selbstgeschriebene Client. Wie man einen dazugehörigen Server schreibt erzähle ich dann ein anderes mal :-) -- All parts should go together without forcing. ... By all means, do not use a hammer. (IBM maintenance manual, 1925) |