Client / Server-Implementierung in C (Senden von Daten / Dateien)

107094

Ich habe diesen Code geschrieben, um beliebige Binärdateien vom Server an den Client zu senden (in unserem Beispiel sende ich sample_file.txt). Der Client sollte die Datei lokal neu erstellen. Code funktioniert gut (ich habe mit ein oder zwei Dateien getestet). Die Daten werden in 256-Byte-Blöcken gesendet.

Ich würde mich über Bemerkungen und Kritik freuen, jedoch nicht so sehr aus programmtechnischer Sicht, eher wenn es einige schwerwiegende Fehler im Netzwerkteil / in der Logik oder in der Datei-E / A gibt.

Klient:

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>

int main(void)
{
    int sockfd = 0;
    int bytesReceived = 0;
    char recvBuff[256];
    memset(recvBuff, '0', sizeof(recvBuff));
    struct sockaddr_in serv_addr;

    /* Create a socket first */
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0))< 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    }

    /* Initialize sockaddr_in data structure */
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(5000); // port
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    /* Attempt a connection */
    if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))<0)
    {
        printf("\n Error : Connect Failed \n");
        return 1;
    }

    /* Create file where data will be stored */
    FILE *fp;
    fp = fopen("sample_file.txt", "ab"); 
    if(NULL == fp)
    {
        printf("Error opening file");
        return 1;
    }

    /* Receive data in chunks of 256 bytes */
    while((bytesReceived = read(sockfd, recvBuff, 256)) > 0)
    {
        printf("Bytes received %d\n",bytesReceived);    
        // recvBuff[n] = 0;
        fwrite(recvBuff, 1,bytesReceived,fp);
        // printf("%s \n", recvBuff);
    }

    if(bytesReceived < 0)
    {
        printf("\n Read Error \n");
    }


    return 0;
}

Server:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>

int main(void)
{
    int listenfd = 0;
    int connfd = 0;
    struct sockaddr_in serv_addr;
    char sendBuff[1025];
    int numrv;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    printf("Socket retrieve success\n");

    memset(&serv_addr, '0', sizeof(serv_addr));
    memset(sendBuff, '0', sizeof(sendBuff));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(5000);

    bind(listenfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));

    if(listen(listenfd, 10) == -1)
    {
        printf("Failed to listen\n");
        return -1;
    }


    while(1)
    {
        connfd = accept(listenfd, (struct sockaddr*)NULL ,NULL);

        /* Open the file that we wish to transfer */
        FILE *fp = fopen("sample_file.txt","rb");
        if(fp==NULL)
        {
            printf("File opern error");
            return 1;   
        }   

        /* Read data from file and send it */
        while(1)
        {
            /* First read file in chunks of 256 bytes */
            unsigned char buff[256]={0};
            int nread = fread(buff,1,256,fp);
            printf("Bytes read %d \n", nread);        

            /* If read was success, send data. */
            if(nread > 0)
            {
                printf("Sending \n");
                write(connfd, buff, nread);
            }

            /*
             * There is something tricky going on with read .. 
             * Either there was error, or we reached end of file.
             */
            if (nread < 256)
            {
                if (feof(fp))
                    printf("End of file\n");
                if (ferror(fp))
                    printf("Error reading\n");
                break;
            }


        }

        close(connfd);
        sleep(1);
    }


    return 0;
}
Antworten
15

3 Antworten auf die Frage

8
Martin York

IMMER die Rückgabewerte der C-Funktionen prüfen und geeignete Maßnahmen ergreifen.

Beispiel:

::read()  returns the number of characters read.
          Just because you write in chunks of 256 does not mean they
          will arrive in chunks of.

::write() Same thing goes here.
          You may try and write in chunks of 256 does not mean
          that all 256 will be written.

Hinweis. Wenn sie -1 (Fehler) zurückgeben, ist der tatsächliche Fehler in der Variablen errnound nicht alle Fehler sind nicht wiederherstellbar (siehe [EINTR]).

