Model klient – serwer

Transkrypt

Model klient – serwer
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
struct sockaddr_in
serwer_adr
klient_adr
//obs ł uga serwera
//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 -przypisuje 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 – nasł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 – akceptuje 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
pid = fork()
if(pid == 0 ) {
close(sockfd);
write(poloczenie, ...);
...
close(poloczenie)
exit(0);
}
close(poloczenie)
•
•
po łą czenie jest poprawne
//jeste śmy w procesie potomnym
// odcinamy się od g ł ównego gniazda
// tu ca ł a obs ł uga nowego po łą czenia
//po zakończeniu zamykamy nasze gniazdo
// wyj ście z podprocesu
//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 – tworzy 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