Gniazda BSD¶
Gniazda BSD (BSD sockets): interfejs pomiędzy programami użytkowników a implementacją stosu TCP/IP.
API gniazd dostarcza funkcji bibliotecznych:
- funkcje ukrywają przed programistą warstwę transportową,
- dają możliwość prowadzenia komunikacji między węzłami sieci i/lub lokalnymi procesami.
Gniazdo jest plikiem specjalnym systemu operacyjnego (UNIX, GNU/Linux) tworzonym na życzenie programisty i identyfikowalnym przez deskryptor. Gniazdo obsługiwane jest przez programistę analogicznie do plików (everything is a file).
Gniazda BSD są identyfikowalne przez trójkę: <adres, nr
portu, tryb komunikacji>.
Tryb komunikacji¶
Tryby komunikacji gniazd BSD:
- połączeniowy – protokół TCP (RFC 793),
- bezpołączeniowy – protokół UDP (RFC 768).
Model systemu¶
Komunikacja przez gniazda:
- jeden z procesów (serwer) oczekuje na zgłoszenia używając gniazda,
- inny proces (klient) nawiązuje połączenie z gniazdem serwera.
Klient musi mieć informacje o adresie, porcie i protokole na którym serwer nasłuchuje. Serwer nie musi mieć informacji o kliencie – dostaje informacje po nawiązaniu komunikacji.
Typy serwerów:
- serwer iteracyjny,
- serwer współbieżny.
Przypadki użycia gniazd BSD¶
Protokół UDP¶
Klient:
- Stwórz gniazdo (
socket)- (Opcjonalnie: zwiąż gniazdo z adresem/portem klienta –
bind)- Odbieraj/wysyłaj dane do serwera (
recvfrom/sendto)- Zamknij gniazdo do komunikacji z serwerem (
close)
Serwer:
- Stwórz gniazdo (
socket)- Zwiąż gniazdo z adresem/portem serwera (
bind)- Odbieraj/wysyłaj dane od klientów (
recvfrom/sendto)- Zamknij gniazdo do komunikacji z klientami (
close)
Protokół TCP¶
Klient:
- Stwórz gniazdo (
socket)- Zwiąż gniazdo z adresem/portem serwera zdalnego (
connect)- Nawiąż połączenie z serwerem (
connect)- Odbieraj/wysyłaj dane do serwera (
read/write)- Zamknij gniazdo do komunikacji z serwerem (
close)
Serwer:
Stwórz gniazdo (
socket)Zwiąż gniazdo z adresem/portem serwera (
bind)Skonfiguruj kolejkę żądań gniazda (
listen)Obsługuj żądania od klientów (pętla):
- Czekaj na połączenie od klienta (
accept)- Stwórz gniazdo do komunikacji z konkretnym klientem (
accept)- Odbieraj/wysyłaj dane do klienta (
read/write)- Zamknij gniazdo do komunikacji z konkretnym klientem (
close)
API gniazd BSD¶
Nagłówki¶
- gniazda
<sys/types.h>,<sys/socket.h>, - pozostałe funkcje
<netinet/in.h>,<arpa/inet.h>,<netdb.h>.
Funkcje¶
Tworzenie gniazda:
int socket(int domain, int type, int protocol);
int domain: domena komunikacji,AF_INET(IPv4),AF_INET6(IPv6), etc.int type: semantyka komunikacji,SOCK_STREAM(TCP),SOCK_DGRAM(UDP), etc.int protocol: wybór protokołu z dostępnych opcji (zazwyczaj 0).- zwraca deskryptor lub
-1w wypadku błędu (nr błędu:errno)
Związanie gniazda:
int bind(int sockfd, struct sockaddr *addr, socklen_t addrlen);
int sockfd: deskryptor,struct sockaddr *addr: adres gniazda,socklen_t addrlen: rozmiaraddr,- zwraca
0lub-1w przypadku błędu.
W domenie protokołów internetowych używa się
sockaddr_inzamiastsockaddrwykonując rzutowanie(struct sockaddr*):struct sockaddr_in { u_short sin_family; // AF_INET u_short sin_port; // numer portu: 0 losuje struct in_addr sin_addr; // adres węzła char sin_zero[8] } struct in_addr { u_long s_addr; // 32 bitowy adres }
Przygotowanie gniazda na obsługę połączeń i konfiguracja kolejki żądań (serwer):
int listen(int sockfd, int backlog);
int sockfd: deskryptor,int backlog: rozmiar kolejki żądań,- zwraca:
0lub-1w przypadku błędu.
Oczekiwanie i obsługa zgłoszenia (serwer):
int accept(int sockfd, struct sockaddr *addr, socklen_t * addrlen);
int sockfd: deskryptor,struct sockaddr *addr: adres gniazda,socklen_t addrlen: rozmiaraddr,- zwraca deskryptor gniazda do komunikacji z klientem lub
-1w przypadku błędu.
Zwiażanie gniazda z serwerem (zdalnym) i nawiązanie połączenia (klient)
int connect(int sockfd, const struct sockaddr *srvaddr, socklen_t addrlen);
int sockfd: deskryptor,struct sockaddr *addr: adres gniazda,socklen_t addrlen: rozmiaraddr,- zwraca deskryptor gniazda do komunikacji z serwerem lub
-1w przypadku błędu.
Odczyt/zapis (protokół połączeniowy):
int read(int fd, void *buf, size_t count); int write(int fd, void *buf, size_t count);
int fd: deskryptor,void *buf: bufor,size_t count: rozmiar bufora.
Odczyt/zapis (protokół bezpołączeniowy):
sendto(int fd, void *buf, size_t count, int flags, const struct sockadds *dest_addr, socklen_t addrlen); recvfrom(int fd, void *buf, size_t count, int flags, const struct sockadds *dest_addr, socklen_t addrlen);
int fd: deskryptor,void *buf: bufor,size_t count: rozmiar bufora,int flags: flagi (np.MSG_DONTROUTE,MSG_DONTWAIT),struct sockaddr *addr: adres gniazda,socklen_t addrlen: rozmiaraddr,- zwraca deskryptor gniazda do komunikacji z serwerem lub
-1w przypadku błędu.
Zamknięcie połączenia/gniazda:
int close(int sockfd);
int sockfd: deskryptor.
Funkcje pomocnicze¶
Dla protokołów TCP/IP przyjęto standard określający sieciowy porządek bajtów. Niektóre parametry funkcji interfejsu gniazd należy podawać zgodnie ze standardem. Funkcje do konwersji dla liczb 16 i 32 bitowych.
// host to network, long uint32_t htonl(uint32_t hostlong); // host to network, short uint16_t htons(uint16_t hostshort); // network to host, long uint32_t ntohl(uint32_t netlong); // network to host, short uint16_t ntohs(uint16_t netshort);
Zamiana adresu IP zapisanego w formacie dziesiętnym (XXX.YYY.ZZZ.VVV) jako string na jego reprezentację sieciową:
in_addr_t inet_addr(const char *cp:); int inet_aton(const char *cp, struct in_addr *inp);
Zmiana adresu IP na nazwę domenową i vice versa:
struct hostent *gethostbyaddr(const char *addr, int len, int type); struct hostent *gethostbyname(const char *name);
Adres zdalnego komputera (klienta) połączonego do gniazda:
int getpeername(int sockfd, struct sockaddr *name, socklen_t *namelen);
Informacje o protokole/usłudze na podstawie nazwy, korzystając z
/etc/protocols/lub/etc/services(odpowiednio).struct protent *getprotobyname(const char *name); struct servent *getservbyname(const char *name);
Inicjalizacja struktur¶
int my_fd = socket(PF_INET, SOCK_STREAM, 0);
struct sockaddr_in my_sockaddr;
my_sockaddr.sin_family = AF_INET;
my_sockaddr.sin_addr.s_addr = INADDR_ANY; // bind to all available interfaces
my_sockaddr.sin_port = htons(9001);
int result = bind (my_fd, (struct sockaddr *) &my_sockaddr, sizeof (my_sockaddr));
my_sockaddr.sin_addr.s_addr = inet_addr("150.254.130.150");
int result = inet_aton("150.254.130.150", &my_sockaddr.sin_addr);
Socat¶
# client
socat - udp:localhost:9001
socat - tcp:localhost:9001
# echo server
socat udp-listen:9001 pipe
socat tcp-listen:9001 pipe
# reply server
while 1
do
socat udp-listen:9001 system:'read msg; echo pong'
socat tcp-listen:9001 system:'real msg; echo pong'
done
# one-line reply server
socat tcp-listen:9001,fork,reuseaddr system:'read msg; echo pong'
Zadania¶
1. Napisz program-klient który wysyła na wskazany serwer napis i wypisuje na ekran odpowiedź serwera. Przesyłana wiadomość, adres serwera oraz port usługi na serwerze są podawane przez argumenty/parametry programu. Klient i serwer używają protokołu bezpołączeniowego (UDP).
- J/w, TCP.
3. Napisz program-serwer iteracyjny echo który odczytuje wiadomości
wysyłane przez klientów. Adres serwera oraz port usługi są podawane przez
argumenty/parametry programu. Serwer używa protokołu bezpołączeniowego (UDP).
- J/w, TCP.
5. Napisz system złożony z klienta i serwera iteracyjnego maze. Serwer
dysponuje mapą o wymiarach 64x64 jednostek, która zawiera 4 rodzaje pól: 1 pole
startowe, 1 pole końcowe, i dowolne liczby pól pustych i ścian. Mapa jest
wczytywana z pliku określonego przez argument/parametr serwera. Klient
nawiązuje połączenie z serwerem. Początkowo awatar klienta znajduje się na polu
startowym, jego celem jest przesunąć go na pole docelowe. Klient może wysłać do
serwera wiadomości up, down, left, right w celu przesunięcia
awatara w określonym kierunku. Serwer przesuwa awatara, jeśli jest to możliwe i
w odpowiedzi odsyła klientowi informacje o tym w jakich kierunkach awatar może
być przesunięty z nowej lokalizacji. Jeśli awatar klienta znajuje się na polu
końcowym, serwer informuje o tym klienta i kończy połączenie. Porty i adresy są
podawane przez argumenty/parametry programów.
6. Napisz system złożony z klienta i serwera iteracyjnego, gdzie klient wysyła do serwera informacje osobowe, a serwer dopisuje je na koniec pliku \(D\) w formacie YAML. Każdy rekord danych osobowych zawiera: imię, nazwisko, wiek, i adres zamieszkania. Opcjonalnie każdy rekord może zawierać także nr telefonu i adres email. Porty, adresy, oraz ścieżka do pliku \(D\) są podawane przez argumenty/parametry programów.
7. W serwerze TCP z zadania 4 zmień rozmiar bufora nadawczego jak poniżej i przestaw gniazdo w tryb nieblokujący. Zaobserwuj działanie serwera przy wysłaniu wiadomości o rozmiarze większym niż 4096 bajtów.
int buffsize = 1024;
setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize));
Linki¶
- Skrypt SK2: Wprowadzenie do gniazd BSD
- Przykłady