Wie würde ich schreiben verwenden?

std::size_t    bytesWritten = 0;
std::size_t    bytesToWrite = <Some Size>;

while (bytesWritten != bytesToWrite)
{
    std::size_t    writtenThisTime;

    do
    {
         writtenThisTime = ::write(fd, buf + bytesWritten, (bytesToWrite - bytesWritten));
    }
    while((writtenThisTime == -1) && (errno == EINTR));

    if (writtenThisTime == -1)
    {
        /* Real error. Do something appropriate. */
        return;
    }
    bytesWritten += writtenThisTime;
}

Wie würde ich lesen:

std::size_t    bytesRead = 0;
std::size_t    bytesToRead = <Some Size>;

while (bytesToRead != bytesRead)
{
    std::size_t    readThisTime;

    do
    {
         readThisTime = ::read(fd, buf + bytesRead, (bytesToRead - bytesRead));
    }
    while((readThisTime == -1) && (errno == EINTR));

    if (readThisTime == -1)
    {
        /* Real error. Do something appropriate. */
        return;
    }
    bytesRead += readThisTime;
}

andere ungeprüfte Anrufe:

fwrite(recvBuff, 1,bytesReceived,fp);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));
connfd = accept(listenfd, (struct sockaddr*)NULL ,NULL);
int nread = fread(buff,1,256,fp);             // You may not get all 256 items.
Vielen Dank. Ich dachte überall, wo ich read verwende, ich überprüfe die Rückgabewerte, ist es nicht der Fall? Für das Schreiben verwende ich es einmal im Server und einmal im Client - an beiden Stellen werde ich vergleichen, ob die Anzahl der geschriebenen Bytes gleich der Anzahl der Bytes ist, die ich zum Schreiben angefordert habe, und wenn nicht - Programm oder smth abbrechen, das ist Ihre Vorschlag richtig? vor 6 Jahren 0
@Wenn die Anzahl der geschriebenen Bytes nicht der Anzahl der angeforderten Bytes entspricht, versuchen Sie es erneut (beginnend mit dem ersten nicht geschriebenen Byte). Ja, Sie prüfen zwar die Lesezahl, kompensieren jedoch keine Fehler, die keine Fehler sind. aktualisiert mit, wie ich lesen / schreiben würde. Martin York vor 6 Jahren 0
Ich werde mehr auf Ihre Vorschläge eingehen. Aber warum ist Ihr Schreiben besser als eines, das ich hatte, oder einfach nur einen Fehlercode schreiben und überprüfen? Grundsätzlich könnte man Ihren Vorschlag auch in eine WRITE-Funktion (Buffer, Size) einfassen und sie anstelle des ursprünglichen Schreibbefehls auch verwenden, oder? vor 6 Jahren 0
Weil Schreiben nicht garantiert, alle angeforderten Bytes zu schreiben. Die FD ist möglicherweise beschäftigt. Das Überprüfen des Fehlercodes reicht nicht aus, Sie müssen überprüfen, wie viel geschrieben wurde. Ja, Sie können das in eine Funktion einpacken (und ich würde das ermutigen). Sehen Sie sich auch die Liste der Funktionen an, für die Sie den Rückkehrcode nicht korrekt überprüfen. Martin York vor 6 Jahren 0
5
bkdc

Wenn ich ein Wort über Networking auswählen müsste, wäre das "unzuverlässig". Netzwerke sind auch in Laborszenarien nicht kugelsicher; Dinge können versagen: ganze Maschinen, Netzwerkadapter, Kabel, Router, Switches oder die Anwendung am "anderen" Ende. Ihr Code trägt wenig dazu bei, dass Fehler ordnungsgemäß behandelt werden, und hängt nur von den (eingeschränkten) Garantien ab, die Ihnen TCP bietet.

Was passiert, wenn der Server mitten in einer Übertragung abstürzt? Woher wissen Sie, dass der gesamte Inhalt der Datei übertragen wurde?

