tworzenienazwy (3)





9.1.6 Tworzenie i nazwy gniazd



Do spisu tresci tematu 9
9.1.6 Tworzenie i nazwy gniazd

Spis tresci

Tworzenie gniazd
Funkcja socket(),
Funkcja socketpair(),

Nazwy gniazd,
Struktury przechowujace nazwy gniazd,
Dziedzina Internetu,
Dziedzina Unixa,

Ustanawianie nazwy gniazda - funkcja bind(),
Pobieranie nazw gniazd
getsockname()
getpeername()


Bibliografia



Tworzenie gniazd
Uzytkownik identyfikuje gniazda za pomoca deskryptorow (analogicznie jak
pliki lub obiekty IPC). Zatem funkcje tworzace gniazdo zwracaja deskryptor, wykorzystywany pozniej przy kazdorazowym
odwolywaniu sie do gniazdka.


Funkcja socket()

Funkcja tworzy nowe gniazdo.


DEFINICJA: int socket (int family, int type, int protocol);
WYNIK: deskryptor gniazda gdy gniazdo zostanie utworzone,
-1 gdy blad: errno =
EINVAL (bledne dane)
EPROTONOSUPPORT (wartosci typu i protokolu nie sa dozwolone
w podanej dziedzinie)
EMFILE (pelna tablica deskryptorow procesu)
ENFILE (pelna systemowa tablica plikow)
EACCES (brak uprawnien do utworzenia gniazda o podanych
parametrach)
ENOSR (brak zasobow systemowych (wolnego i-wezla))
ENOBUFS (brak pamieci na bufory gniazda)



Argument family oznacza domene komunikacyjna (czyli rodzine
protokolow/adresow - stala AF_XXX(PF_XXX)) w jakiej bedzie funkcjonowac
nowo utworzone gniazdo, type okresla rodzaj
gniazda, zas protocol - protokol z jakiego gniazdo korzysta.



Omowienie rodzin protokolow, typow gniazd oraz stalych z nimi zwiazanych
znajduje sie w rozdziale 9.1.2.

Parametr protocol czesto jest determinowany przez rodzine i rodzaj
gniazda (np: family=AF_INET i type=SOCK_DGRAM implikuje
protocol=IPPROTO_UDP) w takich przypadkach podanie jako argument
protocol
wartosci 0 wymusza przyjecie domyslnego protokolu.

Ponizsza tabela wymienia relacje pomiedzy typem gniazd i protokolem (rodzina
AF_INET):




type
protocol
wlasciwy protokol

SOCK_DGRAM
IPPROTO_UDP
UDP

SOCK_STREAM
IPPROTO_TCP
TCP

SOCK_SEQPACKET
IPPROTO_TCP
TCP

SOCK_RAW
IPPROTO_ICMP
ICMP

SOCK_RAW
IPPROTO_RAW
"surowy"



W dziedzinie Unixa jedynymi dostepnymi typami gniazd sa
SOCK_STREAM i SOCK_DGRAM (podanie wartosci
SOCK_RAW nie powoduje bledu - rzeczywisty typ jest
ustawiany na SOCK_DGRAM).


Implementacja funkcji:

Najwyzszy poziom: funkcja sys_socket()

{
if (niemozlwe znalezienie w tablicy proto[] struktury operacji dla rodziny family)
/* patrz: rozdzial 9.1.2 opis struktury proto_ops */
probuj zaladowac odpowiedni modul jadra i wykonaj powyzsza czynnosc
jeszcze raz;
if (niepowodzenie)
return -EINVAL;
if (nieprawidlowe argumenty type lub protocol)
return -EINVAL;
pobierz pusty i-wezel;
ustaw w nim nastepujace pola:
i_mode = S_IFSOCK; /* znacznik: "jestem zwiazany z gniazdem" */
i_sock = 1;
i_gid, i_uid odpowiednio;
zainicjuj pola w strukturze socket (umieszczonej w polu s.socket_i) i-wezla:
state = SS_UNCONNECTED; /* inicjalny stan gniazda */
wartosci pozostalych pol oczywiste (odsylam do kodu);
zwieksz o jeden licznik globalnej ilosci gniazd w systemie;
wywolaj odpowiednia funkcji tworzenia gniazda z poziomu dziedzin;
}

Poziom dziedzin komunikacyjnych:

Po sprawdzeniu zgodnosci typu gniazda z wartoscia protocol, sprawdzeniu
praw (czy nie probujemy utworzyc gniazda surowego bez uprawnien nadzorcy) i paru innych dosc oczywistych rzeczy inicjalizowane sa struktury
sock opisujace szczegoly gniazda na poziomie dziedzin.
Polecam przestudiowanie opisu pol struktury sock - rozdzial 9.1.2 oraz kodu
ponizszych funkcyj:

Dziedzina Internetu:
(plik: net/ipv4/af_inet.c)


int inet_create(struct socket
*sock, int protocol);

Tutaj ustawiamy w strukturze sock miedzy innymi:
mtu = 576 - maksymalny element transmisji
oraz rozne pola zwiazane z protokolem TCP


Dziedzina Unixa:
(plik: net/unix/af_unix.c)


int unix_create(struct socket *sock, int protocol);

Wartosc pola mtu ustalana jest na 4096.


W obu przypadkach inicjalizowane sa nastepujace pola struktury
sock:
type - typ taki sam jak w strukturze socket,
state_change, data_ready, write_space, error_report (patrz:
rozdzial 9.1.10 o sygnalach)
kolejki: write_queue, read_queue i receive_queue
state = TCP_CLOSE
sndbuf, rcvbuf (te wartosci mozna zmienic - patrz: opcje
SO_SNDBUF i SO_RCVBUF


Funkcja socketpair()


Funkcja ta zaimplementowana jest tylko dla dziedziny Unixa, sluzy do tworzenia lacza komunikacyjnego miedzy procesami.

DEFINICJA: int socketpair(int family, int type, int protocol, int sockvec[2]);
WYNIK: 0 w przypadku sukcesu
-1 gdy blad, errno = EOPNOSUPP (protokol nie udostepnia socketpair)
EINVAL (brak zdefiniowanej funkcji
socketpair dla dziedziny)
+ kody bledow funkcji socket()


Gdy nie wystapi blad, do wektora sockvec jest zapisana para deskryptorow gniazd strumieniowych dziedziny Unixa. Otrzymujemy w ten sposob lacze strumieniowe (ang. stream pipe), ktore od lacza komunikacyjnego IPC rozni sie mozliwoscia dwustronnej komunikacji.


Implementacja

Na najwyzszym poziomie - funkcja sys_socketpair():

{
utworz pierwsze gniazdo (za pomoca socket() - w razie bledu zwraca jego kod);
jesli gniazdo nie udostepnia operacji "dziedzinowej" sockatpair (ops->socketpair == NULL)
return -EINVAL;
utworz drugie gniazdo;
wywolaj funkcje socketpair z poziomu dziedzin;
polacz gniazda na poziomie struktur (wskaznikiem conn);
ustaw stan obu gniazd na SS_CONNECTED;
wpisz deskryptory gniazd do tablicy sockvec[];
return 0;
}



Na poziomie dziedzin innych niz Unix odpowiednie funkcje zwracaja jedynie
-EOPNOSUPP.
Funkcja int unix_socketpair(struct socket *a, struct socket *b) dziala nastepujaco:

{
polacz gniazda na poziomie struktur sock (wskaznikiem protinfo.other)
ustaw stan gniazda na TCP_ESTABILISHED
}






UWAGI:
Lacza strumieniowe utworzone za pomoca socketpair() moga
sluzyc do przekazywania deskryptorow plikow (tej mozliwosci nie daja
mechanizmy IPC). Opis mechanizmu przekazywania deskryptorow i specjalnej
struktury cmsghdr z nim zwiazanej - w rozdziale 9.1.2.
Poslugujac sie funkcjami socketpair() oraz bind() mozemy stworzyc strumieniowe lacze nazwane.



Nazwy gniazd

Mowiac o gniazdkach, czesto uzywamy zamiennie pojec nazwy i adresu. Slowo adres moze jednak byc
mylace w odniesieniu do dziedziny Unixa, gdzie nie korzystamy z
rzeczywistych adresow sieciowych, ale identyfikujemy gniazda sciezkami plikowymi.


Struktury przechowujace nazwy gniazd
gniazd

Wiele funkcji dzialajacych na gniazdkach wymaga podania struktury
reprezentujacej adres gniazda.
Jej definicja znajduje sie w pliku naglowkowym
include/linux/socket.h:

struct sockaddr
{
unsigned short sa_family; /* rodzina adresow, AF_xxx */
char sa_data[14]; /* co najwyzej 14 bajtow addresu wlasciwego */
/* protokolu */
};

Zawartosc ostatnich 14 bajtow powyzszej struktury jest interpretowana
odpowiednio od rodzaju adresu.


Dziedzina Internetu
W pliku include/linux/in.h
znajduja sie nastepujace definicje
struktur przechowujacych adresy Internetowe:


/* Adres internetowy */
struct in_addr {
__u32 s_addr; /* 32 bitowy id.sieci/id.stacji */
};



/* Struktura opisujaca adres Internetowy (IP) dla gniazd. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
short int sin_family; /* Rodzina adresow */
unsigned short int sin_port; /* Numer portu */
struct in_addr sin_addr; /* Adres internetowy */

/* Nieuzywany kawalek struktury sockaddr */
unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};



Dziedzina Unixa
W pliku naglowkowym
include/linux/un.h
znajdziemy nastepujace definicje:

#define UNIX_PATH_MAX 108 /* Maksymalna dlugosc sciezki - */
/* nazwy gniazda w dziedzinie Unixa */

/* Struktura opisujaca adres w dziedzinie Unixa. */
struct sockaddr_un {
unsigned short sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* nazwa sciezki */
};



We wszystkich funkcjach wymagajacych podania adresu gniazda, podajemy
wskaznik do odpowiedniej struktury (sockaddr_in,
sockaddr_un lub innej) zrzutowany na wskaznik do struktury ogolnej
sockaddr i uzupelniamy go o rozmiar struktury wlasciwej dla
protokolu.


Ustanawianie nazwy gniazda - funkcja
bind()

Gniazda tworzone za pomoca funkcji socket() sa anonimowe. Aby do gniazda
mogly dotrzec jakies dane, wymagane jest przypisanie mu jednoznacznie
identyfikujacej go nazwy (ang. binding), czyli adresu sieciowego komputera i numeru portu
(w dziedzinie Internetu), badz nazwy sciezkowej (dziedzina Unixa).
Do tego celu sluzy nastepujaca funkcja.


DEFINICJA: int bind(int fd, struct sockaddr *my_addr, int addrlen)
WYNIK: 0 w przypadku sukcesu
-1, gdy blad: errno = [bledy zglaszane na najwyzszym poziomie]
EBADF (fd nie jest poprawnym deskryptorem)
ENOTSOCK (fd nie jest deskryptorem gniazda)
EADDRINUSE (adres juz zarezerwowany
przez inne gniazdo)
EINVAL (zla wartosc addrlen jako dlugosci adresu danej dziedziny,
gniazdo juz ma przypisany adres)

[bledy specyficzne dla AF_INET]
EADDRNOTAVAIL (podany adres nie jest osiagalny)
EACCES (brak praw do uzywania podanego adresu,
np. proba uzycia zastrzezonego numeru portu
przez zwyklego uzytkownika)

[bledy specyficzne dla AF_UNIX]
EINOMEM (brak pamieci na adres)





Parametry: fd - deskryptor gniazda, my_addr - wskaznik na strukture adresu odpowiednia dla rodziny
protokolow, do ktorej nalezy gniazdo, addrlen -
rozmiar tej struktury.


Implementacja funkcji:


Najwyzszy poziom: (funkcja sys_bind())


{
if (fd nie jest poprawnym deskryptorem)
return -EBADF;
if (deskryptor nie wskazuje na gniazdo)
return -ENOTSOCK;
wywolaj bind() z poziomu dziedzin;
}



Poziom dziedzin:

Dziedzina Internetu:
(plik: net/ipv4/af_inet.c)


int inet_bind(struct socket *sock, struct sockaddr *uaddr, int addr_len)

(sk oznacza wskaznik na strukture sock opisujaca
gniazdo).

{
if (zdefiniowana operacja bind na poziomie protokolow)
wywolaj ja;
else {
if (stan gniazda != TCP_CLOSE ||
addr_len < sizeof(struct sockaddr_in)) return -EINVAL;
/* Sprawdzanie numeru portu */
if (podany numer portu == 0)
znajdz wolny numer portu;
if (podany numer portu < PROT_SOCK a proces nie ma praw nadzorcy)
return -EACCES;
}

wywolaj funkcje ip_chk_addr(adres) (zdefiniowana w pliku
net/ipv4/devinet.c) ktora sprawdza, czy podany adres jest
rzeczywiscie adresem naszego komputera, czy tez jest adresem podsieci
(uzywanym do broadcastingu lub multicastingu);

jesli aktualny proces ma uprawnienia nadzorcy - pozwol mu przypisac
gniazdu dowolny adres, zwykly uzytkownikowi moze tylko ustawic adres
lokalny ( blad: EADDRNOTAVAIL );

if (multi(broad)casting)
sk->saddr = 0; /* dajemy znac urzadzeniu sieciowemu */
else
sk->saddr = uaddr->sin_addr.s_addr;

przechodz po liscie umieszczonej pod indeksem
numer portu mod SOCK_ARRAY_SIZE-1 w tablicy haszujacej sock_array;

if (na liscie jest gniazdo o tym samym numerze portu bez ustawionej flagi reuse,
lub nasze gniazdo nie ma ustawionej flagi reuse)
return -EADDRINUSE;

ustaw nastepujace pola:
dummy_th.source = numer portu; (dummy_th jest neglowkiem tcp)
daddr = 0;
dummy_th.dest = 0;

}



UWAGI:

W interesujacych nas protokolach (TCP,UDP) pole bind
struktury proto jest ustawione na NULL (patrz opis struct proto
w rozdziale 9.1.2), zatem nie bedzie wywolywania bind() na
poziomie protokolow.



W strukturze sockaddr_in podajemy numer portu. W pliku include/net/sock.h
znajdziemy definicje stalej:

#define PROT_SOCK 1024;

Porty o numerach < PROT_SOCK moga byc przydzielane tylko procesom z prawami
nadzorcy.


Jesli utworzymy nowe gniazdo w protokole TCP i przypiszemy mu za pomoca bind() ustalony numer portu
wkrotce po zamknieciu gniazda korzystajacego z tego
samego numeru portu (np. przerywajac i wznawiajac dzialanie programu
wykorzystujacego ustalony numer portu), to bind() zwroci
EADDRINUSE.

Dlaczego sie tek dzieje ?
Otoz po zamknieciu gniazda przy pomocy
close() gniazdo nie jest natychmiast usuwane z
tablicy haszujacej gniazd (opis struktur danych - rozdzial 9.1.2), tylko
pozostaje w niej, zmieniajac jedynie swoj stan na TCP_TIME_WAIT.
Podczas przesylania danych zdarza sie bowiem, ze przesylany komunikat
zostanie uznany za zaginiony, a w rzeczywistosci bedzie
przetrzymywany przez jakis router pomiedzy stacja zrodlowa a docelowa.
Przed wyslaniem komunikatu w dalsza droge polaczenie moze zostac
zamkniete przez strone oczekujaca na ,,zaginiony'' pakiet.
Jesli natychmiast zostanie stworzone gniazdo
na tym samym porcie, co przed chwila zamkniete,
to moze sie zdarzyc, ze zawieruszony komunikat dotrze do
nowego gniazda. Dlatego tez standartowo po stronie zamykajacej polaczenie
gniazdo pozostaje w stanie TCP_TIME_WAIT przez czas okreslony stala
(zdefiniowana w pliku: include/net/tcp.h)
#define TCP_TIMEWAIT_LEN (60*Hz) /* Czyli 60 sek. */

(w innych systemach od 20 sek. do 4 min.) W czasie pobytu gniazda w stanie
TCP_TIME_WAIT ignorowane sa nadchodzace komunikaty.


Istnieje jednak mozliwosc wymuszenia przydzielania gniazdu numeru
zablokownego portu, opisana w rozdziale 9.1.5 o opcjach gniazd (opcja SO_REUSEADDR). Uzywanie tej
opcji moze byc jednak niebezpieczne - narazamy sie na otrzymanie smieci z powodow
opisanych powyzej.



Wywolanie funkcji bind() jest obowiazkowe dla procesu - serwera. Klient z
reguly nie musi bezposrednio wywolywac bind() - proba nawiazania polaczenia
(connect()) w protokole bezpolaczeniowym badz wyslania danych automatycznie przypisze
gniazdu klienta efemeryczny numer portu i dostarczy go serwerowi. Automatyczne odszukanie
wolnego numeru portu wykonuje (dla gniazda Internetowego)
funkcja int inet_autobind(struct sock* sk);
Funkcja ta nie robi nic, jesli gniazdo ma juz przydzielony numer portu. W
przeciwnym przypadku szuka wolnego numeru portu przegladjac zbior wszystkich
gniazd umieszczonych w tablicy haszujacej sock_array[].




Dziedzina Unixa (plik:net/unix/af_unix.c)


static int unix_bind(struct socket *sock, struct sockaddr *uaddr, int addrlen)



{
if (nazwa gniazda != NULL || zla wartosc addrlen)
return -EINVAL;
zakoncz NULL'em podana nazwe sciezki;

if(przydzielony i-wezel w filesystemie dla gniazda)
return -EINVAL; /* Mamy juz przydzielona nazwe */

zapisz nazwe sciezki w polu protinfo.af_unix.name struktury sock;
utworz plik specjalny o podanej nazwie; /* funkcja mknod */
znajdz odpowiadajacy mu i-wezel; /* funkcja namei */
umiesc wskaznik na utworzony i-wezel w polu protinfo.af_unix.inode;
return 0;
}


UWAGA:


