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