Ich würde damit beginnen, ein "Mini-Protokoll" für die App zu entwerfen: Vielleicht sollte der Client nach einer bestimmten Datei fragen können. Wie würden Sie die Anfrage (derzeit nicht vorhanden) und die Antwort ändern? Ich würde auch einen Header hinzufügen, um den Kunden darüber zu informieren, wie viele Daten ich verschicken werde, oder vielleicht eine Art Datei- CRC .

Bezüglich Ihrer Wahl der Puffergröße. TCP-Pakete sind normalerweise viel größer als 256 Bytes. In Ihrem Fall ist dies zwar kein großes Problem, Sie sollten jedoch eine günstigere MTU wählen .

sleep- manche nennen es böse. Warum hast du es benutzt?

Warum sollte der Server wie viele Bytes senden, die gesendet werden sollen, damit der Client überprüfen kann, ob die Datenübertragung erfolgreich war? Sie empfehlen daher, CRC zu senden, damit der Client sicherstellen kann, dass die Datei erfolgreich empfangen wurde. Ist es das? Über `sleep` - ein kleiner Teil dieses Codes, wie die grundlegende Client / Server-Verbindung, die ich im Internet gefunden habe. Es scheint also ein Relikt davon zu sein ... vor 6 Jahren 0
@dmcr_code, wenn Sie die Dateigröße im Voraus kennen, kann für mehrere Dinge nützlich sein: 1. Der Client weiß, wann er aufhören muss / wie viel zu lesen ist, mit dem zusätzlichen Vorteil, dass er (zuverlässiger) erkennen kann, ob etwas schief gelaufen ist (möglicherweise bereinigen unvollständiger Dateien). 2. Sie können den Speicherplatz für die Datei (en) "vorbelegen", anstatt Ihre Festplatte mit 256 Byte langen Schreibvorgängen (nicht einmal einem vollständigen 512-Byte-Festplattensektor im alten Stil) in den Papierkorb zu werfen - normalerweise Torrent-Clients Vermeiden Sie es, durch die HDD-Schreibgeschwindigkeit eingeschränkt zu werden. bkdc vor 6 Jahren 0
@dmcr_code CRC ersetzt die Dateigröße nicht. In gewisser Weise könnte sie jedoch so verwendet werden. Der Hauptvorteil, den CRCs bieten, besteht darin, dass der Client überprüfen kann, ob die empfangenen Daten nicht beschädigt wurden oder [manipuliert] (http://en.wikipedia.org/wiki/Man-in-the-middle_attack). bkdc vor 6 Jahren 0
@dmcr_code Sie sollten die Dateien "schließen", sobald Sie mit ihnen fertig sind bkdc vor 6 Jahren 0
Ja, fclose habe ich hinzugefügt. Ich stimme zu, dass CRC die Zuverlässigkeit erhöhen kann, weil ich überprüfen kann, ob der Empfang von Dateien erfolgreich war. Beim Senden der Dateigröße noch nicht überzeugt, aber vielen Dank für Ihr Feedback. vor 6 Jahren 0
4
Josay

Ich bin kein Experte für Netzwerke, daher werde ich den Teil kommentieren, der Sie nicht besonders interessiert. Darüber hinaus ist Ihr Code ziemlich sauber, gut dokumentiert und schien richtig zu funktionieren, als ich es lokal ausprobierte. Es gibt also nicht zu viel zu sagen.

Sie sollten versuchen, magische Zahlen zu vermeiden, um Dinge wartbar zu halten, und andere hartcodierte Daten in der Mitte des Codes.

Sie können Server und Client auf unterschiedliche Weise in dieselbe Datei einfügen.

Sie haben eine nicht verwendete Variable. Ein einfacher Weg, um es zu bemerken, besteht darin, zu versuchen, die Deklaration so spät wie möglich, so klein wie möglich und so nah wie möglich an der ersten Verwendung zu platzieren. Außerdem können Compiler-Warnungen Ihnen helfen.

Ich bin nicht sicher, ob Sie Ressourcen (zum Beispiel Dateien) ordnungsgemäß bereinigen. Um das richtig zu machen, muss ich viel umschreiben, also überlasse ich es Ihnen.

Nach ein paar Änderungen hier und da ist hier der Code, den ich auf meiner Seite habe:

// For both
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// For server
#include <netdb.h>

// For client


#define PORT 5000
#define BUF_SIZE 256

int client(const char* filename)
{
    /* Create file where data will be stored */
    FILE *fp = fopen(filename, "ab");
    if(NULL == fp)
    {
        printf("Error opening file");
        return 1;
    }

    /* Create a socket first */
    int sockfd = 0;
    if((sockfd = socket(AF_INET, SOCK_STREAM, 0))< 0)
    {
        printf("\n Error : Could not create socket \n");
        return 1;
    }

    /* Initialize sockaddr_in data structure */
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT); // port
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    /* Attempt a connection */
    if(connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr))<0)
    {
        printf("\n Error : Connect Failed \n");
        return 1;
    }

    /* Receive data in chunks of BUF_SIZE bytes */
    int bytesReceived = 0;
    char buff[BUF_SIZE];
    memset(buff, '0', sizeof(buff));
    while((bytesReceived = read(sockfd, buff, BUF_SIZE)) > 0)
    {
        printf("Bytes received %d\n",bytesReceived);
        fwrite(buff, 1,bytesReceived,fp);
    }

    if(bytesReceived < 0)
    {
        printf("\n Read Error \n");
    }

    return 0;
}


