# Systemy operacyjne - lista 9 - [X] zad 1 - [X] zad 2 - [X] zad 3 - [X] zad 4 - [X] zad 5 - [X] zad 6` - [X] zad 7 - [X] zad 8 - [X] zad 9 `bonus` ## zad 1 warstwa: łącza (link) - presyła pakiety z warsty sieciowej do następnego korzenia, oraz przepuszcza otrzymany pakiet do warsty sieciowej. ETHENRNET WiFi, inne Sieciowa (network) - przesyła datagramy warsty transportowej z hosta do hosta. IP / routing Transportowa (Transport) - tcp / udp, transportuje pakiety z warstwy aplikacji pomiędzy procesami ![](https://i.imgur.com/mwO88UX.png) udp: ![](https://i.imgur.com/u33gUBQ.png) tcp: ![](https://i.imgur.com/UtUDJHH.png) pakiety - fragmenty danych z jakimiś nagłówkami. Kapsulkowanie to opakowywanie kolejnych informacji w dodatkowe informacje, służy ono kolejnym warstwom by móc wykonywać przeznaczone im zadania. ramka - podstawowa jednostka przekazu warsty łącza. (Datagram + nagłówek warsty łącza, np typ łącza, ethenret, wifi, adresy mac urządzeń) ![](https://i.imgur.com/58B3rKh.png) ![](https://i.imgur.com/UpaeCFz.png) Datagram to podstawowa jednostka przekazu, zwykle zbudowane z sekcji nagłówka i ładunku. (Segment + nagłówek warstwy sieciowej, np SOURCE DESTINATION) Segment warstwy transportowej enkapsułuje wiadomość warstwy apliakcji(Wiadomosc warsty aplikacji + nagłówek warsty transportowej, np UDP, bity wykrywania błędów) Dlaczego protokoły warstwy łącza i sieciowej nie są uzywane do komunikacji między procesami? - na jednej maszynie to bez sensu, bo dane nie są przesyłane przez jakieś fizyczne połączenia od hostu do hostu. - między procesami na 2 komputerach są używane, ale niemogły by istnieć same, potrzebujemy stwierdzić do i z jakiego procesu pakiet jest wysyłany ## zad 2 Na **UDP** nie możemy polegać, pakiet może zginąć, nie dotrzeć w odpowiednim czasie, może pojawić się błąd danych, a kolejny nie zostanie wysłąny. Możemy obsługę takich przypadków dodać przez aplikację, ponowne wysyłanie, potwerdzanie pakietów, algorytm okna przesuwnego, coś takiego realizuje TCP. Client może wysyłać wiele pakietów do różnych serwerów z jednego gniazda, a serwer otrzymywać pakiety z wielu klientów na jednym gnieździe, nie potrzebujemy ustawanwiać żadnego połączenia. **TCP** jes jednak połączeniowym sposobem, my jako użytkownicy widzimy to po prtostu ciąg bajtów, z połączonego wcześniej serwera, na których możemy polegać, jednak na jednym gnieździe ustanawiamy połączenie do jednego serwera, a serwer na jednym gnieździe ustanawia połączenie z nami(3 handshake). Protokół jest bardziej pewny, w takim sensie, że zostaniemy powiadomieni ile bajtów dotarło do serwera. A dzięki numerom sekwencyjnym będą one w odpowiedniej kolejności. **Dupleksowa** - komunikacja odbywa się w dwie strony i jednoczesnie -mając np dwa kable, jeden do wysyłanai drugi do odbierania **Półdupleksowa** - komunikacja odbywa się w dwie strony, ale pojedynczo. -np pojedynczy ethernet? **okno przesuwne animacja:** https://www.youtube.com/watch?v=lk27yiITOvU TCP implementuje algorytm okna przesuwnego, przesuwa okno dopiero jak wszystkie pierwsze n pakietów zostało potwerdzonych, są one ustawiane w dobrej kolejnosci dzięki numerom sekwencyjnym i długości segmentów. Wysyłający również posiada takie okno, które jest dopasowane do tego z serwera, pakiety z tego okna są wysyłane dopóki nie zostaną potwrdzone i okno będzie mogło się przesunąć. Strona odbierająca w potwerdzeniu wysyła również długość aktualnego okna, czyli tak naprawdę danych które może przyjąć. Taka aktualizacja okna nazywa się advertised window. Wysyłający dzięki temu nigdy nie przepełni bufora odbierającego. Taki proces nazywa się sterowaniem przepływem. ![](https://i.imgur.com/sUFe6kE.png) ![](https://i.imgur.com/dDjNJF2.png) ![](https://i.imgur.com/amPkp2K.png) abstrakcyjnie: 1. Serwer na początku jest w trybie LISTEN. 2. Wysyłamy list do obiorcy, który nas przedstawia. 3. Odbiorca po otrzymaniu listu wysyła list potwrdzający i również się przestawia. 4. Po otrzymaniu takiego listu wysyłamy odbiorcy listowne potwerdzenie odebrania przez nas listu. My teraz jesteśmy w pewien sposób połączeni z odbiorcą. 5. Odbiorca po otrzymaniu potwerdzenia też zaczyna z nami być w pewien sposób połączony. 6. Teraz wymieniamy się listami i je sobie potwrdzamy. Dalej bardziej kompleksowo. 1. Po otrzymaniu tego co chcieliśmy wysyłamy segment kończący **[FIN, ACK]** i przechodzimy w **FIN_WAIT1** 2. Serwer otrzyumje segment kończący, przechodzi w CLOSE_WAIT i wysyła potwierdzenie **[ACK]**, po czym wywoła close(), wysyła segment kończący **[FIN, ACK]** i przechodzi w LAST_ACK 3. nastepnie my otrzymujemy potwierdzenie i przechodzimy w tryb **FIN_WAIT2**, następnie otrzymujemy segment kończący, potwierdzamy go **[ACK]**, przechodzimy w **TIME_WAIT** i czekamy 60 sekund, po czym zamykamy połączenie. **UWAGA**: Jeśli otrzymamy segment **[FIN]** od serwera zanim dostaniemy potwierdzenie, to przechodzimy w CLOSING, które przejdzie w TIME_WAIT po dostaniu potwierdzenia. Serwer otrzymuje potwierdzenie pakietu kończącego, po czym zamyka połączenie. Otwarcie aktywne jak i również zamknięcie aktywne wykonujemy my. **Skąd serwer wie, że połączenie zostało zerwane?** Kiedy od dłużeszj chwili nie dostaje potwierdzeń, lub gdy serwer otrzyma pakiet FIN, i wyśle na niego potwerdzenie (gnaizdo do którego pisze serwer może rzucić jakimś sygnałem(SIGPIPE?), podobnie jak było w przypadku rur. **Sterowanie przepływem** - jest to sterowanie ilością wysyłanych pakietów, tak by nie przeciążyć odbierającego. Wysyłający dostosowuje swoje okno do możliwości okna drugiej strony. W każdym potwierdzeniu odbiorca ogłasza jaki ma teraz rozmiar okna, by serwer mógł się dostosować. **Rozmiar okna** jest zależny od **buforu** w jądrze odbierającego, jeśli jego aplikacja zwalnia i czyta mniej z socketu, a nawet zasypia, wtedy pakiety będą czekać i zajmować **bufor**, więc nastepne okno będzie musiało być mniejsze lub nawet 0, żeby nie przepełnić bufforu, aż aplikacja je weźmie. Wtedy serwer otrzymuje potwierdzenia z rozmiarem okna 0. Serwer po otrzymnaniu takiego potwierdzenia z oknem 0 (**zero-window**), to wysyła co jakiś czas pakiet synchronizujący (**zero-window-probe**), by w odpowiedzi dostać ewentualną informację o więszkym oknie. ## zad 3 ![](https://i.imgur.com/4LaD8fP.png) bind() - związanie gniazda z adresem lokanym. accept() - związanie gniazda z adresem zdanlnym. connect() - związanie gniazda z adresem zdalnym. Klient używa connect, jądro przydziela port z puli portów, a gdy połączenie zostanie zamknięte to port wraca do puli. Zatem to klient używa portu efemerycznego. klient-serwer to architektura systemu umożliwiająca podział ról. Polega ona na ustaleniu, że serwer zapewnia usługi dla klientów, zgłaszających do serwera żądania obsługi(GET). Gniazda strumieniowe to gniazda oparte na połączeniach, umożliwiają one sekwencyjny przepływ danych z gwarancją przesłania pakietu i zachowania kolejności. Polecenie int **listen(int sockfd, int backlog)** służy do nasłuchiwania (oczekiwania) na połączenia. Pierwszy argument sockfd to id deskryptora pliku gniazda, a drugi backlog to maksymalna liczba połączeń w kolejce dla deskryptora o zadanym sockfd. W przypadku, gdy kolejka zostanie zapełniona i zostanie wysłane kolejne żądanie, klient może otrzymać błąd z flagą ECONNREFUSED lub żądanie zostanie zignorowane. Polecenie int **accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)** Akceptujemy nim połączenie, **przekazujemy mu nasze utworzone gniazdo**, pod addr będzie znajodwać się adres gniazda klienta - czyli IP i PORT. Polecenie **zwraca deskryptor pliku służacy do komunikacji z klientem** przy pomocy uniksowych operacji. Po stronie klienta to connect zwraca taki deskryptor pliku połączenia z serwerem. gniazdo przekazywane do accept(2) jest związane z portem stałym w takiej strukturze(Ustawaimy ją bindem), a ten socket to socket na którym słuchamy i który posiada listę cozekujących połączeń: ipv4 lub ipv6: ![](https://i.imgur.com/6gxlH7e.png) jeśli ipv4, to taka struktóra będzie skastowana do tego wyżej ![](https://i.imgur.com/Ey2wqJN.png) ``` The argument sockfd is a socket that has been created with socket(2), bound to a local address with bind(2), and is listening for connections after a listen(2). ``` gniazdo zwracane, to gnaizdo do klienta z portem ulotnym(przydziela jej jądra na czas połączenia lub nawet wysłania jednego pakietu UDP) aplikacji Klient wywołuje close(), a serwer gdy próbuje czytać z deskryptora podobnie jak z rury dostanie EOF(SIGPIPE?), wtedy serwer również woła close(). ``` ``` ## zad 4 ![](https://i.imgur.com/zSQtg4t.png) **Gniazda datagramowe** to gniazda bezpołączeniowe. Oznacza to, że każdy pakiet jest indywidualnie adresowany i przekierowywany. Nie ma również gwarancji, że pakiet zostanie przesłany, ani że kolejność wysyłanych pakietów zostanie zachowana. **Serwer może od razu rozpocząć działanie**, ponieważ jedyne co potrzebował zrobić to związać gniazdo z portem, i zacząć oczekiwać na jakiekolwiek pakiety skierowane w jego stronę. **read i write** po stronie serwera jest niewystaczający gdyż nie podaje nam informacji skąd przyszły pakiety, zresztą nie mamy otawrtego deskryptora do konkretnego procesu, a jedynie gniazdo. Musimy wiedzieć też dokąd odesłać informację zwrotną i musimy również umieć powiedzieć ganiazdu gdzie chcemy daną porcję danych wysłać. By rozwiązać te problemy używamy ```c= size_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen); ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen); ``` **recvfrom()**: > **sockfd** to deskryptor gniazda, *buf to miejsce w pamieci do którego zostanie zapisana wiadomość, len długość wiadomości, > **fags** - falgi jak wiadomośc ma być odebrana. https://man7.org/linux/man-pages/man2/recvfrom.2.html > ***src_addr** miejsce w pamięci w które zapisany ma być ades nadawcy, > **addrlen** długość tej struktury. zwracana jest długość odebranych bajtów, jeśli nic nie zostało odebrane to 0, a jesli wystąpił błąd to -1. sendto() > **sockfd** deskryptor gniazda, > ***buf** wiadomosc do wysłania, > **len** długosc wiadomosci, > **flags** jak wiadomosc ma zostać wysłana, > ***dest_addr** i **addrlen** gdzie gniazdo ma wysłać wiadomość. zwracana jest liczba wysłanych(niekoniecznie odebranych przez odbiorce) bajtów, w przypadku błedu -1. Wykonanie **connect() na gnieździe klienta** sprawia, że jądro sprawdza, czy nie występują jakieś błędy, np. o nieosiągalnej destynacji, ponadto zapisuje adres IP i port, po czym wraca do procesu, który je wykonał. **Od tego momentu nie możemy już ustalić docelowego adresu IP ani portu dla operacji wysyłania**. Oznacza to, że nie używamy już **sendto(), tylko write() lub send()**. Wszystko, co zostanie zapisane do podłączonego gniazda UDP będzie automatycznie wysyłane na adres podany w connect(). Podobnie, nie możemy używać recvfrom(), **lecz read(), recv() lub recvmsg()** (lub null zamiast adresu?) Gniazdo teraz zwraca jedynie pakiety z danego IP ![](https://i.imgur.com/i83sEME.png) **Przewaga tych fuknkcji to możliwość buforowania.** ## zad 5 https://www.freebsd.org/cgi/man.cgi?query=read&sektion=2&apropos=0&manpath=FreeBSD+13.1-RELEASE+and+Ports **write(2)** może zwrócić short count, gdy w buforze gniazda nie ma wystarczająco miejsca, oznacza to że kernel nie mógł skopiować wszystkich danych do wysłania. Gdy w buforze nie ma w ogóle miejsca, to od razu zwracany jest błąd **EWOULDBLOCK**. **read(2)** zwróci short count, gdy w buforze nie ma wystarczająco miejsca na odczytanie wszystkich danych. **Oba polecenia** mogą również zwrócić short count, gdy nastąpią jakieś **ograniczenia buforowania lub duże opóźnienia sieci**. Datagramy IP, UDP i TCP przechowują w nagłówku informacje o długości danych oraz bity detekcji błędów. **Na podstawie tego możemy sprawdzić, czy otrzymany datagram dotarł w pełni**, czy ucięty oraz możemy obliczyć sumy kontrolne, a więc pewne liczby uzyskane za pomocą jakiegoś algorytmu do zapewnienia integralności danych. > SOCK_SEQPACKET sockets employ the same system calls as SOCK_STREAM sock- ets. The only difference is that read(2) calls will return only the amount of data requested, and any remaining in the arriving packet will be discarded. **EINTR** zwracaany jest gdy funkcja zostanie przerwana sygnałem w trakcie działania. > A read from a slow device (i.e. one that might block for an arbitrary amount of time) was interrupted by the delivery of a signal before any data arrived. Trzeba być przygotowanym gdyż podczas operacji na gniazdach potencjalnie możemy długo oczekiwać na rezultat. W zależności od ustawionych flag i obsługi przerwać, polecenie które zostało przerwane może zostać uruchomione ponownie, lub zakończyć się -1 i błędem **EINTR** - **interapted**. Gdy klient spóbuje zapisać do gniazda połączenia zamkniętego już przez serwer, będzie ponawiane kilukrotnie wysyłanie segmentu. Jeśli serwer nie zacznie działać(Nie dostaniemy żadnej odpowiedzi) to błędem będzie **ETIMEDOUT**. Gdy pośredni router ustali, że nie można osiągnąć serwera, a protokół ICMP(ang. Internet Control Message Protocol) prześle o tym wiadomosc, to błędem będzie **EHOSTUNREACH** lub **ENETUNREACH**. Może też się zdażyć SIGPIPE, ale to chyba jak coś jest zepsute, np połączenie tcp? **setsockopt(2)** służy do ustawiania opcji gniazda. flaga **SO_REUSEADDR** pozwala nierezerwować adresu adersu ```c= SO_REUSEADDR Indicates that the rules used in validating addresses supplied in a bind(2) call should allow reuse of local addresses. For AF_INET sockets this means that a socket may bind, except when there is an active listening socket bound to the address. When the listening socket is bound to INADDR_ANY with a specific port then it is not possible to bind to this port for any local address. Argument is an integer boolean flag. ``` Opcja służy do zezwolenia na ponowne użycie adresu. Mamy następujący przypadek brzegowy. Restartujemy serwer by wprowadzić zmiany, Gdy serwer uruchamia się ponownie bind() zwraca błąd gdyż ten sam adres jest wciąż zajęty(zwykle przez **30 sekund** oczekuje na latające opuźnione pakiety, po tym czasie uważa się, że wszystkim pakietom wygasła ważność). Jednak my chielibyśmy móc odrazu użyć tego adresu, a jakieś latające pakiety można **łatwo rozróżnić po czasie wysłania od tych które mają dopiero nadejść**. Zatem używamy tej falgi. ## zad 7 ![](https://i.imgur.com/Lxpmw8Z.png) ```c= for (int i = 0; i < MAXCLIENTS; i++) { pid = client[i].pid; if (pid) { if (Waitpid(pid, NULL, WNOHANG)) { // jeśli proces jeszcze zyję to zwróć 0 safe_printf("[%d] Disconnected!\n", pid); delclient(pid); } } } sigset_t mask; sigaddset(&mask, SIGCHLD); while(nclients > 0){ sigsuspend(&mask); } safe_printf("\nServer received %ld bytes\n", nread); _exit(0); ``` > strace -e trace=signal xterm -e ’./echoserver 7777’