Gniazda
Transkrypt
Gniazda
Gniazda Gniazda Historycznie tworzenie sieci w Uniksie szło w dwóch kierunkach: Gniazda systemu BSD (ang. Sockets) Interfejs Warstwy Transportowej TLI (ang. Transport Level Interface) W dokumentacji X/Open podrozdział poświęcony oprogramowaniu sieci nazywany jest XTI i obejmuje zarówno gniazda jak i TLI. TLI i gniazda bazują na podobnych ideach, z tym, że TLI jest o wiele bardziej skomplikowane. My będziemy zajmować się interfejsem gniazd. Główną ideą gniazda jest dostarczenie sposobu/mechanizmu komunikacji międzyprocesowej, wystarczająco ogólnej, aby pozwolić na dwukierunkowe komunikaty między dwoma procesami, niezależnie od tego, czy znajdują sie na tym samym komputerze, czy też na różnych. 2 Rodzaje połączeń Istnieją dwa rodzaje komunikacji sieciowej: Model połączeniowy (ang. connection oriented model) lub obwód wirtualny (ang. virtual circuit); używany przez proces, do wysyłania niesformatowanego i nieprzerwanego strumienia znaków do tego samego miejsca; daje pełną interaktywność: określona kolejność komunikatów i potwierdzeń odbioru (jak rozmowa telefoniczna); implementowany za pomocą protokołu TCP (ang. Transmission Control Protocol) – protokół sterowany transmisją Model bezpołączeniowy (ang. Connectionless oriented model); proces wysyła komunikat do danego miejsca w sieci i nie czekając na potwierdzenie odbioru może wysłać kolejny komunikat do innego miejsca w sieci; model ten jest podobny do wysyłania listów: nigdy nie jesteśmy pewni czy list dotarł, ale jeśli chcemy otrzymać odpowiedź musimy podać adres zwrotny; implementowany za pomocą protokołu UDP (ang. User Datagram Protocol) – protokół datagramów/pakietów użytkownika. 3 Adresy internetowe Adresy internetowe (adresy IP) składają się z czterech liczb dziesietnych, rozdzielonych kropkami, np.: 192.168.1.254 Liczby te zawierają wystarczającą informację do określenia położenia docelowej sieci, jak i komputera docelowego w tej sieci -stąd nazwa Internet: sieć zawierająca sieci. Systemowe funkcje sieciowe Linuxa nie obsługują numerów Ip w pow. formacie. Adresy IP są przechowywane w typie in_addr_t. Funkcja: in_addr_t inet_addr(const char *ip_address); dokonuje odpowiednich konwersji. W przypadku niepowodzenia oddaje (in_addr_t) -1. Jeśli proces chce odnieść się do adresu swojego komputera, plik nagłówkowy <netinet/in.h> definiuje stałą INADDR_ANY jako skrót od lokalnego hosta w formacie in_addr_t. 4 Interfejs gniazd Ogólna struktura adresu gniazda (<sys/socket.h>): struct sockaddr{ sa_family_t sa_family; char sa_data[]; } /* rodzina adresów */ /* adres gniazda */ Powyższa struktura opisuje tzw. gniazdo ogólne, używane np. Do komuniukacji typu IPC – na tym samym komputerze. Struktura adresu gniazda przeznaczonego do komunikacji sieciowej (<netinet/in.h>): struct sockaddr_in{ sa_family_t sin_family; /* in_port_t sin_port; /* struct in_addr sin_addr; /* unsigned char sin_zero[8];/* } rodzina adresów */ numer portu */ adres IP */ wypełnienie */ 5 Punkt końcowy transportu W każdej komunikacji klient i serwer muszą ustalić swoje własne punkty końcowe transportu (ang. transport end points). Są one uchwytami do utworzenia połączenia przez sieć pomiędzy procesami. int socket(int domain, int type, int protocol); Parametr domain mówi, gdzie ma być używane gniazdo: AF_INET – komunikacja przez internet, AF_UNIX – na tym samym komputerze. Parametr type określa tryb połączenia: SOCK_STREAM – tryb połączeniowy, SOCK_DGRAM – tryb bezpołączeniowy. Parametr protocol określa, który protokół powinien być używany przez gniazdo. Zwykle ustawia się wartość 0, co oznacza TCP dla SOCK_STREAM i UDP dla SOCK_DGRAM. Funkcja zwraca całkowitą liczbę nieujemną, która jest deskryptorem pliku gniazda. Schemat procesu serwera i klienta: serwer_schemat.c klient_schemat.c 6 Nawiązanie, nasłuchiwanie, ... int bind(int sockfd, const struct sockaddr *address, size_t add_len); Funkcja wiąże prawdziwy adres sieciowy z identyfikatorem gniazda. sockfd, to deskryptor pliku gniazda zwrócony przez socket(). Drugi parametr, to wskaźnik do ogólnej struktury gniazda. Ostarni parametr zawiera wielkość faktycznie używanej struktury gniazda. Pomyślne wywołanie zwraca 0, błąd to -1, np. Gniazdo dla danego adresu już istnieje: errno = EADDRINUSE. int listen(int sockfd, int queue_size); Funkcja ustawia serwer w tryb czekania na połączenie. sockfd jak poprzednio. Serwer może ustawić w kolejce do queue_size przybywających żądań połączenia – XTI określa przenośne maksimum pięciu takich żądań. 7 Przyjmowanie Kiedy serwer od klienta otrzyma żądanie connect, musi utworzyć nowe gniazdo do obsługi tej komunikacji. Pierwsze gniazdo jest używane tylko do nawiązania komunikacji. Tworzenie drugiego gniazda odbywa się za pomocą funkcji: int accept(int sockfd, struct sockaddr *address, size_t *add_len); sockfd, to deskryptor nasłuchwanego gniazda. Po zakończeniu funkcji zwracany jest identyfikator nowego gniazda używanego do komunikacji. Parametr address wypełniony będzie informacją o kliencie, chociaż przy połączeniu ukierunkowanym na komunikację nie jest potrzebna – można address ustawić na NULL. Jeśli address != NULL, to to add_len powinna zawierać początkowo długość struktury adresu. Po powrocie z wywołania *add_len będzie zawierał ilość faktycznie skopiowanych bajtów. 8 Przyłączanie klienta Aby prosić o połączenie z procesem i komputerem serwera, klient używa funkcji systemowej: int connect(int csockfd, const struct sockaddr *address, size_t add_len); csockfd, to deskryptor pliku dla dołączanego gniazda klienta – nie ma on żadnego związku z identyfikatorem gniazda na serwerze. Parametr address, to wskaźnik do struktury zawierającej adres serwera, natomiast add_len długośc używanej struktury adresu. Schemat procesu serwera i klienta: serwer_schemat2.c klient_schemat2.c 9 Wysyłanie i odbieranie Do przesyłania danych w trybie połączeniowym służą funkcje: ssize_t recv(int csockfd, void *buffer, size_t length, int flags); ssize_t send(int csockfd, const void *buffer, size_t length, int flags); Funkcje te zachowują sie podobnie jak read i write (przy ostatnim parametrze = 0 są identyczne). Funkcja recv określa deskryptor pliku, z którego dane będą czytane, bufor, w którym powinny być umieszczone i wielkość bufora – send podobnie. Proces serwera i klienta: serwer_1.c klient_1.c 10 Zamykanie połączenia Ważne jest rozsądne działanie procesu przy nieoczekiwanym zakończeniu procesu na drugim końcu gniazda. Nie można przewidzieć, czy proces będzie próbować odczytywać czy zapisywać, gdy zdarzy sie przerwa a komunikacji. Jeśli proces próbuje wysyłać (send lub write) dane do gniazda, które zostało odłączone, otrzyma sygnał SIGPIPE. Jeśli funkcja read lub recv zwraca 0, wskazuje to na koniec pliku i koniec połączenia. Dlatego należy zawsze sprawdzać wartość zwracaną przez te funkcje. Do gniazda może być użyta funkcja systemowa close. Jeśli używane jest gnizdo typu SOCK_STREAM, jądro grawantuje, że wszystkie dane wysłane do gniazda zostaną wysłane do procesu odbiorczego. Może to spowodować wstrzymanie operacji close, aż wszystkie dane zostaną dostarczone. Jeśli gniazdo jest typu SOCK_DGRAM, zostanie zamknięte natychmiast. Proces serwera i klienta: serwer_2_close.c klient_2_close.c 11 Model bezpołączeniowy Główna różnica pomiędzy modelem połączeniowym, a bezpołączeniowym polega na tym, że w trybie bezpołączeniowym pakiety transmitowane pomiędzy klientem a serwerm będą przychodzić do miejsca przeznaczenia w nieokreślonej kolejności. Z punktu widzenia programowania w medelu bezpołączeniowym proces musi utworzyć swoje lokalne gniazdo i związać z nim (bind) swój własny adres sieciowy. Proces może wówczas używać tego gniazda jako bramy (ang gateway) do sieci. Aby wysyłać komunikat, proces musi znać adres przeznaczenia – może to być adres rozgłoszeniowy, dotyczący wielu komputerów. 12 Wysyłanie i odbieranie Jedynymi nowymi funkcjami w modelu bezpołączeniowym są: ssize_t recvfrom(int sockfd, void *message, size_t length, int flags, struct sockaddr *send_addr, size_t *add_len); ssize_t sendto(int sockfd, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, size_t dest_len); Paramatry pow. funkcji podobne są do parametrów recv i send. Ostatnie dwa parametry pomagają w bezpołączeniowej formie komunikacji. Struktura send_addr jest zapełniana informacją adresową komputera, który wysłał komunikat – dzięki temu proces odbierający będzie mógł wysłać odpowiedź. Proces serwera i klienta: serwer_udp.c klient_udp.c 13 Porównianie W obu modelach serwer musi utworzyć gniazdo i związać z nim swój lokalny adres. W modelu połączeniowym serwer musi wtedy zacząć nasłuchiwać przybywających połączeń, co nie jest konieczne w scenariuszu bezpołączeniowym – to klient ma tu więcej pracy. Z perspektywy klienta, w modelu połączeniowym klient tylko łączy się z serwerem. W modelu bezpołączeniowym klient musi utworzyć gniazdo i związać z nim swój lokalny adres. Inne funkcje systemowe są wykorzystywane do transmisji danych. Funkcje systemowe send i recv mogą być używane w obu modelach. Jednak w modelu bezpołączeniowym zwykle używane są funkcje sendto i recvfrom, co pozwala uzyskać informacje o nadawcy komunikatu i ewentualnie odpowiedzieć. 14