[C]Z socketa do pliku - czyli dziwny błąd

0

Witam. Mam dość nietypowy problem. Piszę sobie klienta i serwera, które mają przesyłać sobie plik. Napisałem najpierw kod, w którym serwer wysyłał zawartość pliku po 1 kB i aby sprawdzić czy klient odbiera te dane kazałem w oknie klienta je wyświetlać. Wszystko bosko wręcz śmiga! No to teraz wziąłem zamiast printf fwrite (po wczesniejszym otwarciu pliku oczywiście). No i co się dzieje? Jest nieźle, poza tym, że ostatni wysłany bufor się nie chce zapisać w pliku! Niezależnie od jego wielkości - za każdym razem pozostaje on niezapisany w pliku... Problem pewnie banalny i oczywisty, ale jakoś nie mogę sobie poradzić :/ Oto kod:

fragment serwa odpowiedzialny za wysyłanie danych:

while(statusRead!=0){
            memset(bufor, 0, MAX_BUF);
            statusRead=fread(bufor, 1, MAX_BUF, file);
            if(statusRead==-1){
                perror("Blad odczytu pliku\n");
                exit(1);
            }

            printf("\n\n\nWysylam: %s", bufor);

            statusWrite=write(socket, bufor, statusRead);
            if(statusWrite==-1){
                perror("Blad wysylania pliku\n");
                exit(1);
            }
        }
        fclose(file);
</CODE>

Przy tym kodzie po każdej wysłanej paczce mam informację co zostalo wysłane. Wynika z tego, że cały plik się wysyła.

Teraz klient - odpowiedzialny za odbiór i zapis
        file=fopen("plik.txt", "w");

        while(statusRead!=0){
            memset(bufor, 0, MAX_BUF);
            statusRead=read(socket, bufor, MAX_BUF);
            if(statusRead==-1){
                perror("Blad polaczenia\n");
                exit(1);
            }
            printf("%s\n",bufor);
            statusWrite=fwrite(bufor, 1, statusRead, file);
            if(statusWrite==-1){
                perror("Blad zapisu w pliku\n");
                exit(1);
            }
        }
        close(file);
</code>

Przy tym kodzie również na outpucie mam cały plik... A gdy otwieram plik - brak tekstu z ostatniego bufora. O co chodzi? Proszę o pomoc, bo mi pomysły się skonczyły...

0

Jestem odrobinę bliżej rozwiązania. Wywaliłem z kodu wszystko, a zostawiłem samą komunikację z serwerem w celu skopiowania pliku. Zostało mi:

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

#define MAX_BUF 1024 //maksymalna pojemnosc bufora
#define MAX_CODE 11 //maksymalna dlugosc kodu bledow
#define MAX_NAME 24 //maksymalna dlugosc nazwy pliku (np. app.exe)
#define MAX_ADDR 231 //maksymalna dlugosc adresu pliku
#define MAX_CMD 512 //maksymalna dlugosc komendy

struct plik {
    char nazwa[MAX_NAME];
    char adres[MAX_ADDR];
};

void wskazSerwer(struct sockaddr_in *);
void wskazPlik(struct plik *);
int wyslijZlecenie(char, int, struct sockaddr_in *, struct plik *);
int szukaj(char *, char);

/*
 *
 */
