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