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; }

czwartek, 4 czerwca 2009

Smutna refleksja

Ksiądz Twardowski napisał kiedyś w swoim wierszu: "spieszmy się kochać ludzi, tak szybko odchodzą". Z czasem stało się to zdanie bardzo często używane.
Ostatnio jednak miałam okazję się przekonać, że kochać ludzi to za mało. Brakuje jeszcze, żeby tę miłość okazywać, tak żeby wiedzieli że są dla nas kimś ważnym. Bo potem można tego żałować bardziej, dużo bardziej niż jakby tej osoby się nie kochało wcale. Sprafrazowałabym słowa księdza Twardowskiego: "spieszmy się okazywać ludziom naszą miłość, tak szybko odchodzą".

czwartek, 28 maja 2009

Serwer

Sesja zbliża się wielkimi krokami, trza zabrać się do pracy :) Oczywiście to co najnudniejsze automatycznie spada na sam koniec listy rzeczy do zrobienia i tym sposobem zabrałem się za pisanie zadanka z sieci. Zaopatrzywszy się w odpowiednie zaplecze teoretyczne, włączyłem muzykę...




... i przepisałem kawałek kodu z książki ;)

#include <sys/types.h>
#include
<sys/socket.h>
#include
<netinet/in.h>
#include
<netdb.h>
#include
<stdio.h>
// printfy
#include <stdlib.h>
// exit()
#include <string.h>
//zawiera definicje memset()

#define SERVER_PORT 4095
#define BUF_SIZE 4096
#define QUEUE_SIZE 10

fatal(
char *x)
{
printf(
"%s", x);
exit(
1);
}

int main(int argc, char **argv)
{
char bufor[BUF_SIZE];
int gniazdo, one = 1, i;
struct hostnet *ServerInfo;
struct sockaddr_in NrIP;

memset(
&NrIP, 0, sizeof(NrIP)); // wypełniam zerami NrIP
NrIP.sin_family = AF_INET; // dziedzina adresów: IPv4
NrIP.sin_addr.s_addr = htonl(INADDR_ANY);
/* może korzystać z każdego numeru IP jaki znajdzie dla danego
komputera. W praktyce oznacza to, że gdy są dwa interfejsy
(np. sieć lokalna z kabelka i wifi(internet) to obsługiwane
będą połączenia od obu tych interfejsów
*/
NrIP.sin_port
= htons(SERVER_PORT); //nr portu

gniazdo
= socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
/* SOCK_STREAM - dane przesyłane strumieniowo
IPPRO_TCP - z wykorzystaniem protokolu TCP
*/
if (gniazdo < 0)
fatal(
"Blad utworzenia gniazda\n");

setsockopt(gniazdo, SOL_SOCKET, SO_REUSEADDR, (
char*) &one, sizeof(one));
/*SOL_SOCKET - operacja ma być wykonana na poziomie gniazda
(a nie na przez oprogramowanie IPv4 lub TCP)
SO_REUSEADDR - pozwalamy na wiele równoczesnych połączeń serwera.
Ustawiamy tą wartość (one). Ostatni parametr mówi jak
duża jest zmienna one
*/

//zmienna one nie jest już dalej potrzebna.
one = bind(gniazdo, (struct sockaddr *) &NrIP, sizeof(NrIP));
if (one < 0)
fatal(
"Blad przyporzadkowania adresu IP do gniazda\n");

// maksymalna ilość żądań połączenia
one = listen(gniazdo, QUEUE_SIZE);
if (one < 0)
fatal(
"Error: listen()\n");

//--------------------------------------------------------------
// gniazdo gotowe do pracy
//--------------------------------------------------------------

while (1)
{
one
= accept(gniazdo, 0, 0);
/* argumenty 2 i 3 mogą określać strukturę sockaddr, która
zostałaby wypełniona danymi nt połącznia - niepotrzebne nam to.
*/
if (one < 0)
fatal(
"Error: accept()\n");
read(one, bufor, BUF_SIZE);
// odczytanie nazwy przychodzącego pliku
i = -1;
if (!strcmp(bufor,"LIST")) i = 0;
if (!strcmp(bufor "GET")) i = 1;
if (!strcmp(bufor "CLOSE")) i = 2;

switch (i)
{
case 0:
printf(
"Odebralem LIST\n");
break;
case 1:
printf(
"Odebralem GET\n");
break;
case 2:
printf(
"Odebralem CLOSE\n");
break;
default:
printf(
"Zly format polecenia\n");
break;
};
close(one);
// rozlaczenie
}

}


Jest to szkielet, który jest jeszcze mniej funkcjonalny, niż jego odpowiednik w książce Tanenbauma, ale delikatnie ukształtowany pod kształt tego, co chcę osiągnąć - przynajmniej tak myślę. No i przesadnie wręcz obkomentowany.

EDIT: Wiele więcej problemów niż z samym powyższym kodem, miałem z jego czytelnym zamieszczeniem - dlatego zresztą notka jest tak krótka i skąpa. Posłużyłem się w końcu tym sposobem , ale nie jest do końca skuteczny - między liniami kodu powstały trzyakapitowe przerwy... Wynik wybitnie nie jest zadowalający.

EDIT2: Znalazłem wreszcie świetne narzędzie: www.blogtrog.com. Wada? Nie obsługuje kodu w C/C++... trudno. Grunt, że jest czytelnie i nie trzeba nic ręcznie poprawiać. Victory!