25.6 (Cross-Plattform-)TCP-Echo-Server
In diesem Abschnitt schreiben wir als Beispiel einen einfachen portablen TCP-Echo-Server mit fast allen Funktionen, die eben beschrieben wurden. Befindet sich beispielsweise die Server-Software auf einem Rechner mit der IP-Adresse 196.12.32.6, so können Sie mit der Clientanwendung unter Angabe der entsprechenden IP-Adresse einen einfachen String via Kommandozeile senden. Der Server gibt diesen String mitsamt der Herkunft des Clients (IP-Adresse) und des Datums mit Uhrzeit auf die Standardausgabe aus.
25.6.1 Der Client
Hier sehen Sie den Quellcode der Clientanwendung:
/* client.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #ifdef _WIN32 /* Headerfiles für Windows */ #include <winsock.h> #include <io.h> #else /* Headerfiles für UNIX/Linux */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> #endif #define PORT 1234 #define RCVBUFSIZE 8192 /* Funktion gibt aufgetretenen Fehler aus und * beendet die Anwendung. */ static void error_exit(char *errorMessage) { #ifdef _WIN32 fprintf(stderr,"%s: %d\n", errorMessage, WSAGetLastError()); #else fprintf(stderr, "%s: %s\n", errorMessage, strerror(errno)); #endif exit(EXIT_FAILURE); } int main( int argc, char *argv[]) { struct sockaddr_in server; struct hostent *host_info; unsigned long addr; #ifdef _WIN32 SOCKET sock; #else int sock; #endif char *echo_string; int echo_len; #ifdef _WIN32 /* Initialisiere TCP für Windows ("winsock"). */ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD (1, 1); if (WSAStartup (wVersionRequested, &wsaData) != 0) error_exit( "Fehler beim Initialisieren von Winsock"); else printf("Winsock initialisiert\n"); #endif /* Sind die erforderlichen Kommandozeilenargumente vorhanden? */ if (argc < 3) error_exit("usage: client server-ip echo_word\n"); /* Erzeuge das Socket. */ sock = socket( AF_INET, SOCK_STREAM, 0 ); if (sock < 0) error_exit( "Fehler beim Anlegen eines Sockets"); /* Erzeuge die Socketadresse des Servers. * Sie besteht aus Typ, IP-Adresse und Portnummer. */ memset( &server, 0, sizeof (server)); if ((addr = inet_addr( argv[1])) != INADDR_NONE) { /* argv[1] ist eine numerische IP-Adresse. */ memcpy( (char *)&server.sin_addr, &addr, sizeof(addr)); } else { /* Für den Fall der Fälle: Wandle den * Servernamen bspw. "localhost" in eine IP-Adresse um. */ host_info = gethostbyname(argv[1]); if (NULL == host_info) error_exit("Unbekannter Server"); /* Server-IP-Adresse */ memcpy( (char *)&server.sin_addr, host_info->h_addr, host_info->h_length ); } /* IPv4-Verbindung */ server.sin_family = AF_INET; /* Portnummer */ server.sin_port = htons( PORT ); /* Baue die Verbindung zum Server auf. */ if(connect(sock,(struct sockaddr*)&server,sizeof(server)) <0) error_exit("Kann keine Verbindung zum " "Server herstellen"); /* Zweites Argument wird als "echo" beim Server verwendet. */ echo_string = argv[2]; /* Länge der Eingabe */ echo_len = strlen(echo_string); /* den String inkl. Nullterminator an den Server senden */ if (send(sock, echo_string, echo_len, 0) != echo_len) error_exit("send() hat eine andere Anzahl" " von Bytes versendet als erwartet !!!!"); /* Schließe Verbindung und Socket. */ #ifdef _WIN32 closesocket(sock); /* Cleanup Winsock */ WSACleanup(); #else close(sock); #endif return EXIT_SUCCESS; }
25.6.2 Der Server
Hier ist der Quellcode zur Server-Anwendung:
/* server.c */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <errno.h> #include <time.h> #ifdef _WIN32 /* Headerfiles für Windows */ #include <winsock.h> #include <io.h> #else /* Headerfiles für UNIX/Linux */ #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <arpa/inet.h> #include <unistd.h> #endif /* Portnummer */ #define PORT 1234 /* Puffer für eingehende Nachrichten */ #define RCVBUFSIZE 1024 #ifdef _WIN32 static void echo(SOCKET); #else static void echo( int ); #endif static void error_exit(char *errorMessage); /* Die Funktion gibt Daten vom Client auf stdout aus, * die dieser mit der Kommandozeile übergibt. */ #ifdef _WIN32 static void echo(SOCKET client_socket) #else static void echo(int client_socket) #endif { char echo_buffer[RCVBUFSIZE]; int recv_size; time_t zeit; if((recv_size = recv(client_socket, echo_buffer, RCVBUFSIZE,0)) < 0) error_exit("Fehler bei recv()"); echo_buffer[recv_size] = '\0'; time(&zeit); printf("Nachrichten vom Client : %s \t%s", echo_buffer, ctime(&zeit)); } /* Die Funktion gibt den aufgetretenen Fehler aus und * beendet die Anwendung. */ static void error_exit(char *error_message) { #ifdef _WIN32 fprintf(stderr,"%s: %d\n", error_message, WSAGetLastError()); #else fprintf(stderr, "%s: %s\n", error_message, strerror(errno)); #endif exit(EXIT_FAILURE); } int main( int argc, char *argv[]) { struct sockaddr_in server, client; #ifdef _WIN32 SOCKET sock, fd; #else int sock, fd; #endif unsigned int len; #ifdef _WIN32 /* Initialisiere TCP für Windows ("winsock"). */ WORD wVersionRequested; WSADATA wsaData; wVersionRequested = MAKEWORD (1, 1); if (WSAStartup (wVersionRequested, &wsaData) != 0) error_exit( "Fehler beim Initialisieren von Winsock"); else printf("Winsock initialisiert\n"); #endif /* Erzeuge das Socket. */ sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock < 0) error_exit("Fehler beim Anlegen eines Sockets"); /* Erzeuge die Socketadresse des Servers. */ memset( &server, 0, sizeof (server)); /* IPv4-Verbindung */ server.sin_family = AF_INET; /* INADDR_ANY: jede IP-Adresse annehmen */ server.sin_addr.s_addr = htonl(INADDR_ANY); /* Portnummer */ server.sin_port = htons(PORT); /* Erzeuge die Bindung an die Serveradresse * (genauer: an einen bestimmten Port). */ if(bind(sock,(struct sockaddr*)&server, sizeof( server)) < 0) error_exit("Kann das Socket nicht \"binden\""); /* Teile dem Socket mit, dass Verbindungswünsche * von Clients entgegengenommen werden. */ if(listen(sock, 5) == -1 ) error_exit("Fehler bei listen"); printf("Server bereit - wartet auf Anfragen ...\n"); /* Bearbeite die Verbindungswünsche von Clients * in einer Endlosschleife. * Der Aufruf von accept() blockiert so lange, * bis ein Client Verbindung aufnimmt. */ for (;;) { len = sizeof(client); fd = accept(sock, (struct sockaddr*)&client, &len); if (fd < 0) error_exit("Fehler bei accept"); printf("Bearbeite den Client mit der Adresse: %s\n", inet_ntoa(client.sin_addr)); /* Daten vom Client auf dem Bildschirm ausgeben */ echo( fd ); /* Schließe die Verbindung. */ #ifdef _WIN32 closesocket(fd); #else close(fd); #endif } return EXIT_SUCCESS; }
Generell werden die meisten Leser dieses Beispiel am lokalen Rechner testen. Das heißt, sowohl die Server- als auch die Clientanwendung befindet sich dabei auf einem Rechner. Wie schon erwähnt wurde, ist dies dank des Loopback-Interfaces kein Problem. Die lokale IP-Adresse, die Sie auf Ihrem Rechner verwenden können, lautet hierbei 127.0.0.1 bzw. »localhost«. Wenn Sie allerdings die Möglichkeit haben, die Serveranwendung auf einem anderen System zu kompilieren und zu testen, so sollten Sie sich nicht scheuen, dies zu tun.
Abbildung 25.4 zeigt das Programm bei der Ausführung.
Abbildung 25.4 Ein Client schickt einen String an den Server.
Abbildung 25.5 Der Server bearbeitet die Anfragen des bzw. der Clients.
Natürlich stellt dieses Beispiel die primitivste Form der Netzwerkprogrammierung dar. Dennoch werden eigentlich alle wichtigen Funktionen der Netzwerkprogrammierung dazu verwendet.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.