int server(const char * filename)
{
    int listenfd = socket(AF_INET, SOCK_STREAM, 0);

    printf("Socket retrieve success\n");

    struct sockaddr_in serv_addr;
    memset(&serv_addr, '0', sizeof(serv_addr));
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_addr.sin_port = htons(PORT);

    bind(listenfd, (struct sockaddr*)&serv_addr,sizeof(serv_addr));

    if(listen(listenfd, 10) == -1)
    {
        printf("Failed to listen\n");
        return -1;
    }

    for (;;)
    {
        int connfd = accept(listenfd, (struct sockaddr*)NULL ,NULL);

        /* Open the file that we wish to transfer */
        FILE *fp = fopen(filename,"rb");
        if(fp==NULL)
        {
            printf("File opern error");
            return 1;
        }

        /* Read data from file and send it */
        for (;;)
        {
            /* First read file in chunks of BUF_SIZE bytes */
            unsigned char buff[BUF_SIZE]={0};
            int nread = fread(buff,1,BUF_SIZE,fp);
            printf("Bytes read %d \n", nread);

            /* If read was success, send data. */
            if(nread > 0)
            {
                printf("Sending \n");
                write(connfd, buff, nread);
            }

            /*
             * There is something tricky going on with read ..
             * Either there was error, or we reached end of file.
             */
            if (nread < BUF_SIZE)
            {
                if (feof(fp))
                    printf("End of file\n");
                if (ferror(fp))
                    printf("Error reading\n");
                break;
            }
        }
        close(connfd);
        sleep(1);
    }

    return 0;
}

int main(int argc, char** argv)
{
    if (argc == 3)
    {
        const char* mode = argv[1];
        const char* filename = argv[2];
        if (strcmp(mode, "client") == 0)
            return client(filename);
        else if (strcmp(mode, "server") == 0)
            return server(filename);
        else
            printf("Invalid mode %s - should be 'client' or 'server'\n",mode);
    }
    else
    {
        printf("Invalid number of argument, usage is %s [MODE] [FILENAME]\n",argv[0]);
    }
    return 1; // Something went wrong
}
Sie können Flags auch mit gcc verwenden, um nach nicht verwendeten Variablen zu suchen. Ich benutze immer "-Wall -Werror -Wextra". Eine dieser Anzeigen zeigt eine Warnung an, wenn nicht verwendete Variablen vorhanden sind, die nicht genau wissen, welche gerade verwendet wird dietbacon vor 5 Jahren 0