Model klient – serwer

Prosty serwer

Początkowy etap przygotowania programu jest identyczny dla klienta i serwera jest bardzo podobny podstawowe różnice sprowadzają się do odpowiedniego uzupełnienia struktury: struct sockaddr_in serwer_adr

//obs ługa serwera

struct sockaddr_in klient_adr

//obs ługa klienta

oraz użycia wywołań systemowych bind() i listen() zamiast connect() a następnie accept() do obsługi połączenia.

Etapy uruchomienia jednowątkowego/jednoprocesowego serwera TCP/IP

1. utworzenie gniazdka sockfd

2. zarezerwowanie pamięci dla struktury sockaddr_in serwera 3. uzupełnienie struktury typu sockaddr_in serwera

◦ dla nasłuchiwania na wszystkich lokalnych adresach dla pola struktury serwer_addr.sin_addr.s_addr ustawiamy INADDR_ANY

◦ wybranie portu serwer_addr.sin_port = htons(wybrany_port) 4. skojarzenie utworzonego gniazda z adresem i portem za pomocą wywołania systemowego bind()

5. Przejście serwera w tryb nasłuchiwania za pomocą wywołania systemowego listen()

6. akceptowanie nadchodzącego połączenia wywołaniem za pomocą accept() 7. obsługa połączenia – read/write send/recv 8. po zamknięciu połączenia serwer może zakończyć działanie lub przejść do obsługi kolejnego połączenia.

Opisy funkcji

bind

-pr

zypisuje nazwę do gniazda

#include <sys/types.h>

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); działanie:

skojarzenie gniazda z adresem nazywanie też „przypisaniem nazwy do gniazda” -

etap poprzedzający rozpoczęcie nasłuchiwania serwera argumenty:

sockfd – deskryptor gniazdka (jeżeli do niego jest przypisany adres to zostanie użyty) addr

- struktura definiująca adresy nasłuchiwania i port (patrz punkt 3) addrlen - wielkość struktury adresowej

wynik:

0 – w przypadku powodzenia

-1 – w przypadku błędu

listen

– n

asłuchuje nadchodzące połączenia

#include <sys/types.h>

#include <sys/socket.h>

int listen(int sockfd, int backlog);

działanie:

przełącza gniazdo w stan pasywny – nasłuchiwania połączeń argumenty:

sockfd – deskryptor gniazda

backlog – maksymalna długość kolejki oczekujących na połączenie klientów jeżeli kolejka przekroczy tę liczbę nowy klient otrzymuje komunikat o błędzie wynik:

0 – w przypadku powodzenia

-1 – w przypadku błędu

accept

– akc

eptuje przychodzące połączenia od klientów

#include <sys/types.h>

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); działanie:

wybiera pierwsze nadchodzące połączenie z kolejki skojarzonej z deskryptorem sockfd, otwiera to połączenie zwracając jego nowy deskryptor, argumenty:

sockfd – gniazdo na którym nasłuchuje serwer addr – wskaźnik do struktury adresowej klienta np sockaddr_in klient_adr

addrlen – wskaźnik do liczby określającej rozmiar struktury adresowej klienta np: kli_len = sizeof(klient_adr);

wynik:

zwraca liczbę >0 będącą deskryptorem zaakceptowanego połączenia UWAGA read/write recv/send powinny odnosić się do tego gniazdka Scenariusze obsługi połączeń

Obsługę nadchodzącego połączenia można zrealizować na kilka sposobów:

•

Najprostszy – obsługa tylko jednego połączenia i zamknięcie serwera. Rzadko stosowany

•

Pętla z sekwencyjną obsługą kolejnych nadchodzących połączeń. Stosowany w przypadku krótkich odpowiedzi serwera np protokołem UDP ale także TCP – daytime, DNS itp.

Zwykle uruchamia się pętlę nieskończoną for(;;){ ...} lub whlie(1){...}

for (;;){

poloczenie = accept(...)

read/write send/recv

close(poloczenie)

}

•

Pętla z utworzeniem nowego procesu lub wątku (każdy obsługuje jedno połączenie).

Stosowany tam gdzie konieczna jest dłuższa komunikacja między serwerem i klientem (wszystkie bardziej zaawansowane serwery).Pętla wygląda tak jak w poprzednim wypadku

ale po zaakceptowaniu połączenia tworzony jest np nowy proces, który je obsługuje.

int pid,poloczenie;

...

poloczenie = accept(...)

//sprawdzenie czy nadchodz ce po

ą

łączenie jest poprawne

pid = fork()

if(pid == 0 ) {

//jesteśmy w procesie potomnym

close(sockfd);

// odcinamy si od g

ę

łównego gniazda

write(poloczenie, ...); // tu ca ła obs ługa nowego po łą czenia

...

close(poloczenie)

//po zako c

ń zeniu zamykamy nasze gniazdo

exit(0);

// wyjście z podprocesu

}

close(poloczenie)

//proces rodzicielski nie potrzebuje

•

poll() - jest to wywołanie systemowe, które oczekuje na zdarzenia związane z jakimś deskryptorem np deskryptorem gniazda

•

select() - monitoruje wiele deskryptorów, jeżeli któryś jest aktywny to umożliwia komunikację we/wy z nim

fork

– t

worzy nowy proces

wynik:

0 – w procesie potomnym

>0 – w procesie rodzica – jest to numer procesu dziecka

<0 - błąd

Zadanie 1

Napisać serwer TCP, którego port jest podawany z linii poleceń. Po połączeniu z serwerem ma odpowiadać:

połączyłeś się z adresu: <tu ma się pojawić adres IP klienta> autor:<Imię i nazwisko>

potem serwer zamyka połączenie i kończy działanie Zadanie 2

Napisać serwer TCP, którego port jest podawany z linii poleceń. Po połączeniu z serwerem ma odpowiadać:

połączyłeś się z adresu: <tu ma się pojawić adres IP klienta> autor:<Imię i nazwisko>

Serwer ma obsługiwać maksymalnie 3 oczekujące połączenia