12. GNIAZDA BSD
Biblioteka funkcji związanych z gniazdami jest interfejsem programisty do
obsługi protokołów
komunikacyjnych. Została utworzona dla Unixa BSD, głównie w związku z
implementacją
protokołów rodziny TCP/IP. Gniazda umożliwiają komunikację zarówno między
procesami
umiejscowionymi w jednym komputerze (komunikację w dziedzinie Unixa), jak
i między procesami
w różnych komputerach (komunikację w dziedzinie Internetu). Z punktu
widzenia programisty
posługiwanie się funkcjami jest w obu przypadkach podobne - podstawowa
różnica związana jest
z adresowaniem gniazd.
W przypadku komunikacji w dziedzinie Unixa gniazda (podobnie, jak łącza
komunikacyjne) mogą
mieć nazwy i być uwidocznione w systemie plików, lub mogą być bezimienne
(nienazwane).
W przypadku komunikacji w dziedzinie Internetu gniazda są identyfikowane
poprzez adres IP
komputera oraz numer portu.
Gniazda umożliwiają łączność dwukierunkową (zarówno zapis, jak i odczyt
danych).
Biblioteka funkcji związanych z gniazdami została zaprojektowana pod kątem
tworzenia par programów
typu klient - serwer (w szczególności serwerów współbieżnych). Przesyłanie
informacji pomiędzy
klientem a serwerem może mieć charakter połączeniowy (analogia: rozmowa
telefoniczna) lub
bezpołączeniowy (analogia: wysyłanie telegramów). Abstrakcją transmisji
połączeniowej jest
strumień danych (zapewniający zachowanie kolejności przesyłanych informacji i
niezawodność ich
dostarczania). Abstrakcją transmisji bezpołączeniowej jest ciąg oddzielnych
pakietów informacji,
tak zwanych datagramów (o ograniczonej wielkości), które mogą wędrować
różnymi drogami,
docierać w zmienionej kolejności, a czasem ulegać zagubieniu lub zduplikowaniu.
klient
serwer
Protokół połączeniowy
klient
serwer
Protokół
bezpołączeniowy
Uwaga.
1) Protokół połączeniowy (szczególnie w przypadku połączenia między różnymi
komputerami) jest
wielokrotnie wolniejszy od bezpołączeniowego (potwierdzenia, sprawdzanie
sum kontrolnych ...).
2) W przypadku protokołu bezpołączeniowego programista musi sam
zabezpieczać swoje programy
przed skutkami jego zawodności.
3) Zawodność transmisji bezpołączeniowej objawia się zwykle tylko w przypadku
przesłań pomiędzy
oddzielnymi komputerami (i to nie w obrębie jednej sieci lokalnej).
Typowym przykładem protokołu połączeniowego jest TCP
(Transmission Control
Protocol)
, protokołu
bezpołączeniowego - UDP
(User Datagram Protocol)
, oba działające na bazie
(bezpołączeniowego)
protokołu niższego poziomu IP
(Internet Protocol)
.
Podstawowe funkcje operujące na gniazdach
Typowa kolejność wywoływania funkcji w przypadku transmisji połączeniowych i
bezpołączeniowych
(wg W.R. Stevensa):
serwer
socket
( );
bind
( );
listen
( );
accept (
);
read ( );
przetwarza
nie
danych
write
( );
klient
socket (
);
connect ( );
write ( );
read ( );
oczekiwanie na
uzys-
kanie połączenia
oczekiwanie na
wyniki
a) transmisja
połączeniowa
dane
wyniki
serwer
socket
( );
bind
( );
recvfrom (
);
przetwarza
nie
danych
sendto
( );
klient
socket (
);
bind ( );
sendto
( );
recvfrom
( );
oczekiwanie na
dane
oczekiwanie na
wyniki
b) transmisja
bezpołączeniowa
dane
wyniki
Gniazda zamykamy zwykle funkcją close( ) (tak, jak zwykłe pliki).
Istotnym problemem dla transmisji połączeniowej pomiędzy oddzielnymi
komputerami jest
synchronizacja pomiędzy zapisem (write) i odczytem (read). Problem ten nie
występuje dla połączeń
w dziedzinie Unixa, bo zapis i odczyt są wykonywane jako niepodzielne operacje
przez to samo jądro.
W przypadku połączenia w dziedzinie Internetu zwykle funkcja read wykonywana
jest w pętli aż do
upewnienia się przez program, że już wszystkie przekazywane dane zostały
przeczytane.
Uwaga.
Dla zapewnienia komunikacji przez gniazda procesów spokrewnionych zwykle
stosujemy funkcję
socketpair, która generuje deskryptory pary gniazd (podobnie, jak funkcja pipe) i
od razu ustala
pomiędzy nimi połączenie. Dalsze postępowanie wygląda również podobnie, jak w
przypadku łącz
nienazwanych (utworzenie procesów potomnych dziedziczących deskryptory,
zamykanie niepo-
trzebnych deskryptorów itd.).
int socketpair (int rodzina, int typ, int protokół, int deskr [2] );
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
rodzina - oznaczenie rodziny protokołów komunikacyjnych (w tym przypadku
może być tylko
PF_UNIX )
( uwaga: oznaczenie AF_UNIX jest
równoważne )
typ - oznaczenie typu gniazd: SOCK_STREAM (gniazdo strumieniowe)
lub SOCK_DGRAM (gniazdo datagramowe)
protokół - zwykle podajemy wartość 0, żeby system dobrał wartości domyślne
(TCP dla strumieni,
UDP dla datagramów)
deskr [2] - para deskryptorów gniazd (podobnie, jak dla pary łącz
nienazwanych)
Działanie: tworzy parę gniazd nienazwanych i w przypadku gniazd
strumieniowych otwiera
połączenie pomiędzy nimi.
int socket ( int rodzina, int typ, int protokół );
Zwraca: deskryptor gniazda w przypadku sukcesu;
-1 w przypadku błędu.
Znaczenie argumentów - takie samo, jak dla funkcji socketpair.
Działanie: tworzy gniazdo nienazwane podanego typu.
int bind (int deskryptor, struct sockaddr adres, int długość );
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
deskryptor - wartość zwrócona przez funkcję socket
adres - wskaźnik do struktury zawierającej adres gniazda (interpretacja
zawartości tej struktury
zależy od używanego protokołu)
długość - długość adresu gniazda (nie uwzględniając znaku pustego kończącego
łańcuch)
Działanie: robi z gniazda nienazwanego gniazdo nazwane (w przypadku
operowania w dziedzinie
Unixa umiejscawia je w strukturze plików, zaś w przypadku
operowania w dziedzinie
Internetu wiąże je z adresem IP danego komputera i numerem
portu).
Uwaga.
1) Nie ma potrzeby bezpośredniego wpisywania adresu IP własnego komputera
(można to zrobić
automatycznie).
2) Numer portu może być wpisany bezpośrednio (jest to wskazane w przypadku
serwera, którego
adres musi być ogólnie znany), lub wygenerowany automatycznie przez
wpisanie wartości 0
(jest to zwykle praktykowane w przypadku klienta).
int listen (int deskryptor, int rozmkolejki);
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
deskryptor - jak poprzednio
rozmkolejki - maksymalny rozmiar kolejki zgłoszeń klientów (typowa wartość
wynosi 5)
Działanie: tworzy kolejkę dla zgłoszeń klientów chcących połączyć się z danym
gniazdem.
Jeśli w którymś momencie kolejka przepełni się, klienci chcący nawiązać
połączenie będą
otrzymywali sygnał błędu.
int connect (int deskryptor, struct socksddr serwadres, int długość);
Zwraca: 0 w przypadku sukcesu;
-1 w przypadku błędu.
deskryptor - jak poprzednio
serwadres - wskaźnik do struktury zawierającej adres gniazda serwera (o postaci
zależnej od
używanego protokołu)
długość - długość adresu gniazda serwera
Działanie: spowodowanie (jeśli to możliwe) wpisania zgłoszenia klienta do kolejki
serwera,
a następnie nawiązanie połączenia pomiędzy gniazdem klienta a
gniazdem serwera.
int accept (int deskryptor, struct sockaddres kliadres, int długość);
Zwraca: nową wartość deskryptora gniazda w przypadku sukcesu;
-1 w przypadku błędu.
deskryptor - jak poprzednio
kliadres - wskaźnik do struktury, do której w wyniku wykonania funkcji zostanie
wpisany adres
gniazda klienta, którego żądanie połączenia oczekuje jako
najwcześniejsze w kolejce
długość - w wyniku wykonania funkcji zostaje tu wpisana długość adresu gniazda
klienta
Działanie: jeśli kolejka jest pusta, proces serwera zostaje zawieszony. Kiedy w
kolejce pojawią się
zgłoszenia, pobrane jest najwcześniejsze z nich, utworzone jest nowe
gniazdo serwera
i połączone jest z gniazdem klienta (którego adres gniazda i jego
długość są zwracane
przez drugi i trzeci argument funkcji).
int sendto (int deskryptor, char bufor, int wielkość, int flagi, strust sockaddr
adres, int długość);
Zwraca: liczbę faktycznie wysłanych bajtów w przypadku sukcesu;
-1n w przypadku błędu.
deskryptor - jak poprzednio
bufor - wskaźnik do bufora zawierającego dane do wysłania
wielkość - liczba bajtów danych
flagi - różne znaczenia (mogą na przykład nadać danym wyższy priorytet)
adres - wskaźnik do struktury zawierającej adres gniazda odbiorcy
długość - długość adresu gniazda odbiorcy
Działanie: wysyła porcję informacji zgromadzonej w buforze z gniazda o danym
deskryptorze do
gniazda o podanym adresie
int recvfrom (int deskryptor, char bufor, int wielkość, int flagi, struct sockaddr
adres, int długość);
Zwraca: liczbę faktycznie odebranych bajtów w przypadku sukcesu;
-1 w przypadku błędu.
deskryptor - jak poprzednio
bufor - wskaźnik do bufora przeznaczonego do przyjęcia danych
wielkość - rozmiar tego bufora
flagi - różne znaczenia (mogą na przykład spowodować skopiowanie zamiast
przeniesienia danych
do bufora)
adres - wskaźnik do struktury, do której w wyniku wykonania funkcji zostaje
wpisany adres gniazda
nadawcy
długość - wskaźnik do zmiennej, do której w wyniku działania funkcji zostaje
wpisana długość adresu
gniazda nadawcy
Działanie: zawiesza proces odbiorcy, jeśli nie ma żadnych danych do odebrania.
Jeśli są, pobiera do
bufora dane wysłane do gniazda o danym deskryptorze. Adres gniazda
nadawcy i jego
długość zostają zwrócone przez dwa ostatnie argumenty funkcji.