SOISK - SYSTEMY OPERACYJNE I SIECI KOMPUTEROWE
Tomasz Puchała

LAB 2 i 3 Serwer Klient

Interaktywne aplikacje Serwer – Klient
1) Program laboratorium:
Na kolejnych zajęciach laboratoryjnych (9 zajęć po 2*45 minut) będziemy wykonywać następujące
aplikacje:
0 – Zajęcia organizacyjne, przedstawienie programu laboratorium Programowania Usług Sieciowych.
1 – Zapoznanie ze środowiskiem.
2,3 – Napisanie iteracyjnego programu klienta i serwera wykorzystującego protokoł TCP.
4 – Napisanie współbieżnego serwera w oparciu o funkcję fork().
5 – Napisanie współbieżnego serwera w oparciu o funkcję select().
6 – Napisanie iteracyjnego programu klienta i serwera UDP.
7 – Doposażyć programy z poprzednich zajęć w zabezpieczenia funkcjonowania.
8 – Zaliczenie laboratorium i wpisy.
2) Wstęp teoretyczny
Celem ćwiczenia jest wykonanie iteracyjnych aplikacji serwera i klienta opartych na protokole TCP
i gniazdowej strukturze adresowej. Serwer iteracyjny pozwala na podłączenie wyłącznie jednego
klienta w danym czasie. Działanie serwera polega na pobraniu danych od klienta → wykonaniu
operacji na danych klienta, oraz wysłanie rezultatu działania.
Aby zrealizować zadania niniejszej instrukcji student powinien mieć opanowany materiał na temat:
· Protokołu TCP/IP
· Gniazdowej struktury adresowej
· Gniazd protokołu TCP
Algorytm komunikacji serwera iteracyjnego:
Serwer – program po uruchomieniu powinien wykonać pewne czynności konfiguracyjne, definiujące
zamierzone działanie serwera. Na początek tworzony jest deskryptor gniazda komunikacyjnego
sockfd (rownież w aplikacji klienta). Pierwszy argument określa rodzinę adresow, drugi rodzaj
gniazda, trzeci protokoł:
int sockfd = socket(AF_INET, SOCK_STREAM, 0)
AF_INET – protokoł IPv4, SOCK_STREAM – gniazdo strumieniowe TCP, przekazujemy 0.
Funkcja zwraca liczbę całkowitą ktora stanowi identyfikator danego gniazda, lub -1 gdy wystąpi błąd.
Następnie wywoływana jest funkcja, ktora w całej strukturze adresowej wskazywanej przez pierwszy
element „adres” ( struct sockaddr_in servaddr; ) umieści zera w kompletnej liczbie bajtow zajmowanych
przez strukturę.
bzero(&servaddr, sizeof(servaddr));
Do struktury przypisujemy (ustawiamy) rodzaj protokołu, numer domyślny IP oraz numer portu np.:
50.
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);
servaddr.sin_port = htons(50);
Kolejnym krokiem jest „przybicie” gniazda czyli przypisanie (w rozwijanym przykładzie) 32 bitowego
adresu protokołowego.
bind(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))
Następnie serwer ustawia gniazdo w tryb pasywny (czyli tryb nasłuchu), zestawia połączenie i czeka
na klienta.
listen(sockfd, LISTENQ)
Powyższa funkcja rozpoczyna nasłuch na gnieździe podanym jako parametr 1. Jeżeli wszystko jest
poprawnie skonfigurowane funkcja zwraca 0.
int connfd = accept(sockfd, (struct sockaddr *) NULL, NULL)
Następnie w pętli (nieskończonej) uruchamiamy funkcję blokującą, ktora oczekuje na utworzenie
połączenia przez klienta. W przypadku gdy połączenie zostanie utworzone funkcja kończy działanie.
Drugi parametr funkcji określa wskaźnik do struktury adresowej w ktorej funkcja ma umieszczać dane
klienta w przypadku gdy dane nie są potrzebne wpisujemy NULL. Trzeci parametr funkcji określa
długość struktury danych klienta np.: socklen_t clilen=sizeof(cliaddr), gdzie sockaddr_in cliaddr. Funkcja
zwraca wartość deskryptora jeżeli konfiguracja jest poprawna (zwraca wartość nieujemną).
Ostatnim krokiem jest uruchomienie pętli odczytu danych z bufora pamięci.
while ((int n = read(connfd, recvline, MAXLINE)) > 0) { //przetwarzanie danych otrzymanych od klienta przez
buffor danych recvline [MAXLINE+1]
} // zamknięcie funkcji odczytu
} // zamknięcie funkcji blokującej
Funkcja read odczytuje dane z podanego deskryptora gniazda lub deskryptora pliku. Pierwszy
parametr to deskryptor, drugi miejsce – wskaźnik do miejsca w pamięci z odczytanymi danymi, trzeci
parametr funkcji długość danych w bajtach.
Potrzebne biblioteki:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/types.h>
#define MAXLINE 4096
Przydatne funkcje:
int err (char*s) //funkcja wyświetla błąd systemowy
{
printf("%sn",s);
printf("Errno:%dn",errno);
fprintf(stderr,"%sn",strerror(errno));
exit(-1);
}
void wyslij (int sockfd, const char*c)
{
If (write(sockfd, c, strlen(c))<=0) //strlen oblicza dlugosc ciagu znakow bez spacji
err("write error");
//printf("Wyslano dane do serwera: ");
}
Funkcja write wysyła dane do podanego deskryptora, parametry to deskryptor pliku lub gniazda,
wskaźnik do miejsca w pamięci gdzie zapisane są dane do wysłania oraz długość danych w bajtach.
Algorytm komunikacji klienta iteracyjnego:
Klient – program zostaje uruchomiony z dwoma parametrami, pierwszym parametrem jest numer IP
serwera w notacji dziesiętno - kropkowej, zaś drugi parametr określa numer portu, na ktorym klient
oczekuje odpowiedzi na zadane zapytanie.
if(argc!=3)
err("Nie podano numeru IP oraz Portu");
Ustawienia protokołu tak jak dla serwera
sockfd=socket(AF_INET,SOCK_STREAM,0))
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family=AF_INET;
Wprowadzenie funkcji konwersji adresu podanego jako parametr wywołania programu z postaci
kropkowo – dziesiętnej na 32 bitową liczbę całkowitą. W przypadku gdy podamy niepoprawnie adres
IP, funkcja wyświetli błąd.
inet_pton(AF_INET,argv[1],&servaddr.sin_addr)<0)
err("Zly format adresu IP");
if((servaddr.sin_port=htons(atoi(argv[2])))<0)
err("Zly numer portu");
Wywołujemy funkcję connect, ktora nawiązuje połączenie aktywne z serwerem przez deskryptor
gniazda
break;
strcat(buff,k); //przepisuje tablice źrodłowa k do docelowego bufora buff
strcat(buff,"rn"); //r -> przejście na początek bieżącej linii
wyslij(sockfd,buff);
n=read(sockfd,recvline,MAXLINE); //odczyt zawartości recvline
recvline[n]=0;
if(fputs(recvline,stdout) == EOF)
err("fputs error");
if(n<0)
err("read error");
}
if(close(sockfd)<0)
err("close error");
return 0;
}
Wyświetlenie ( i przesłanie) danych przez serwer, otrzymanych od klienta:
//odczyt danych wysłanych przez klienta
for (i=0; j<n; i++) {
recvline[currpos++]=buff[i]; //przepisanie bufora buff do recvline
if (buff[i]=='n'){
buff[i+1]=0;
if (!strcmp(buff, "koniecrn")){
printf("Odczytane dane: ", buff[i]);
if (close(connfd))
err("close error");
} else
wyslij(connfd, buff);
currpos = 0;
break;
}
3) Zadania do wykonania
Aby zaliczyć laboratoria 2 i 3 należy wykonać następujące zadania:
Laboratorium 2 – Napisać aplikację klienta, ktory będzie wysyłał dane do serwera w postaci:
a) Ciągu znakow np.: a<spacja> +<spacja>b.
b) Liczb całkowitych – program pyta o liczbę a, b, działanie.
c) Ciągu znakow.
Laboratorium 3 – Napisać aplikację serwera, ktory będzie odbierał dane od klienta w postaci:
a) Ciągu znakow, przy czym serwer będzie identyfikował działania +,-,*,/ - wykonywał operację
na liczbach podanych jako a i b.
b) Liczb całkowitych – serwer wysyła odpowiedź w postaci wyniku działania arytmetycznego.
c) Ciągu znakow – serwer zamienia kolejność wyświetlania danych i wysyła do klienta.
__________________________________________________________________________ Serwer

#include <stdio.h>

#include <errno.h>

#include <string.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>


#define MAXLINE 4096

#define LISTENQ 1024



void err(char* s)
{

printf("%sn", s);

printf("Errno: %dn", errno);

exit(-1);

}



int main(int argc, char **argv)
{

int listenfd, connfd;

int l[3], j=0,wynik;

struct sockaddr_in servaddr; // struktura adresow internetowych

char buff[MAXLINE], recvline[MAXLINE],buff2[MAXLINE],str[MAXLINE],z;

int currpos = 0, i, n;

time_t ticks; // zwraca czas w sekundach od 01/01/1970



if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) // utworzenie gniazda (IPv4, gniazdo strumieniowe, protokol 0 dla SOCK_STREAM

err("socket error");


bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htons(INADDR_ANY);

servaddr.sin_port = htons(13);

if (bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) // przypisanie gniazdu lokalnego adresu protokolu

err("bind error");

if (listen(listenfd, LISTENQ) < 0) // przeksztalcenie gniazda w gniazdo bierne - naluchiwanie

err("listen error");



while (1) {


if ((connfd = accept(listenfd, (struct sockaddr *) NULL, NULL)) < 0) // akceptowanie - pobieranie pierwszego zadania z kolejki i utworzenie gniazda polaczeniowego

err("accept error");


while ((n = read(connfd, buff, MAXLINE)) > 0) // odbieranie danych i umieszczanie w pojedynczym buforze
{

j=0;

for (i = 0; i < n; i++)
{
if(j==2) j=0;

if(buff[i]==' ')
{

currpos=0;

l[j]=atoi(recvline);

if(j==1) z=buff[i-1];

j++;

}

recvline[currpos++] = buff[i];

if (buff[i] == 'n')
{

buff[i + 1] = 0;

if (!strcmp(buff, "rn"))
{

if (close(connfd))

err("close error");

}
else
{
l[2]=atoi(recvline);

wynik=0;

strcat(buff2, buff);
strcat(buff2," = ");
if(z=='+')
wynik=l[0]+l[2];
if(z=='-')
wynik=l[0]-l[2];
if(z=='*')
wynik=l[0]*l[2];
if(z=='/')
wynik=l[0]/l[2];
if(z=='%')
wynik=l[0]%l[2];

sprintf(str, "%i", wynik);

strcat(buff2,str);

strcat(buff,buff2);
strcat(buff,"n");

bzero(buff2,sizeof(buff2));bzero(l,sizeof(l));

if (write(connfd, buff, strlen(buff)) <= 0)

err("write error");

bzero(recvline,sizeof(recvline));

currpos = 0;

bzero(buff,sizeof(buff));
}

currpos = 0;

break;

}
}
}
close(connfd);

}

return 0;

}

________________________________________________________________________________ Klient

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <string.h>

#define MAXLINE 4096

int err(char* s)

{

printf("%sn", s);

printf("Errno: %dn", errno);

exit(-1);

}



int main(int argc, char **argv)
{

int sockfd, n;

int i=0;

char k[100];

char recvline[MAXLINE+1], buff[MAXLINE+1];

struct sockaddr_in servaddr;


if (argc != 3)

err("Ussage: klient IP_ADDRESS PORT");


if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

err("socket error");


bzero(&servaddr, sizeof(servaddr));

servaddr.sin_family = AF_INET;


if (inet_pton(AF_INET, argv[1], &servaddr.sin_addr) < 0)

err("Wrong IP_ADDRESS format");

else

printf("Adres: %sn", argv[1]);


if ((servaddr.sin_port = htons(atoi(argv[2]))) < 0)

err("Wrong PORT format");

else

printf("Port po┼‚─…czenia: %dn", servaddr.sin_port);


if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)

err("connect error");


for(;;)
{

bzero(buff,sizeof(buff));

printf("wprowad┼║ dzia┼‚anie w postaci a + bnq - wyj┼›cie z programun");

fgets(k, sizeof(k), stdin);

if(k[0]=='q')

break;

strcat(buff,k);

strcat(buff,"rn");

if (write(sockfd, buff, strlen(buff)) <= 0)

err("write error");

printf("wys┼‚a┼‚em ");


n = read(sockfd, recvline, MAXLINE);

recvline[n] = 0;

if (fputs(recvline, stdout) == EOF)

err("fputs error");

if (n < 0)

err("read error");

}

if (close(sockfd) < 0)

err("close error");

return 0;

}