| UNIX(7) | Miscellaneous Information Manual | UNIX(7) |
unix - gniazda lokalnej komunikacji międzyprocesowej
#include <sys/socket.h> #include <sys/un.h>
unix_socket = socket(AF_UNIX, type, 0); error = socketpair(AF_UNIX, type, 0, int *sv);
Rodzina gniazd AF_UNIX (znana również jako AF_LOCAL) służy do wydajnej komunikacji pomiędzy procesami na tej samej maszynie. Zgodnie z tradycją, gniazda domeny uniksowej mogą być albo anonimowe (tworzone przez socketpair(2)), albo skojarzone z plikiem typu gniazda. Linux wspiera również abstrakcyjną przestrzeń nazw, niezależną od systemu plików.
Poprawne typy gniazd w domenie Uniksa to: SOCK_STREAM dla gniazd strumieniowych, SOCK_DGRAM dla gniazd datagramowych, które zachowują granice komunikatów (w przypadku większości implementacji Uniksa gniazda uniksowe są zawsze niezawodne i nie zmieniają kolejności datagramów), oraz (od wersji Linuksa 2.6.4) SOCK_SEQPACKET dla gniazd pakietów sekwencyjnych zorientowanych połączeniowo, które zachowują granice komunikatu i dostarczają komunikaty w kolejności ich wysyłania.
Za pośrednictwem pomocniczych danych można przez gniazda domeny uniksowej przekazywać do innych procesów deskryptory plików i uwierzytelnienia procesów.
Adres gniazda domeny uniksowej jest reprezentowany przez następującą strukturę:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* Ścieżka */
};
Pole sun_family zawsze zawiera AF_UNIX. W Linuksie sun_path ma rozmiar 108 bajtów, zob. też USTERKI poniżej.
Różne wywołania systemowe (np. bind(2), connect(2) i sendto(2)) przyjmują argument sockaddr_un jako wejście. Niektóre inne wywołania systemowe (np. getsockname(2), getpeername(2), recvfrom(2) i accept(2)) zwracają argument tego typu.
W strukturze sockaddr_un rozróżniane są trzy typy adresów:
offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1
Przy przypisywaniu gniazda do ścieżki powinno się przestrzegać kilku zasad w celu maksymalnej przenośności i łatwości programowania:
offsetof(struct sockaddr_un, sun_path)+strlen(addr.sun_path)+1
W różnych implementacjach różnie obsługiwane są adresy gniazd domen Uniksa, które nie przestrzegają powyższych zaleceń. Na przykład niektóre (lecz nie wszystkie) implementacje dodają kończący znak null, jeśli nie jest on obecny w przekazanej sun_path.
Przy programowaniu przenośnych aplikacji proszę wziąć pod uwagę, że niektóre implementację mają sun_path o długości zaledwie 92 bajtów.
Różne wywołania systemowe (accept(2), recvfrom(2), getsockname(2), getpeername(2)) zwracają struktury adresów gniazd. Gdy chodzi o gniazda domeny Uniksa, wartość-rezultat argumentu addrlen umieszczonego w wywołaniu powinna być zainicjowana jw. Gdy jest zwracany, argument ten jest ustawiany aby przedstawiać aktualny rozmiar struktury adresu. Wywołujący powinien sprawdzić wartość zwracaną w tym argumencie, jeśli wartość wyjściowa przekracza wartość wejściową, to nie ma gwarancji, że kończący znak null jest obecny w sun_path (zob PROBLEMY).
W implementacji Linuksa, ścieżki gniazd honorują uprawnienia katalogów, w których się znajdują. Utworzenie nowego gniazda nie powiedzie się, jeśli proces nie ma uprawnień zapisu i wyszukiwania (wykonania) w katalogu, w którym tworzone jest gniazdo.
W Linuksie, łączenie z gniazdem strumieniowym wymaga uprawnień zapisu w stosunku do tego gniazda; wysłanie datagramu do gniazda datagramowego również wymaga uprawnień zapisu gniazda. POSIX nie zabiera głosu w sprawie efektu uprawnień pliku gniazda i w niektórych systemach (np. starszych wersjach BSD) uprawnienia gniazd są ignorowane. Przenośne programy nie powinny opierać się na tym zachowaniu w celu zapewnienia bezpieczeństwa.
Przy tworzeniu nowego gniazda, właściciel i grupa pliku gniazda są ustawiani zgodnie ze zwykłymi zasadami. Plik gniazda ma włączone wszystkie uprawnienia, poza tym wyłączonymi przez proces umask(2).
Właściciela, grupę i uprawnienia ścieżki gniazda można zmienić (za pomocą chown(2) i chmod(2)).
Uprawnienia gniazd nie mają znaczenia dla gniazd abstrakcyjnych: proces umask(2) nie ma wpływu przy kojarzeniu z gniazdem abstrakcyjnym, a zmiana własności i uprawnień obiektu (za pomocą fchown(2) i fchmod(2)) nie wpływa na dostępność gniazda.
Gniazda abstrakcyjne automatycznie znikają po zamknięciu wszystkich otwartych odniesień do takich gniazd.
Przestrzeń nazw gniazd abstrakcyjnych jest nieprzenośnym rozszerzeniem systemu Linux.
Ze względów historycznych następujące opcje gniazd są podawane przy typie SOL_SOCKET, pomimo że są one specyficzne dla AF_UNIX. Można je ustawić za pomocą setsockopt(2), a odczytać za pomocą getsockopt(2), podając SOL_SOCKET jako rodzinę gniazd.
Jeśli w wywołaniu bind(2) podane zostanie addrlen równe sizeof(sa_family_t) lub opcja SO_PASSCRED gniazda była ustawiona dla gniazda nieprzypisanego do adresu, wtedy gniazdo jest automatycznie przypisywane do adresu abstrakcyjnego. Adres ten składa się z bajtu NULL, po którym następuje 5 bajtów ze zbioru znaków [0-9a-f]. W związku z tym liczba automatycznie przypisywanych adresów jest ograniczona do 2^20. (W Linuksie 2.1.15, w którym dodano możliwość automatycznego przypisywania adresów, i w kolejnych wersjach używane było 8 bajtów, a limit wynosił 2^32 adresów. Zostało to zmienione na 5 bajtów w Linuksie 2.3.15).
W kolejnych paragrafach opisano pewne szczegóły implementacji API gniazd domeny UNIX specyficzne dla Linuksa oraz cechy niewspierane.
Gniazda z domeny uniksowej nie obsługują zawiadomienia o danych autonomicznych (flaga MSG_OOB funkcji send(2) i recv(2)).
Flaga MSG_MORE funkcji send(2) nie jest obsługiwana dla gniazd domeny uniksowej.
Przed Linuksem 3.4, użycie MSG_TRUNC w argumencie flags funkcji recv(2) nie było obsługiwane dla gniazd domeny uniksowej.
Opcja SO_SNDBUF działa w przypadku gniazd domeny uniksowej, ale opcja SO_RCVBUF już nie. Dla gniazd datagramowych wartość SO_SNDBUF nakłada górny limit na rozmiar wychodzących datagramów. Limit ten jest liczony jako podwojona (patrz socket(7)) wartość opcji minus 32 bajty wymagane na informacje niebędące danymi.
Dane pomocnicze są wysyłane i odbierane za pomocą sendmsg(2) i recvmsg(2). Ze względów historycznych komunikaty pomocnicze poniższych typów są podawane przy typie SOL_SOCKET, pomimo że są one specyficzne dla AF_UNIX. Aby je wysłać, należy ustawić pole cmsg_level struktury cmsghdr na SOL_SOCKET, a pole cmsg_type na typ. Więcej informacji można znaleźć w cmsg(3).
struct ucred {
pid_t pid; /* identyfikator procesu wysyłającego */
uid_t uid; /* ident. użytkownika procesu wysyłającego */
gid_t gid; /* ident. grupy procesu wysyłającego */
};
Przy wysyłaniu danych pomocniczych za pomocą sendmsg(2), w wysłanym komunikacie można podać tylko po jednej pozycji dla każdego z powyższych typów.
Przy wysyłaniu danych pomocniczych konieczne jest przesłanie choć jednego bajta rzeczywistych danych. W Linuksie jest to wymagane do poprawnego wysłania danych pomocniczych za pomocą gniazda strumieniowego domeny uniksowej. Przy wysyłaniu danych pomocniczych za pomocą gniazda datagramowego domeny uniksowej, na Linuksie nie ma konieczności wysyłania jakichkolwiek rzeczywistych danych. Przenośne aplikacje powinny jednak również wysyłać choć jeden bajt rzeczywistych danych przy wysyłaniu danych pomocniczych za pomocą gniazda datagramowego.
Przy odbieraniu z gniazda strumieniowego, dane pomocnicze stanowią w pewien sposób barierę dla odbieranych danych. Proszę przyjąć przykładowo, że nadawca transmituje:
Przyjmijmy, że odbiorca wykonuje teraz wywołania recvmsg(2), każde z buforem o wielkości 20 bajtów. Pierwsze wywołanie otrzyma pięć bajtów danych, razem z danymi pomocniczymi wysłanymi przez drugie wywołanie sendmsg(2). Następne wywołanie otrzyma pozostałe cztery bajty danych.
Jeśli przestrzeń przydzielona do otrzymywanych danych pomocniczych jest zbyt mała, to dane pomocnicze są przycinane do liczby nagłówków mieszczących się w udostępnionym buforze (lub, w przypadku listy deskryptora pliku SCM_RIGHTS, sama lista deskryptorów pliku może zostać przycięta). Jeśli dla przychodzących danych pomocniczych nie udostępniono bufora (np. pole msg_control struktury msghdr przekazanej do recvmsg(2) wynosi NULL), to przychodzące dane pomocnicze są odrzucane. W obu tych przypadkach, flaga MSG_CTRUNC zostanie ustawiona na wartość msg.msg_flags zwróconą przez recvmsg(2).
Następujące wywołania ioctl(2) zwracają informacje w parametrze value. Poprawna składnia to:
int value; error = ioctl(unix_socket, ioctl_type, &value);
ioctl_type może przyjmować wartość:
Inne błędy mogą zostać wygenerowane przez podstawową warstwę gniazd lub przez system plików podczas tworzenia obiektu gniazda w systemie plików. Więcej informacji można znaleźć na odpowiednich stronach podręcznika.
SCM_CREDENTIALS oraz abstrakcyjna przestrzeń nazw zostały wprowadzone w Linuksie 2.2 i nie należy ich używać w przenośnych programach. (Niektóre systemy wywodzące się z BSD również wspierają przekazywanie uwierzytelnień, ale implementacje różnią się szczegółami).
W trakcie łączenia się z gniazdem mającym przypisaną nazwę pliku, tworzony jest plik specjalny gniazda w systemie plików, który musi zostać usunięty (za pomocą unlink(2)) przez wywołującego, gdy już nie będzie potrzebny. Stosuje się tu zwykła uniksowa składnia opóźnionego zamknięcia (ang. close-behind): gniazdo można skasować w dowolnym momencie, ale zostanie ono ostatecznie usunięte z systemu plików po zamknięciu ostatniego odwołania do niego.
Aby przekazać deskryptory plików lub uwierzytelnienia poprzez SOCK_STREAM trzeba wysłać/odebrać co najmniej jeden bajt niepomocniczych danych w tym samym wywołaniu sendmsg(2) lub recvmsg(2)
Gniazda strumieniowe z domeny uniksowej nie obsługują zawiadomienia o danych autonomicznych.
Przy wiązaniu gniazda z adresem, Linux jest jedną z implementacji dodających kończące null, jeśli nie poda się go w sun_path. Zwykle jest to bezproblemowe, gdy adres gniazda jest pozyskiwany będzie on o jeden bajt dłuższy niż podawany początkowo. Jest jednak jeden przypadek mogący spowodować mylące zachowanie: jeśli podany zostanie adres 108 bajtowy, bez znaku null, to dodanie znaku null spowodowałoby przekroczenie długości ścieżki poza sizeof(sun_path). W konsekwencji, przy pozyskiwaniu adresu gniazda (np. poprzez accept(2)), jeśli wejściowy argument addrlen dla pozyskiwanego wywołania jest podany jako sizeof(struct sockaddr_un), to zwrócona struktura adresu nie będzie miała kończącego null w sun_path.
Dodatkowo, niektóre implementacje nie wymagają kończącego null przy wiązaniu gniazda (argument addrlen jest używany do określenia długości sun_path), a gdy w tych implementacjach jest pozyskiwany adres gniazda, to nie ma kończącego null w sun_path.
Aplikacje pozyskujące adresy gniazd mogą posiadać (przenośny) kod do obsługi możliwości, że w sun_path nie ma kończącego null zauważając fakt, że liczba prawidłowych bajtów w ścieżce to:
strnlen(addr.sun_path, addrlen - offsetof(sockaddr_un, sun_path))
Alternatywnie, aplikacja może pozyskać adres gniazda przez przydzielenie buforu o rozmiarze sizeof(struct sockaddr_un)+1 który jest wyzerowany przed pozyskaniem. Pobierające wywołanie może określić addrlen jako sizeof(struct sockaddr_un), a dodatkowy bajt zero zapewnia, że w łańcuchu zwróconym w sun_path będzie kończące null:
void *addrp; addrlen = sizeof(struct sockaddr_un); addrp = malloc(addrlen + 1); if (addrp == NULL)
/* Obsługa błędu */ ; memset(addrp, 0, addrlen + 1); if (getsockname(sfd, (struct sockaddr *) addrp, &addrlen)) == -1)
/* obsługa błędu */ ; printf("sun_path = %s\n", ((struct sockaddr_un *) addrp)->sun_path);
Tego bałaganu można uniknąć, jeśli jest pewność, że aplikacja tworząca ścieżki gniazd przestrzega reguł opisanych powyżej rozdziale Ścieżki gniazd.
Poniższy kod demonstruje użycie gniazd pakietów sekwencyjnych do lokalnej komunikacji międzyprocesowej. Składa się z dwóch programów. Serwer czeka na połączenie z programu klienckiego. Klient wysyła każdy ze swoich argumentów wiersza poleceń w oddzielnych wiadomościach. Serwer traktuje przychodzące wiadomości jako liczby całkowite i dodaje je. Klient wysyła łańcuch polecenia "END". Serwer odsyła komunikat zawierający sumę klienckich liczb całkowitych. Klient wypisuje sumę i wychodzi. Serwer czeka na połączenie od kolejnego klienta. Aby zatrzymać serwer, klient jest wywoływany z argumentem wiersza poleceń "DOWN".
Podczas działania serwera w tle i kolejnych uruchomień klienta zarejestrowano następujące wyjście. Wykonywanie programu serwera kończy się, gdy otrzymuje on polecenie "DOWN".
$ ./server & [1] 25887 $ ./client 3 4 Wynik = 7 $ ./client 11 -5 Wynik = 6 $ ./client DOWN Wynik = 0 [1]+ Done ./server $
/*
* Plik connection.h
*/ #ifndef CONNECTION_H #define CONNECTION_H #define SOCKET_NAME "/tmp/9Lq7BNBnBycd6nxy.socket" #define BUFFER_SIZE 12 #endif // include guard (ochr. przed wielokr. przetw.)
/*
* Plik server.c
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(void) {
int down_flag = 0;
int ret;
int connection_socket;
int data_socket;
int result;
ssize_t r, w;
struct sockaddr_un name;
char buffer[BUFFER_SIZE];
/* Tworzenie gniazda lokalnego. */
connection_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (connection_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/*
* Ze względu na przenośność konieczne jest wyczyszczenie całej
* struktury, ponieważ niektóre implementacje zawierają dodatkowe
* (nieprzenośne) pola w strukturze.
*/
memset(&name, 0, sizeof(name));
/* Skojarzenie gniazda z nazwą gniazda. */
name.sun_family = AF_UNIX;
strncpy(name.sun_path, SOCKET_NAME, sizeof(name.sun_path) - 1);
ret = bind(connection_socket, (const struct sockaddr *) &name,
sizeof(name));
if (ret == -1) {
perror("bind");
exit(EXIT_FAILURE);
}
/*
* Przygotowanie do przyjmowania połączeń. Rozmiar dziennika zaległości
* ustawiony na 20. W trakcie przetwarzania jednego żądania, inne mogą
* zatem oczekiwać.
*/
ret = listen(connection_socket, 20);
if (ret == -1) {
perror("listen");
exit(EXIT_FAILURE);
}
/* To jest główna pętla do obsługi połączeń. */
for (;;) {
/* Oczekiwanie na połączenie przychodzące. */
data_socket = accept(connection_socket, NULL, NULL);
if (data_socket == -1) {
perror("accept");
exit(EXIT_FAILURE);
}
result = 0;
for (;;) {
/* Oczekiwanie na następny pakiet danych. */
r = read(data_socket, buffer, sizeof(buffer));
if (r == -1) {
perror("read");
exit(EXIT_FAILURE);
}
/* Upewnienie się, że bufor jest zakończony 0. */
buffer[sizeof(buffer) - 1] = 0;
/* Obsługa poleceń. */
if (!strncmp(buffer, "DOWN", sizeof(buffer))) {
down_flag = 1;
continue;
}
if (!strncmp(buffer, "END", sizeof(buffer))) {
break;
}
if (down_flag) {
continue;
}
/* Dodanie otrzymanego składnika. */
result += atoi(buffer);
}
/* Wysłanie wyniku. */
sprintf(buffer, "%d", result);
w = write(data_socket, buffer, sizeof(buffer));
if (w == -1) {
perror("write");
exit(EXIT_FAILURE);
}
/* Zamknięcie gniazda. */
close(data_socket);
/* Wyjście na polecenie DOWN. */
if (down_flag) {
break;
}
}
close(connection_socket);
/* Odlinkowanie gniazda. */
unlink(SOCKET_NAME);
exit(EXIT_SUCCESS); }
/*
* Plik client.c
*/ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/un.h> #include <unistd.h> #include "connection.h" int main(int argc, char *argv[]) {
int ret;
int data_socket;
ssize_t r, w;
struct sockaddr_un addr;
char buffer[BUFFER_SIZE];
/* Tworzenie gniazda lokalnego. */
data_socket = socket(AF_UNIX, SOCK_SEQPACKET, 0);
if (data_socket == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/*
* Ze względu na przenośność konieczne jest wyczyszczenie całej
* struktury, ponieważ niektóre implementacje zawierają dodatkowe
* (nieprzenośne) pola w strukturze.
*/
memset(&addr, 0, sizeof(addr));
/* Łączenie gniazda z adresem gniazda. */
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
ret = connect(data_socket, (const struct sockaddr *) &addr,
sizeof(addr));
if (ret == -1) {
fprintf(stderr, "Serwer jest wyłączony.\n");
exit(EXIT_FAILURE);
}
/* Wysyłanie argumentów. */
for (int i = 1; i < argc; ++i) {
w = write(data_socket, argv[i], strlen(argv[i]) + 1);
if (w == -1) {
perror("write");
break;
}
}
/* Żądanie wyniku. */
strcpy(buffer, "END");
w = write(data_socket, buffer, strlen(buffer) + 1);
if (w == -1) {
perror("write");
exit(EXIT_FAILURE);
}
/* Otrzymanie wyniku. */
r = read(data_socket, buffer, sizeof(buffer));
if (r == -1) {
perror("read");
exit(EXIT_FAILURE);
}
/* Upewnienie się, że bufor kończy się 0. */
buffer[sizeof(buffer) - 1] = 0;
printf("Wynik = %s\n", buffer);
/* Zamknięcie gniazda. */
close(data_socket);
exit(EXIT_SUCCESS); }
Przykłady użycia SCM_RIGHTS można znaleźć w podręcznikach cmsg(3) i seccomp_unotify(2).
recvmsg(2), sendmsg(2), socket(2), socketpair(2), cmsg(3), capabilities(7), credentials(7), socket(7), udp(7)
Tłumaczenie niniejszej strony podręcznika: Andrzej Krzysztofowicz <ankry@green.mf.pg.gda.pl>, Robert Luberda <robert@debian.org> i Michał Kułach <michal.kulach@gmail.com>
Niniejsze tłumaczenie jest wolną dokumentacją. Bliższe informacje o warunkach licencji można uzyskać zapoznając się z GNU General Public License w wersji 3 lub nowszej. Nie przyjmuje się ŻADNEJ ODPOWIEDZIALNOŚCI.
Błędy w tłumaczeniu strony podręcznika prosimy zgłaszać na adres listy dyskusyjnej manpages-pl-list@lists.sourceforge.net.
| 15 czerwca 2024 r. | Linux man-pages 6.9.1 |