int main(int argc, char** argv) {
    struct plik file;
    struct sockaddr_in addr;
    char bufor[MAX_BUF], zadanie = 'k';
    int sockd, statusRead, statusWrite, i;
    void *ptr;
    FILE *fp;

    //Faza przygotowan
    addr.sin_family = AF_INET;
    inet_aton("127.0.0.1", &addr.sin_addr);
    addr.sin_port = htons(6666);

    file.adres[0] = '\0';
    file.nazwa[0] = '\0';

    sockd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockd == -1) {
        perror("Socket nie zostal utworzony\n");
        exit(1);
    }

    statusRead = connect(sockd, (struct sockaddr *) & addr, sizeof (addr));
    if (statusRead == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

    memset(bufor, 0, MAX_BUF);
    bufor[0] = 'k'; //kod zlecenia

    ptr = &file;

    //zbuforowanie i wyslanie struktury z danymi pliku, ktorego zlecenie dotyczy
    for (i = 0; i<sizeof (struct plik); i++) {
        bufor[i + 1] = *((char *) (ptr + i));
    }

    statusWrite = write(sockd, bufor, MAX_BUF);
    if (statusWrite == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

    //zamkniecie gniazda dla pisania
    shutdown(sockd, SHUT_WR);

    printf("Wyslano zlecenie do serwera\n");

    //opracowanie odpowiedzi serwera
    memset(bufor, 0, MAX_BUF);
    statusRead = read(sockd, bufor, MAX_CODE);
    if (statusRead == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

    if (zadanie == 'k') {
        printf("Rozpoczynam kopiowanie pliku...\n");

        statusRead = 1;
        /* DEBUG czy musi byc osobny katalog??
        strcpy(cmd, "mkdir ");
        strcat(cmd, plik->adres);
        strcat(cmd, " >/dev/null 2>&1");
        //jesli katalog nie istnieje -utworz go
        system(cmd);*/

        fp = fopen("wtf.txt", "w");

        while (statusRead != 0) {
            memset(bufor, 0, MAX_BUF);
            statusRead = read(sockd, bufor, MAX_BUF);
            if (statusRead == -1) {
                perror("Blad polaczenia\n");
                exit(1);
            }

            statusWrite = fwrite(bufor, 1, statusRead, fp);
            if (statusWrite == -1) {
                perror("Blad zapisu w pliku\n");
                exit(1);
            }
            printf("%s", bufor);
        }
        close(fp);
        printf("Plik skopiowano!\n");
    }
    close(sockd);

    return 1;
}

Po skompilowaniu - działa. No to zacząłem z pełnej aplikacji usuwać zbedne partie kodu, stopniowo, sprawdzając, który kod po wyrzuceniu powoduje że aplikacja działa. Doszedlem do magicznego momentu, kiedy moj plik wygląda tak:

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

#define MAX_BUF 1024 //maksymalna pojemnosc bufora
#define MAX_CODE 11 //maksymalna dlugosc kodu bledow
#define MAX_NAME 24 //maksymalna dlugosc nazwy pliku (np. app.exe)
#define MAX_ADDR 231 //maksymalna dlugosc adresu pliku
#define MAX_CMD 512 //maksymalna dlugosc komendy

struct plik {
    char nazwa[MAX_NAME];
    char adres[MAX_ADDR];
};

void wskazSerwer(struct sockaddr_in *);
void wskazPlik(struct plik *);
int wyslijZlecenie(char, int, struct sockaddr_in *, struct plik *);
int szukaj(char *, char);

/*
 *
 */
int main(int argc, char** argv) {
    struct plik file;
    struct sockaddr_in addr;
    char bufor[MAX_BUF], zadanie = 'k';
    int sockd, statusRead, statusWrite, i;
    void *ptr;
    FILE *fp;

    //Faza przygotowan
    addr.sin_family = AF_INET;
    inet_aton("127.0.0.1", &addr.sin_addr);
    addr.sin_port = htons(6666);

    file.adres[0] = '\0';
    file.nazwa[0] = '\0';


    while (1) {
        //system("clear");
        printf("\n\tProjekt SKM - Zadanie 25\n");
        printf("\t\tKlient\n\n");

        printf("Serwer: %s:%d\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
        printf("Adres pliku: \"%s\"\n", file.adres);
        printf("Plik: \"%s\"\n\n", file.nazwa);

        printf("1. Wskaz serwer\n");
        printf("2. Wskaz adres pliku\n");
        printf("3. Pobierz plik z Internetu za posrednictwem serwera\n");
        printf("4. Sprawdz status transmisji plikow na serwerze\n");
        printf("5. Skopiuj plik z serwera\n");
        printf("6. Wyjscie\n");

        getchar();
        sockd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockd == -1) {
            perror("Socket nie zostal utworzony\n");
            exit(1);
        }

        statusRead = connect(sockd, (struct sockaddr *) & addr, sizeof (addr));
        if (statusRead == -1) {
            perror("Blad polaczenia\n");
            exit(1);
        }

        memset(bufor, 0, MAX_BUF);
        bufor[0] = 'k'; //kod zlecenia

        ptr = &file;

        //zbuforowanie i wyslanie struktury z danymi pliku, ktorego zlecenie dotyczy
        for (i = 0; i<sizeof (struct plik); i++) {
            bufor[i + 1] = *((char *) (ptr + i));
        }

        statusWrite = write(sockd, bufor, MAX_BUF);
        if (statusWrite == -1) {
            perror("Blad polaczenia\n");
            exit(1);
        }

        //zamkniecie gniazda dla pisania
        shutdown(sockd, SHUT_WR);

        printf("Wyslano zlecenie do serwera\n");

        //opracowanie odpowiedzi serwera
        memset(bufor, 0, MAX_BUF);
        statusRead = read(sockd, bufor, MAX_CODE);
        if (statusRead == -1) {
            perror("Blad polaczenia\n");
            exit(1);
        }

        if (zadanie == 'k') {
                printf("Rozpoczynam kopiowanie pliku...\n");

                statusRead = 1;
                /* DEBUG czy musi byc osobny katalog??
                strcpy(cmd, "mkdir ");
                strcat(cmd, plik->adres);
                strcat(cmd, " >/dev/null 2>&1");
                //jesli katalog nie istnieje -utworz go
                system(cmd);*/

                fp = fopen("wtf.txt", "w");

                while (statusRead != 0) {
                    memset(bufor, 0, MAX_BUF);
                    statusRead = read(sockd, bufor, MAX_BUF);
                    if (statusRead == -1) {
                        perror("Blad polaczenia\n");
                        exit(1);
                    }

                    statusWrite = fwrite(bufor, 1, statusRead, fp);
                    if (statusWrite == -1) {
                        perror("Blad zapisu w pliku\n");
                        exit(1);
                    }
                    printf("%s", bufor);
                }
                close(fp);
                printf("Plik skopiowano!\n");
            } 
        close(sockd);
    }
    return 1;
}

Jeżeli się przyjrzycie, lub w notepadzie odpalicie porównywanie plików to okaże się, że jedyna różnica między tymi kodami to fakt, że w tym, ktory nie działa jest pętla while(1) do wyświetlenia menu.

// <cpp> </cpp> [mf]

0

sprobuj z send/recv, send z opcja MSG_NOSIGNAL, albo ustaw signal(SIGPIPE,SIG_IGN);, SIGPIPE zabija proces, a w kazdym razie przeszkadza w jego dzialaniu, gdy wysylane sa dane do zamknietego pliku (socketa,...)

system(), to bardzo zly nawyk i uproszczenie podczas pisania programow. wyobraz sobie sytuacje, gdy serwer jest odpalany z roota i wywoluje mkdir... system("mkdir z parametrami"), powloka przeszukuje zmienna srodowiska PATH zeby znalezc programik i znajduje: /bin/mkdir. a co sie stanie, jesli zmienna PATH nie bedzie zawierala /bin albo przed wskazaniem na /bin bedzie zawierala katalog w ktorym bedzie symlink o nazwie mkdir do /bin/bash? stracisz system. na szczescie potrzeba rowniez suid, zeby zwykly user mogl cos odpalic na prawach roota i sam demon tez musialby ustawiac sobie uid, tak więc:

man 2 mkdir albo man 3p mkdir, oraz:

void ClrScr(){
  printf("\033[2J"); // Czyści ekran
  printf("\033[0;0f"); // Ustawia kursor w lewym, górnym rogu
}

dalej jesli otwierasz plik fopen a potem uzywasz fread/fwrite, czyli czytasz binarnie do buforów, uzywaj opcji "b" fopen("plik","wb") / fopen("plik","rb");

dalej shutdown socketa moze powodowac utrate pakietow, powinienes go uzywac w sytuacjach awaryjnych, gdy np. klient rozlaczy sie od serwera a ty wciaz masz otwarty socket i nie masz zamiaru czekac na timeout przy close(fd). przy okazji close(fd) sluzy do prowidlowego zamkniecia socketa i probuje zapisac/wyslac niedostarczone dane.

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

#define MAX_BUF 1024 //maksymalna pojemnosc bufora
#define MAX_CODE 11 //maksymalna dlugosc kodu bledow
#define MAX_NAME 24 //maksymalna dlugosc nazwy pliku (np. app.exe)
#define MAX_ADDR 231 //maksymalna dlugosc adresu pliku
#define MAX_CMD 512 //maksymalna dlugosc komendy

struct plik {
    char nazwa[MAX_NAME];
    char adres[MAX_ADDR];
};

void wskazSerwer(struct sockaddr_in *);
void wskazPlik(struct plik *);
int wyslijZlecenie(char, int, struct sockaddr_in *, struct plik *);
int szukaj(char *, char);

int main(int argc, char** argv) {
    struct plik file;
    struct sockaddr_in addr;
    char bufor[MAX_BUF], zadanie = 'k';
    int sockd, statusRead, statusWrite, i;
    void *ptr;
    FILE *fp;

    //Faza przygotowan
    addr.sin_family = AF_INET;
    inet_aton("127.0.0.1", &addr.sin_addr);
    addr.sin_port = htons(6666);

    file.adres[0] = '\0';
    file.nazwa[0] = '\0';

    sockd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockd == -1) {
        perror("Socket nie zostal utworzony\n");
        exit(1);
    }

    statusRead = connect(sockd, (struct sockaddr *) & addr, sizeof (addr));
    if (statusRead == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

//    memset(bufor, 0, MAX_BUF);  // szkoda czasu i tak zapelnisz danymi ktore przyjda i dokladnie bedziesz wiedzial ile ich jest
    bufor[0] = 'k'; //kod zlecenia

    ptr = &file;
    //zbuforowanie i wyslanie struktury z danymi pliku, ktorego zlecenie dotyczy
    for (i = 0; i<sizeof (struct plik); i++) {
        bufor[i + 1] = *((char *) (ptr + i));
    }

    statusWrite = write(sockd, bufor, MAX_BUF);
    if (statusWrite == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

// TEZ nie musisz jesli nie chcesz wysylac to po prostu przestajesz wysylac, shutdown moze powodowac/powoduje porzucenie danych
    //zamkniecie gniazda dla pisania
//    shutdown(sockd, SHUT_WR);

    printf("Wyslano zlecenie do serwera\n");

    //opracowanie odpowiedzi serwera
//    memset(bufor, 0, MAX_BUF);        // jak wyzej, nie potrzeba zerowac
    statusRead = read(sockd, bufor, MAX_CODE);
    if (statusRead == -1) {
        perror("Blad polaczenia\n");
        exit(1);
    }

    if (zadanie == 'k') {
        printf("Rozpoczynam kopiowanie pliku...\n");

        statusRead = 1;
        /* DEBUG czy musi byc osobny katalog??

// man 2 mkdir :) -> mkdir(plik.adres,0755);

        strcpy(cmd, "mkdir ");
        strcat(cmd, plik->adres);
        strcat(cmd, " >/dev/null 2>&1");
        //jesli katalog nie istnieje -utworz go
// wywolanie zewnetrznego programu jest proszeniem sie o przejecie calego systemu
// gdy odpalany na prawach roota program wykonuje zewnetrzny program, np. /bin/mkdir
// wystarczy zrobic symlinka do powloki (bash/sh/csh/tcsh/ksh/...) o nazwie mkdir i podmienic w srodowisku zmienna PATH
        system(cmd);*/

        fp = fopen("wtf.txt", "wb");  // WB !!!

        while (statusRead != 0) {
            memset(bufor, 0, MAX_BUF);
            statusRead = read(sockd, bufor, MAX_BUF);
            if (statusRead == -1) {
                perror("Blad polaczenia\n");
                exit(1);
            }

            statusWrite = fwrite(bufor, 1, statusRead, fp);
            if (statusWrite == -1) {
                perror("Blad zapisu w pliku\n");
                exit(1);
            }

//            printf("%s", bufor);  // NIE NIE NIE !! jesli odczytasz MAX_BUF z socketa, czyli zapchasz caly bufor, to jak sobie zagwarantujesz ze na koncu jest znak \0 ?
            write(1,bufor,statusRead); // 1 to stdout ... ls -l /dev/stdout, odpowiednio 0 to stdin a 2 to stderr, tak samo jak przekierowania pod powloka

        }
        fclose(fp);    // skoro fopen(), to fclose()
        printf("Plik skopiowano!\n");
    }
    close(sockd);

    return 1;
}

1 użytkowników online, w tym zalogowanych: 0, gości: 1