sobota, 6 czerwca 2009

Serwer cd

Dobra wiadomość jest taka, że szablon, jaki ostatnio tu umieściłem, nie uległ zmianie, rzeczywiście wystarczyło go rozbudować. Oczywiście wyniknęły problemy: gniazda faktycznie można traktować jak pliki przy pomocy funkcji read() i write(), ale szybko okazuje się, że nie zawsze wszystko działa jak należy. Moją bolączką był głównie problem z powtórnym odczytaniem, w sieci można też znaleźć informacje, że czasami zczytywana jest niepełna ilość znaków. Dlatego zdecydowałem się na zastąpienie read() przez recv().

Kolejnym problemem okazało się wykrycie końca pliku. Serwer wysyłając plik czytał kolejne jego porcje, ładował do bufora, następnie wysyłał (zapisywał do gniazda) zawartość bufora. Gdy plik się skończył (ilość odczytanych znaków <= 0), wychodził z pętli i czekał na kolejną komendę. Gdy klient pobierał plik, w momencie gdy gniazdo okazywało się puste, read blokował proces oczekując na zapełnienie gniazda. Teoretycznie można wysłać konkretny łańcuch znaków, który byłby identyfikowany jako koniec transmisji, ale nie można zagwarantować, że taki łańcuch nie pojawi się w przesyłanym pliku. Innym rozwiązaniem jest zastosowanie recv z parametrem MSG_DONTWAIT, jednak w rzadkich przypadkach może to spowodować niepełny odbiór danych, a tego byśmy nie chcieli. Rozwiązaniem tych problemów jest przyjęcie następującej konwencji: przesyłanie wielkości pakietu na samym początku. Z żalem przyznaję, że zanim na to wpadłem, zdążył mi to zasugerować ktoś inny... w każdym razie, w dalszym razie posłużyłem się tak zdefiniowanymi funkcjami:
int pobierz(int src, char *bufor) { int dlugosc, x; recv(src, &dlugosc, sizeof(int), MSG_WAITALL); if (dlugosc == 0) return -1; x = recv(src, bufor, dlugosc, MSG_WAITALL); return dlugosc; }; void wyslij(int des, char *bufor, int len) { write(des, &len, sizeof(len)); write(des, bufor, len); };


W samym programie posługiwałem się następującą konwencją. Przy wysyłaniu:

wyslij(gdzie, co, jak_duze);
wyslij(gdzie, cokolwiek, 0);

Przy pobieraniu:

x = pobierz(skad, bufor);
if (x mniejszy od 0) { /* koniec transmisji */ }

W ten sposób serwer i klient już hulają :) Poniżej załączam fragmenty kodów obu aplikacji odpowiedzialne za przesłanie listy plików dostępnych na serwerze.

Klient.c:
do { printf("Wybierz polecenie:\n1 - LIST\n2 - GET\n3 - CLOSE\n"); fflush(stdin); scanf("%d", &wybor); } while ((wybor < 1) || (wybor > 3)); write(gniazdo, &wybor, sizeof(int)); // wyslanie komunikatu switch (wybor) { case 1: // LIST - odbieram liste plikow while(1) { memset(bufor, '\0', BUF_SIZE); odczytano = pobierz(gniazdo, bufor); if (odczytano <= 0) break; write(1, bufor, odczytano); printf("\n"); } break; . . . }


Serwer.c:

while(1) { printf("Waiting for command\n"); memset(bufor, '\0', BUF_SIZE); read(one, &i, 4); // odczytanie komendy switch (i) { case 1: printf("Odebralem LIST\n"); list(one); break; . . . } . . . } int list(int gdzie) { struct dirent *pozycja; DIR *katalog; struct stat info; int prawa, i; katalog = opendir("."); if (katalog == NULL) return 1; while ((pozycja = readdir(katalog)) != NULL) { if (stat(pozycja->d_name, &info) == 0) { if (S_ISDIR(info.st_mode)) continue; wyslij(gdzie, pozycja->d_name, strlen(pozycja->d_name)); } } wyslij(gdzie, "Koniec transmisji", 0); return 0; }

Brak komentarzy: