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