W dziedzinie Unixa mozliwosc automatycznego nazywania gniazda nie istnieje,
gdyz gniazda identyfikowane sa nazwami sciezek. Zlecenie jadru
automatycznego tworzenia nazwy dla gniazda mogloby spowodowac efekty uboczne (np. proba tworzenia
pliku w katalogu bez praw dostepu lub niejednoznacznosc nazw).


Pobieranie nazw gniazd



Funkcja getsockname()

Przekazuje nazwe zwiazana z gniazdem poprzez asocjacje. Dzieki niej mozna
poznac numer portu automatycznie przydzielony prez system.

DEFINICJA: int getsockname(int fd, struct sockaddr *sockaddr, int *sockaddrlen)
WYNIK: 0 w przypadku sukcesu
-1 gdy blad: errno = EBADF (fd nie jest deskkryptorem)
ENOTSOCK (deskryptor fd nie wskazuje na gniazdo)



Adres zwiazany z gniazdem jest zapisywany pod adresem
pamieci przekazanym jako sockaddr (musi byc uprzednio
zaalokowane miejsce!). Pod adresem
sockaddrlen zapisana zostaje wielkosc struktury przechowujacej
adres.

Implementacja:

Jest oczywista. Dzialanie funkcji sprowadza sie do zwrocenia
danych zapisanych w polach struktury
sock:

W dziedzinie Internetu adres przechowywany jest w polach rcv_addr i
saddr (moze go tam nie byc jesli gniazdo jest dopiero co
stworzone, wtedy pobierany jest adres komputera z warstwy IP). Numer portu
zapisany jest w polu dummy_th.source.

W dziedzinie Unixa lancuch bedacy nazwa gniazda pamietany jest w polu
protoinfo.af_unix.name struktury sock.







Funkcja getpeername()
Zwraca nazwe gniazda procesu partnera, ktory jest polaczony z danym gniazdem.


DEFINICJA: int getpeername(int fd, struct sockaddr *peeraddr, int *peeraddrlen)
WYNIK: 0 w przypadku sukcesu
-1 gdy blad: errno = EBADF (fd nie jest deskkryptorem)
ENOTSOCK (deskryptor fd nie wskazuje na gniazdo)
ENOTCONN (brak polaczenia)


Analogicznie jak w funkcji getsockname() parametr
fd oznacza deskryptor gniazda, zas peeraddr i
peeraddrlen - adresy pod ktorymi beda zapisane: adres partnera
i dlugosc adresu.


Implementacja:
Dzialanie funkcji sprowadza sie do sprawdzenia, czy gniazdo jest w stanie polaczenia,
(jesli nie - sygnalizowany jest blad: ENOTCONN), a nastepnie
zwrocenia wartosci przechowywanych w strukturze
sock

W dziedzinie Internetu numer portu procesu odleglego przechowywany
jest w polu dummy_th.dest,
zas jego adres w polu daddr. (Wartosci tych pol sa ustawiane
podczas nawiazywania polaczen).

W dziedzinie Unixa pobieramy nazwe gniazdka partnera odwolujac sie do
niego bezposrednio (pole protinfo.af_unix.other struktury
sock wskazuje na gniazdo, z ktorym jestesmy polaczeni).



Bibliografia


Pliki zrodlowe Linuxa:


include/linux/net.h
(struktury: socket, proto_ops oraz definicje stalych)

include/net/sock.h
(definicje struktur sock i proto)

net/socket.h (definicja struktury sockaddr)
net/socket.c (implementacja funkcji systemowych - najwyzszy poziom)
net/ipv4/af_inet.c (implementacja funkcji systemowych - dziedzina Internetu)
net/unix/af_unix.c (implementacja funkcji systemowych - dziedzina Unixa)




W. Richard Stevens: "Programowanie
zastosowan sieciowych w systemie Unix"
M. Gabassi, B. Dupouy: "Przetwarzanie rozproszone w systemie UNIX"
Dokumentacja Linuxa 2.0.0 - strony man oraz pliki info
Vic Metcalfe, Andrew Gierth: "Programming sockets in C - FAQ"





Autor: Piotr Walczuk








Wyszukiwarka

Podobne podstrony:
Brand Equity czyli rynkowe efekty tworzenia marki
tworzenie marki
tworzenie aplikacji w jezyku java na platforme android
Pinnacle Studio 11 plus tworzenie keygena
Tworzenie biznesplanu dla bystrzakow biznby
Etapy tworzenia prezentacji
Tworzenie aplikacji okienkowych (programowanie)
PHP i Oracle Tworzenie aplikacji webowych od przetwarzania danych po Ajaksa
Tworzenie stron WWW Ćwiczenia praktyczne Wydanie III

więcej podobnych podstron