[C/C++] C Server + Java Client [Einführung - Socketprogrammierung]

Dieses Thema im Forum "Programmier Tutorials" wurde erstellt von schiene, 8. Dezember 2006 .

Status des Themas:
Es sind keine weiteren Antworten möglich.
  1. 8. Dezember 2006
    C Server + Java Client [Einführung - Socketprogrammierung]

    [ Eine kleine Einführung in die Socketprogrammierung ]
    - man sollte schon einigermaßen in C und Java geübt sein, aber auch für Anfänger hoffentlich verständlich, aber sicherlich nicht leicht durchschaubar -

    Was beinhaltet dieses kleine Tutorial
    • Server in C (Linux) - Der Daten sendet
    • Client in Java - Der Daten anfordert

    und das ganze mit Sockets mit TCP Verbindung (mit IPv4)

    Bemerkung:
    Die Socketprogrammierung ist eine sehr komplexe Angelegenheit und ich kann in diesem Tut sicherlich nicht auf jede Einzelheit eingehen, dennoch möchte ich einfach mal mein wissen weitergeben. Als ich mit programmieren angefangen habe hat mich dieses Thema am meisten gelockt weil es einfach schön ist, wenn ein Server und ein Client gut zusammenarbeiten
    Aber warum ein Server in C und ein Client in Java? Nun ja man kann sicherlich auch den Client C schreiben, dennoch denke ich mir das Folgendermaßen: C ist sehr viel schneller als Java und eignet sich deshalb gut als Server für einen kleinen Linuxserver. Der Javaclient ist dagegen langsamer gut, aber er ist plattformunabhängig und damit sowohl in Linux einsetzbar als auch in Windows, als auch bei anderen Betriebssystemen die Java untersützen. C ist im Grunde auch plattformunabhängig allerdings haben Linuxsysteme andere Header-Dateien für Sockets als Windows, das mit Winsocks arbeitet.

    [ Server ]
    Zunäachst brauchen wir einen Server der eine Verbindung annimmt, dem Client die Daten zusendet und dann die VErbindung zwischen ihm und dem client schließ und auf die nächsten Clients wartet.
    Im Model sieht dies so aus:

    Zunächst einfach mal den Code auf sich wirken lassen :tongue:, ich hoffe die Erklärungen sind eindeutig
    Code:
    //File server.c
    #include <sys/types.h> // für Datentypen
    #include <sys/socket.h> // für Socket-Funktionen
    #include <stdio.h> // für allgemeine Ein/Ausgabe
    #include <netinet/in.h> //für IP-Adresse
    #include <arpa/inet.h> // für die Socket-Datenstruckturen
    #include <string.h> // für Stringfunktionen
    
    main(){
     int server_socket, neuer_socket; // um Server - / Clientinformationen zu speichern
     int anzahl, laenge; // [anzahl] die Anzhal der empfangenen Zeichen,[laenge] die Laenge/Größe der Server- / Clientinformationen, diese brauchen wir später als Parameter
     char empfangen[1000]; // Buffer für empfangene Zeichen
     char senden[1000]; //Buffer für gesendete Zeichen
    
     int verbindung_nummer = 1; // ein Counter für die Anzahl der Verbindungen
    
     struct sockaddr_in serverinfo, clientinfo; //structuren für Server / Client - in diesen werden Port, Ip-Adresse Art der Verbindung gespeichert
     unsigned short int portnummer = 7777; //unser Port an dem der Server horchen soll
     char ip_adresse[] = "127.0.0.1"; // Die IP-Adresse an die der Server "gebunden" wird
    
     printf("\n Server socket()...");
    
     /*
     Wir erstellen einen neuen Socket und zwar mit den Parametern:
     AF_INET, d.h. wir benutzen die Transportprotokolle des Internets, also IP und TCP oder UDP
     SOCK_STREAM, d.h. wir benutzen das TCP Protokoll
     0 hier kan man nochmal einen speziellen Typ eines Transportprotokolls auszuwählen, was in unserem Beispiel aber nicht nötig ist, denn wir haben mit den vorherigen Parametern schon klar gemacht, was für einen Socket wir haben wollen
     */
     server_socket = socket(AF_INET, SOCK_STREAM, 0);
    
     // die Serverstruktur mit Daten füllen
     serverinfo.sin_family = AF_INET; //Family setzen
     serverinfo.sin_addr.s_addr = inet_addr(ip_adresse); //ip-adresse setzen, dabei inet_addr verwenden, um die Zeichenkette in eine 32-Bit-Zahl umzuwandeln, man kann hier auch die Konstante INADDR_ANY setzen, um dem Socket zu sagen, dass er an allen IP Adressen horchen soll
     serverinfo.sin_port = htons(portnummer); // Port setzen, dabei htons() verwenden, um auch den Port in BIts umzuwandeln
     laenge = sizeof(serverinfo); // wir müssen die länge der Struktur speichern, dabei wir diese noch als Parameter benötigen
     /*
     wer sich mehr für Ip-Adressen und dessen Umwandlung in Bits ineteressiert und warum dies nötig ist. schaut mal Wikipedia vorbei:
     http://de.wikipedia.org/wiki/IPv4
     */
    
     printf("\n Server: bind()...");
    
     /*
     Den Server an seinen Port und IP binden.
     wir brauchen einmal den erstellten Socket also server_socket
     und wir brauchen die Struktur allerdings, benötigt bind möglicherweise nicht sockaddr_in sondern nur sockaddr , deswegen müssen wir die serverinfo mittel Zeiger und Refenz in sockaddr umwandeln
     der Parameter ist die laenge der Struktur
     */
     bind(server_socket, (struct sockaddr *)&serverinfo, laenge);
    
     printf("\n Server: listen()...\n Server mit IP %s an Port %d wartet...", ip_adresse, portnummer);
    
     /*
     fange an zu horchen
     die 3 gibt hierbei an, wie lang die Warteschlange für den Socket ist, d.h. es können 3 Clients gleichzeitig einen Verbindungswunsch schicken, dadurch wird der Server selber etwas entlastet. Natürlich kann man den Wert auch höher stellen, nur das jeweilige Betriebsystem nimmt eine zu große Warteschlange nicht als akzeptabel an und verringert sie bis zur Obergrenze
     */
     listen(server_socket, 3);
    
     // Endlosschleife um Clients zu akzeptieren
     while (1){
     printf("\n Verbindung %d wartet...", verbindung_nummer);
     
     /*
     neue Clients akzeptieren und dabei die Clientstruktur setzen, deswegen ist clientinfo als Referenz und die länge auch
     */
     neuer_socket = accept(server_socket, (struct sockaddr *)&clientinfo, &laenge);
     
     printf("\n Verbindung mit %s \n", inet_ntoa(clientinfo.sin_addr));
     
     /* Daten die man empfängt in die Variable empfangen schreiben, man muss hier als letzten Parameter einen Puffer angeben, wie viele Daten empfangen werden können, ist der Puffer zu klein, können möglicherweise nicht alle Daten erfasst werden, man könnte dann eine weitere Schleife einbauen
     */
     anzahl = read(neuer_socket, &empfangen, sizeof(empfangen));
     // Man muss am Ende der Daten eine 0 setzen, damit erkannt wird wo die Nachricht endet, sonst hat man den "Datenmüll" mit dem man nichts anfangen kann noch in seinen Empfangen Daten, um zu sehen wie dies aussieht, könnt ihr ja mal diesen Schritt weglassen
     empfangen[anzahl] = 0;
     
     printf("\n\n Empfangen : \n\n%s", empfangen);
     
     /*
     Was wurde empfangen?
     */
     if (strstr(empfangen, "Hi") != NULL){
     strcpy(senden, "du");
     // Wenn der Client "Hi" sagt, dann kopieren wir "du" in die Variable senden, und senden diese Daten, dabei muss auch die laenge von senden als letzter Parameter übergeben werden. Das gleiche Spiel bei den anderen Überprüfungen, und wenn kein Befehl gepasst hat, ist der Befehl unbekannt
     write(neuer_socket, senden, strlen(senden));
     }else if(strstr(empfangen, "Wie geht's?") != NULL){
     strcpy(senden, "gut");
     write(neuer_socket, senden, strlen(senden));
     }else{
     strcpy(senden, "unbekannter befehl");
     write(neuer_socket, senden, strlen(senden));
     }
     
     // wir schließen den Socket, um neue Verbindungen aufnehmen zu können
     close(neuer_socket);
     printf("\n");
     verbindung_nummer++;
     }
     
     // Wir schließen den Server Socket, allerdings wird diese Stellen niemals erreicht, da wir eine Endlosschleife zuvor eingebaut haben
     printf("Beenden\n\n");
     close(server_socket);
     
     return(0);
    }
    
    das ganze dann mit "gcc -o server server.c" kompilieren und ausführen.

    Dieser Server ist sehr Simpel gehalten. Man kann ihn verbessern, besonders mit Threads. Dann hat man einen Thread der auf Clients wartet und wenn ein Client sich verbunden hat, öffnet er einen neuen Thread, um Operationen, wie Daten zu senden, durchzuführen. Das fürht natürlich zu enormer Geschwindigkeitsverbesserung. PThreads oder weiter Möglichkeiten sind hier die Stichworte.

    So nun zu dem etwas simpleren Java Client und ihr werdet sehen unter Java gestaltet sich das ganze wirklich leichter und lässt sich schneller verstehen

    Code:
    //File Client.java
    import java.net.*; // für Sockets
    import java.io.*; // für die Exceptions
    
    class Client
    {
     public static void main( String[] args ){
     Socket client = null; // wir brauchen einen Socket der als Client fungieren soll
     try{
     client = new Socket( "localhost", 7777 ); // wir erstellen einen neuen Socket und verbinden uns zu einer IP Adresse an einem bestimmten Port
     OutputStream out = client.getOutputStream(); // wir bersorgen uns sozusagen von diesem Client den Output, um Daten senden zu können
     BufferedReader in = new BufferedReader(new InputStreamReader(
     client.getInputStream())); // man könnte jetzt denken, "Junge warum nehmen wir hier nicht auch einfach den InputStream?!?". Ist eine Möglichkeit, nur wir wollen mit dem C - Server kommunizieren, und dazu benötigt man am besten einen BufferedReader (meines Wissens nach). Für only Java Programme kann man auch den direkten InputStream verwenden also: InputStream in = client.getInputStream();
     String get = "Hi"; // der Text den wir senden wollen
     out.write(get.getBytes()); // Wir müssen diesen Text in Bytes schicken, denn als argument werden Bytes von write verlangt
     String result; // dort wird der empfangene Text gespeichert
     while ((result = in.readLine()) != null) {
     System.out.println( result );
     /*wir lesen den empfangenen Text schrittwise aus, wenn wir dies nicht schrittweise machen würden, sondern das ganze einfach ohne while Schleife würden wir immer nur die erste Zeile erhalten. D.h. wenn der Server Beispielsweise sendet:
     "erste Zeile
     zweite Zeile"
     empfangen wir ohne diese Schleife nur "erste Zeile"*/
     }
     // Fehlerbehandlung
     }catch ( UnknownHostException e ) {
     e.printStackTrace(); // unbekannter host? Wenn dann bitte die Fehlermeldung ausgeben
     }catch ( IOException e ) {
     e.printStackTrace(); // andere Exceptions?
     }finally{ //wenn alles ok ist,dann..
     if ( client != null ) //können wir den Client schließen
     try { client.close(); } catch ( IOException e ) { e.printStackTrace(); } // Wenn ein Fehler auftritt beim schließen des Socket, dann bitte die Fehlermeldung ausgeben
     }
     }
    }
    
    kurz und knackig Ich hoffe dieses Tutorial hat euch ein bisschen in die Socketprogrammierung eingeführt und Lust erweckt auf mehr . Also ich finde es ist ein sehr schönes Thema, besonders man hat soo viele Anwendungsmöglichkeiten damit. Man kann FTP-Clients schreiben, simple Browser, aber auch simple Portscanner und das ist manchmal ganz nützlich wenn ihr mal nicht DAS Programm findet um einen Internetservice zu testen, baut euch euer eigenes Programm

    greetz schiene

    Informationsquellen: "Grundkurs - Socketprogrammierung mit C unter Linux" und "Java ist auch eine Insel"
     
  2. Video Script

    Videos zum Themenbereich

    * gefundene Videos auf YouTube, anhand der Überschrift.