Oprogramowanie systemów równoległych i rozproszonych Gniazda
Transkrypt
Oprogramowanie systemów równoległych i rozproszonych Gniazda
Gniazda - Wstep ˛ Podstawa˛ mechanizmu wejścia-wyjścia dla sieci w systemie Unix jest abstrakcyjny mechanizm znany jako gniazda (ang. socket). Oprogramowanie systemów równoległych i rozproszonych Gniazdo należy traktować jako jako uogólnienie mechanizmu dostepu ˛ do plików udostepniaj ˛ ace ˛ jeden z końców łacza ˛ komunikacyjnego. Każdy z komunikujacych ˛ sie˛ ze soba˛ procesów tworzy po swojej stronie jedno gniazdo. Dr inż. Tomasz Olas Główna różnica pomiedzy ˛ deskryptorami plików i gniazdami polega na tym, że system operacyjny wiaże ˛ deskryptor z określonym plikiem lub urzadzeniem, ˛ natomiast gniazda można tworzyć bez wiazania ˛ ich z określonym adresem odbiorcy. [email protected] Instytut Informatyki Teoretycznej i Stosowanej Politechnika Cz˛estochowska Gniazdo jest opisywane za pomoca kombinacji trzech atrybutów: domeny adresowej, sposobu komunikacji i protokołu sieciowego. Wykład 5 – p Wykład 5 – p. 1/?? Sposób komunikacji Domena adresowa Gniazda typu SOCK_STREAM - komunikacja w modelu połaczeniowym ˛ (zwykle zwiazane ˛ z protokołem TCP warstwy transportowej) Domena adresowa określa domene˛ w której bedzie ˛ realizowana komunikacja: PF_LOCAL (PF_UNIX) - komunikacja w obrebie ˛ jednej maszyny, PF_INET - Internet, czyli używamy protokołów z rodziny TCP/IP, PF_IPX - protokoły IPX/SPX (Novell), PF_PACKET - niskopoziomowy interfejs do odbierania pakietów w tzw. surowej (ang. raw) postaci. Gniazda typu SOCK_DGRAM - komunikacja w modelu bezpołaczeniowym ˛ korzystajaca ˛ z tzw. datagramów (zwykle zwiazane ˛ z protokołem UDP warstwy transportowej) Gniazda typu SOCK_RAW - komunikacja na niskim poziomie dostep ˛ do surowych pakietów na poziomie warstwy sieciowej TCP/IP. Zamiennie używa sie˛ notacji PF_xxx (PF - Protocol Family) oraz AF_xxx (AF - Address Family). Wykład 5 – p. 3/?? Wykład 5 – p Zamkniecie ˛ gniazda Funkcja socketpair Do tworzenia gniazdek w domenie unixowej może zostać wykorzystana funkcja socketpair: Funkcje˛ close służy do zamykania gniazda i kończenia połaczenia: ˛ #include <sys/types.h> #include <sys/socket.h> int close(int fd); int socketpair(int family, int type, int protocol, int sockvec[2]); Funkcja zwraca deskryptor gniazda. family - oznacza domene˛ komunikacyjna˛ (czyli rodzine˛ protokołów adresów) w jakiej bedzie ˛ funkcjonować nowe gniazdo. W systemie Linux jedyna˛ obsługiwana˛ domena˛ jest PF_UNIX. type - określa rodzaj gniazda, fd - deskryptor gniazda, wynik: 0 - w przypadku sukcesu, -1 - jeśli wystapił ˛ bład. ˛ Przy wywoływaniu funkcji close zmniejszany jest licznik odwołań do deskryptora. Gdy proces sie˛ kończy, system operacyjny zamyka wszystkie gniazda, które były wówczas otwarte. protocol - protokół z jakiego gniazdo korzysta, sockver - gdy nie wystapi ˛ bład ˛ zostana˛ do tej zmiennej zapisana para deskryptorów gniazd w domenie Unixa. Otrzymuje sie˛ w ten sposób łacze ˛ strumieniowe z możliwościa˛ dwustronnej komunikacji. Wykład 5 – p Wykład 5 – p. 5/?? Przesyłanie danych read/write Przesyłanie danych poprzez gniazda może odbywać sie˛ z wykorzystaniem pieciu ˛ różnych funkcji systemowych: send/write, sendto/recvfrom, sendmsg/recvmsg, write/read, writev/readv. Procedury send/recv, sendto/redvfrom oraz sendmsg/recvmsg wymagaja˛ aby gniazdo było przyłaczone, ˛ gdyż nie umożliwiaja˛ podania adresu odbiorcy. wiele dodatkowe adres informacje buforów znaczniki partnera kontrolne read/write readv/writev + send/recv + sendto/recvfrom sendmsg/recvmsg + + + + + ssize_t read(int fd, void *buf, size_t count) ssize_t write(int fd, const void *buf, size_t count) fd - deskryptor, buff - adres danych do odczytania/zapisania, length - rozmiar danych do odczytania/zapisania. wynik: liczba odczytanych/zapisanych bajtów - w przypadku sukcesu, -1 - w przypadku błedu. ˛ + Wykład 5 – p. 7/?? Wykład 5 – p socketpair - przykład (1) socketpair - przykład (2) ... else { // proces macierzysty close(sockets[0]); char buf[SIZE]; read(sockets[1], buf, SIZE); std::cout << "#0: " << getpid() << "\n#0: " << buf << std::endl; #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <iostream> const int SIZE = 40; int main() { int sockets[2]; socketpair(PF_UNIX, SOCK_STREAM, 0, sockets); int pid = fork(); if (pid == 0) { // proces potomny close(sockets[1]); char buf[SIZE]; sprintf(buf, "proces potomny: %d", getpid()); write(sockets[0], buf, sizeof(buf) + 1); read(sockets[0], buf, SIZE); close(sockets[0]); std::cout << "#1: " << getpid() << "\n#1: " << buf << std::endl; } sprintf(buf, "proces macierzysty: %d", getpid()); write(sockets[1], buf, sizeof(buf) + 1); close(sockets[1]); } return 0; } Wykład 5 – p. Wykład 5 – p. 9/?? socketpair - przykład (3) Model klient serwer Przykładowy rezultat działania programu: #0: #0: #1: #1: Termin serwer odnosi sie˛ do każdego programu, który oferuje usługe˛ dostepn ˛ a˛ poprzez sieć. Serwer przyjmuje przez sieć zamówienia, wykonuje usługe˛ i zwraca wyniki zamawiajacemu. ˛ 1458 proces potomny: 1459 1459 proces macierzysty: 1458 staje sie˛ program, który wysyła zamówienie do serwera i czeka na odpowiedź. Klientem W przypadku modelu klient serwer każde połaczenie ˛ sieciowe jest tworzone przez klienta wysyłajacego ˛ żadania ˛ do stale oczekujacego ˛ na nie klienta. Gniazda używane przez procesy klienckie nazywane sa˛ gniazdami aktywnymi (one inicjuja˛ połaczenie), ˛ natomiast gnizda wykorzystywane w serwerach nazywane sa˛ analogicznie gniazdami pasywnymi. Wykład 5 – p. 11/?? Wykład 5 – p. Komunikacja połaczeniowa ˛ Tworzenie gniazd Użytkownik identyfikuje gniazda za pomoca˛ deskryptorów (wykorzystywanych przy każdorazowym odwoływaniu sie˛ do gniazda) Funkcja socket tworzy nowe gniazdo i zwraca jego deskryptor: int socket (int family, int type, int protocol); family - oznacza domene˛ komunikacyjna˛ (czyli rodzine˛ protokołów adresów) w jakiej bedzie ˛ funkcjonować nowe gniazdo, np: PF_INET - intersieć TCP/IP, PF_UNIX - system plików systemu Unix, PF_APPLETALK - itersieć firmy Apple Computer Incorporated, type - określa rodzaj gniazda, protocol - protokół z jakiego gniazdo korzysta (w przypadku 0 bedzie ˛ to domyślny protokół dla danego rodzaju gniazda). Wykład 5 – p. Wykład 5 – p. 13/?? Określanie adresu lokalnego Struktura sockaddr Poczatkowo ˛ nowo utworzone gniazdo nie jest zwiazane ˛ z żadnym adresem lokalnym ani odległym. Po utworzeniu gniazda serwer wiaże ˛ z nim adres lokalny za pomoca˛ funkcji systemowej bind: Dla domeny uniksowej: struct sockaddr_un { short sun_family; char sun_data; }; int bind(int fd, struct sockaddr *my_addr, int addrlen) fd - deskryptor gniazda, my_addr - wskaźnik na strukture˛ adresów odpowiednia˛ dla rodziny protokołów, do której należy gniazdo, addrlen - rozmiar tej struktury. /* AF_UNIX */ /* ścieżka */ Dla domeny internetowej: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; /* /* /* /* /* AF_INET */ 16-bitowy numer portu */ 32-bitowy internetowy */ adres hosta */ zarezerwowane */ }; Wykład 5 – p. 15/?? Wykład 5 – p. Serwer - przyjmowanie połacze ˛ ń (I) Serwer używa funkcji socket, bind i listen do utworzenia gniazda. Wywołanie bind wiaże ˛ gniazdo z powszechnie znanym portem protokołu, ale nie powoduje podłaczenia ˛ gniazda do żadnego odległego odbiorcy. Zamiast odległego odbiorcy podaje sie˛ adres uogólniajacy ˛ (ang. wildcard), by gniazdo mogło przyjmować połaczenia ˛ od dowolnych klientów. Po utworzeniu gniazda serwer czeka na połaczenia. ˛ W tym celu wywołuje procedure˛ systemowa˛ accept. Serwer - przyjmowanie połacze ˛ ń (II) Gdy nadchodzi żadanie, ˛ system wypełnia strukture˛ addr adresem klienta, który je zgłosił. Nastepnie ˛ system tworzy nowe gniazdo, które jest połaczone ˛ z klientem i dostarcza wywołujacemu ˛ programowi deskryptor tego gniazda. Pierwotne gniazdo nadal nie jest połaczone ˛ z żadnym konkretnym odległym klientem i pozostaje w dalszym ciagu ˛ otwarte. Dzieki ˛ temu serwer nadal może czekać na żadania ˛ nadchodzace ˛ za pośrednictwem tego gniazda. Wywołanie accept kończy sie˛ po nadejściu żadania ˛ połaczenia. ˛ Serwer może obsługiwać nadchodzace ˛ żadania ˛ po kolei, badź ˛ też współbieżnie. Wykład 5 – p. 17/?? Wykład 5 – p. accept listen Funkcja listen ustala maksymalna˛ długość kolejki klientów oczekujacych ˛ na połaczenie. ˛ #include <sys/types.h> #include <sys/socket.h> int listen(int fd, int backlog); Funkcja accept służy do oczekiwania na połaczenie ˛ - proces jest blokowany aż do momentu nawiazania ˛ połaczenia ˛ przez inny proces. W momencie nawiazania ˛ połaczenia ˛ i funkcja zwraca nowy deskryptor gniazda, który nastepnie ˛ jest używany do wymiany danych z klientem: #include <sys/types.h> #include <sys/socket.h> int accept(int fd, struct sockaddr *upeer_sockaddr, int *upeer_addrlen); fd - deskryptor gniazda, backlog - maksymalna liczba zgłoszeń połaczenia ˛ z serwerem. wynik: 0 - w przypadku sukcesu, wartość ujemna - jeśli wystapił ˛ bład. ˛ fd - deskryptor gniazda, upeer_sockaddr - wskaźnik na nazw˛e gniazda klienta (jako dodatkowy wynik) upeer_addrlen - rozmiar nazwy upeer_sockaddr (jako dodatkowy wynik) W przypadku rodziny Unixa gniazdo musi być strumieniowe i mieć dowiazan ˛ a˛ nazwe. ˛ wynik: 0 - w przypadku sukcesu, -1 - jeśli wystapił ˛ bład. ˛ W przypadku rodziny Internetu, jeśli gniazdo nie ma nazwy, to listen automatycznie je dowiaże. ˛ Wykład 5 – p. 19/?? Wykład 5 – p. Serwer - przykład (I) #include #include #include #include #include Server - przykład (II) <sys/types.h> <sys/socket.h> <sys/un.h> <unistd.h> <iostream> listen(orginal_socket, 1); sockaddr_un client_address; socklen_t client_length = sizeof(client_address); int new_socket = accept(orginal_socket, (sockaddr*)&client_address, &client_length); const char name[] = "my_socket"; char buf[255]; read(new_socket, buf, sizeof(buf)); std::cout << buf << std::endl; int main() { int orginal_socket = socket(AF_UNIX, SOCK_STREAM, 0); close(new_socket); close(orginal_socket); unlink(name); sockaddr_un server_address; server_address.sun_family = AF_UNIX; strcpy(server_address.sun_path, name); unlink(name); return 0; } bind(orginal_socket, (sockaddr*)&server_address, sizeof(server_address.sun_family) + sizeof(server_address.sun_path)); Wykład 5 – p. 21/?? Łaczenie ˛ gniazda z adresem odbiorcy Bezpośrednio po utworzeniu gniazda znajduje sie˛ ono w stanie co oznacza, że nie jest ono zwiazane ˛ z żadnym adresatem. niepołaczonym ˛ , Procedura systemowa connect trwale łaczy ˛ gniazdo z adresem odbiorcy, zmieniajac ˛ jednocześnie stan gniazda na połaczony ˛ . W przypadku gniazda odpowiadajacego ˛ niezawodnym strumieniom klient przed rozpocz˛eciem przesyłania danych za pośrednictwem gniazda musi wywołać procedure˛ connect w celu uzyskania połaczenia. ˛ Gniazda używane z usługami bezpołaczeniowego ˛ przesyłania datagramów nie wymagaja˛ podłaczania ˛ przed użyciem, ale dzieki ˛ podłaczeniu ˛ można wysyłać dane do gniazdka, nie określajac ˛ adresu odbiorcy. Wykład 5 – p. 23/?? Wykład 5 – p. connect Funkcja connect umożliwia nawiazanie ˛ połaczenia ˛ przy użyciu podanego gniazda z gniazdem nasłuchujacym: ˛ int connect(int fd, struct sockaddr *uservaddr, int addrlen); fd - deskryptor gniazda, uservaddr - struktura określajaca ˛ adres, z którym należy zwiazać ˛ wskazane gniazdo, addrlen - rozmiar adresu serwera. wynik: 0 - w przypadku sukcesu, wartość ujemna - w przypadku błedu, ˛ np: · EBADF - fd jest nieprawidłowym deskryptorem, · ENOTSOCK - fd nie jest deskryptorem gniazda, · EISCONN - jest już zrealizowane polaczenie. Wykład 5 – p. Komunikacja bezpołaczeniowa ˛ Klient - przykład #include <sys/types.h> #include <sys/socket.h> #include <sys/un.h> #include <unistd.h> #include <iostream> const char name[] = "my_socket"; int main() { int orginal_socket = socket(AF_UNIX, SOCK_STREAM, 0); sockaddr_un server_address; server_address.sun_family = AF_UNIX; strcpy(server_address.sun_path, name); connect(orginal_socket, (sockaddr*)&server_address, sizeof(server_address.sun_family) + sizeof(server_address.sun_path)); char buf[] = "hello"; write(orginal_socket, buf, sizeof(buf)); close(orginal_socket); return 0; } Wykład 5 – p. Wykład 5 – p. 25/?? Funkcje send i sendto Funkcje recv i recvfrom ssize_t send(int s, const void *buf, size_t len, int flags); ssize_t sendto(int s, const void *buf, size_t len, int flags, const struct sockaddr *to, socklen_t tolen); ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); ssize_t recv(int s, void *buf, size_t len, int flags); ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen); s - deskryptor gniazda, przez które odbieramy dane, buf - adres (wskaźnik) bufora, który bedzie ˛ zawierał dane po ich odebraniu, s - deskryptor gniazda, przez które wysyłamy dane, buf - wskaźnik do bufora, który chcemy wysłać, len - liczba bajtów bufora do wysłania, len - rozmiar bufora (maksymalna liczba bajtów, która˛ można jednorazowo odebrać), flags - dodatkowe flagi (np. codeMSG_DONTWAIT Przełacza ˛ funkcje˛ w tryb nieblokujacy), ˛ flags - dodatkowe flagi, to - wskaźnik do struktury zawierajacej ˛ adres odbiorcy, to - wwskaźnik do struktury, przez która˛ zwrócony zostanie adres nadawcy, addrlen - rozmiar struktury zawierajacej ˛ adres. addrlen - rozmiar struktury zawierajacej ˛ adres. wynik: liczba odczytanych/zapisanych bajtów - w przypadku sukcesu, -1 - w przypadku błedu. ˛ Wykład 5 – p. 27/?? Wykład 5 – p. Przykład - serwer Przykład - klient const char name[] = "my_socket"; int main() { int orginal_socket = socket(AF_UNIX, SOCK_DGRAM, 0); const char name[] = "my_socket"; int main(int argc, char** argv) { int orginal_socket = socket(AF_UNIX, SOCK_DGRAM, 0); unlink(name); sockaddr_un server_address; server_address.sun_family = AF_UNIX; strcpy(server_address.sun_path, name); bind(orginal_socket, (sockaddr*)&server_address, sizeof(server_address.sun_family) + sizeof(server_address.sun_path)); char buf[255]; while (true) { socklen_t client_length; recvfrom(orginal_socket, buf, sizeof(buf), 0, (sockaddr*)&server_address, &client_length); std::cout << buf << std::endl; } close(orginal_socket); return 0; } sockaddr_un server_address; server_address.sun_family = AF_UNIX; strcpy(server_address.sun_path, name); bind(orginal_socket, (sockaddr*)&server_address, sizeof(server_address.sun_family) + sizeof(server_address.sun_path)); sendto(orginal_socket, argv[1], sizeof(argv[1]), 0, (sockaddr*)&server_address, sizeof(sockaddr)); close(orginal_socket); return 0; } Wykład 5 – p. Wykład 5 – p. 29/?? Numer portu Adres sieciowy Każdy host w sieci ma przynajmniej dwa tylko właściwe sobie adresy: 48-bitowy adres ethernetowy (przypisywany ethernetowej karcie sieciowej przez producenta), 32-bitowy adres internetowy (tzw. numer IP). W interakcji z użytkownikiem jest on podzielony na cztery 8-bitowe liczby dziesietne ˛ separowane kropkami. Każda z tych liczb może sie˛ zawierać w przedziale od 0 do 255, choć wartości brzegowe sa˛ używane jako wartości specjalnego przeznaczenia. Wykład 5 – p. 31/?? Numer portu jest liczba˛ 16-bitowa˛ bez znaku. Jednoznacznie identyfikuje połaczenie ˛ sieciowe w ramach jednego adresu IP. Para (adres IP, numer portu) jednoznacznie określa komputer w sieci, jak również konkretny proces działajacy ˛ na tym komputerze. Wykład 5 – p. Funkcja gethostbyname (I) Funkcja gethostbyname (II) W przypadku domeny internetowej procesy musza˛ dysponować swoimi adresami hostów i numerami portów, aby mogły ze soba˛ nawiazać ˛ kontakt. Funkcja gethostbyname zwraca pełne informacje o hoście, którego nazwe˛ podano w argumentach: #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <netdb.h> struct hostent { char *h_name; char **h_aliases; int h_addrtype; int h_length; char **h_addr_list; #define h_addr h_addr_list[0] }; h_name - oficjalna nazwa hosta, h_aliases - lista aliasów, hostent* gethostbyname(const char* name); h_addrtype - typ adresu hosta, name - wskaźnik na napis zawierajacy ˛ nazw˛e hosta. h_length - długość adresu, Jeśli nazwa hosta zostanie znaleziona funkcja zwróci wskaźnik na strukture˛ hostent, w przeciwnym wypadku zwróci NULL. h_addr_list - lista adresów z serwera nazw. Wykład 5 – p. 33/?? Wykład 5 – p. gethostbyname - przykład (I) Zmiana kolejności bajtów Poszczególne typy komputerów różnia˛ sie˛ sposobem przechowywania liczb, a protokoły TCP/IP określaja˛ niezależny od maszyny standard porzadku ˛ bajtów liczb. System Unix BSD oferuje cztery procedury biblioteczne służace ˛ do konwersji miedzy ˛ porzadkiem ˛ bajtów na danym komputerze oraz sieciowym porzadkiem ˛ bajtów (<netinet/in.h>): Do zamiany liczby z reprezentacji sieciowej do reprezentacji lokalnej (network to host) służa˛ funkcje: uint16_t ntohs(uint16_t netshort); uint32_t ntohl(uint32_t netlong); Do konwersji liczb z porzadku ˛ danego komputera na porzadek ˛ sieciowy (host to network) służa˛ funkcje: uint16_t htons(uint16_t hostshort); uint32_t htonl(uint32_t hostlong); Wykład 5 – p. 35/?? hostent *host; std::string hostName; std::cout << "Podaj nazwe hosta: " << std::flush; std::getline(std::cin, hostName); host = gethostbyname(hostName.c_str()); if (host != NULL) { std::cout << "Oficialna nazwa: " << host->h_name << std::endl << "Aliasy: " << std::endl; while (*host->h_aliases) { std::cout << " " << *host->h_aliases << std::endl; ++host->h_aliases; } std::cout << "Typ adresu: " << host->h_addrtype << std::endl << "Dlugosc adresu: " << host->h_length << std::endl << "Lista adresów: " << std::endl; Wykład 5 – p. gethostbyname - przykład (II) Domena internetowa - serwer (I) #include #include #include #include #include #include #include while (*host->h_addr_list) { in_addr in; memcpy(&in.s_addr, *host->h_addr_list, sizeof(in.s_addr)); std::cout << "[" << host->h_addr_list << "] = " << *host->h_addr_list << inet_ntoa(in) << std::endl; ++host->h_addr_list; } } } <sys/types.h> <sys/socket.h> <netinet/in.h> <arpa/inet.h> <unistd.h> <iostream> "local.h" int main() { int orginal_socket = socket(AF_INET, SOCK_STREAM, 0); if (orginal_socket < 0) { perror("blad generowania"); exit(1); } sockaddr_in server_address; memset(&server_address, 0, sizeof(sockaddr_in)); server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(PORT); Przykładowy wynik działania programu: Podaj nazwe hosta: localhost Oficialna nazwa: localhost Aliasy: Typ adresu: 2 Dlugosc adresu: 4 Lista adresów: [0x804a648] = 127.0.0.1 Wykład 5 – p. Wykład 5 – p. 37/?? Domena internetowa - serwer (II) Domena internetowa - serwer (III) if (bind(orginal_socket, (sockaddr*)&server_address, sizeof(server_address)) < 0) { perror("blad dowiazania"); exit(2); } if (listen(orginal_socket, 5) < 0) { perror("blad nasluchu"); exit(3); } if (fork() == 0) { int len; const int BUFSIZE = 255; char buf[BUFSIZE]; while ((len = read(new_socket, buf, BUFSIZE)) > 0) { for (int i = 0; i < len; i++) buf[i] = toupper(buf[i]); write(new_socket, buf, len); if (buf[0] == ’.’) break; } close(new_socket); exit(0); } else close(new_socket); do { sockaddr_in client_address; socklen_t client_len = sizeof(client_address); int new_socket = accept(orginal_socket, (sockaddr*)&client_address, &client_len); if (new_socket < 0) { perror("blad akceptowania"); exit(4); } } while (true); } Wykład 5 – p. 39/?? Wykład 5 – p. Domena internetowa - klient (I) Domena internetowa - klient (II) int main(int argc, char** argv) { hostent *host = gethostbyname(argv[1]); char buf[255]; std::string line; do { std::cout << "> " << std::flush; getline(std::cin, line); sockaddr_in server_address; memset(&server_address, 0, sizeof(server_address)); server_address.sin_family = AF_INET; memcpy(&server_address.sin_addr, host->h_addr, host->h_length); server_address.sin_port = htons(PORT); write(orginal_socket, line.c_str(), line.size()+1); read(orginal_socket, buf, 255); std::cout << buf << std::endl; int orginal_socket = socket(AF_INET, SOCK_STREAM, 0); if (orginal_socket < 0) { perror("blad generowania"); exit(3); } } while (line[0] != ’.’); close(orginal_socket); exit(0); } if (connect(orginal_socket, (sockaddr*)&server_address, sizeof(server_address)) < 0) { perror("blad polaczenia"); exit(4); } Wykład 5 – p. 41/?? Lokalne i odległe adresy gniazd Wykład 5 – p. Obsługa wielu klientów Do ustalenia adresu odbiorcy, z którym jest połaczone ˛ dane gniazdo służy funkcja: Funkcje accept, send, recv, sendto, recfrom sa˛ blokujace. ˛ #include <sys/socket.h> int getpeername(int s, struct sockaddr *name, socklen_t *namelen); Aby serwer mógł obsługiwać wielu klientów należy: utworzyć dla każdego klienta oddzielny proces, obsługiwać poszczególnych klientów przy użyciu dodatkowych watków, ˛ wykorzystać funkcje˛ select. Procedura getsockname dostarcza lokalnego adresu zwiazanego ˛ z danym gniazdem: #include <sys/socket.h> int getsockname(int s, struct sockaddr *name, socklen_t *namelen); Wykład 5 – p. 43/?? Wykład 5 – p.