11.18 UNIX-Domain-Sockets (IPC)
Die UNIX-Domain-Sockets sind eigentlich keine wirklichen »Sockets« im Sinne der Netzwerkprogrammierung und gehören eigentlich ins Kapitel der Interprozesskommunikation (IPC), da diese Sockets nur für den lokalen Rechner verwendet werden können. Aber um eben die UNIX-Domain-Sockets verwenden und vor allem verstehen zu können, benötigt man eben auch das Wissen zu den Sockets allgemein – daher können Sie dieses Kapitel als nachgereichtes IPC-Kapitel verstehen.
Die Verwendung von UNIX-Domain-Sockets als IPC ist recht beliebt, da diese recht einfach und flexibel angewandt werden können. Statt IP-Adressen werden zu Kommunikation zwischen den Socket-Endpunkten Dateien verwendet, die im Dateisystem angelegt werden, wenn ein Socket an eine Datei gebunden wird. Gewöhnlich werden die UNIX-Domain-Sockets als Stream-Schnittstelle verwendet, obgleich auch hier eine Datagramm-Schnittstelle möglich ist.
Die UNIX-Domain-Sockets arbeiten verbindungsorientiert, was bedeutet, dass Sie immer erst eine Verbindung zwischen zwei Prozessen aufbauen müssen, die miteinander kommunizieren. Die Datenübertragung beider Prozesse kann im Normalfall von keinem anderen Prozess mitgelesen werden. Da hier immer eine private Verbindung zwischen den Prozessen besteht, müssen Sie bei einem Server, wenn dieser mit mehreren Prozessen gleichzeitig kommunizieren soll, für jeden Kommunikationskanal einen eigenen Filedeskriptor einrichten.
Besondere Eigenschaften von UNIX-Domain-Sockets sind, dass diese vollduplex und auch schneller als TCP-Sockets sind. Letzteres ist natürlich nur von Vorteil, wenn sich der Server und der Client auf demselben Rechner befinden. Das X-Window-System nutzt daher die UNIX-Domain-Sockets.
11.18.1 Die Adressstruktur von UNIX-Domain-Sockets
Die Struktur von UNIX-Domain-Sockets lautet sockaddr_un und ist in <sys/un.h> wie folgt definiert:
struct sockaddr_un {
uint8_t sun_len; // Länge Struktur - Nicht POSIX
sa_family_t sun_family; // PF_UNIX, AF_UNIX, PF_LOCAL...
char sun_path[104]; // Pfadname
};
Mit sun_family müssen Sie bei UNIX-Domain-Socket die Konstante PF_UNIX, AF_UNIX bzw. PF_LOCAL, AF_LOCAL setzen. sun_path hingegen muss mit einem terminierten Dateinamen (mit Pfadangabe) beschrieben werden, der für die Verbindung verwendet werden soll. Diese Datei wird vom Serverprozess beim Aufrufen der Funktion bind() angelegt. Wichtig: Existiert diese Datei bereits, so schlägt der Funktionsaufruf von bind() fehl. Daher sollte man ggf. die Datei zuvor mit unlink() immer entfernen (egal ob diese nun existiert oder nicht).
Abgesehen von dieser Struktur funktionieren die UNIX-Domain-Sockets ansonsten ähnlich wie die TCP-Sockets. Daher soll wieder ein einfaches Client-Server-Beispiel erstellt werden, das genauso funktioniert wie schon das erste Client-Server-Beispiel (mit TCP-Sockets) in diesem Kapitel, nur dass eben dieses Mal UNIX-Domain-Sockets dazu verwendet werden. Zunächst der Quellcode zum Server:
/* uds_server.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
#define UDS_FILE "/tmp/sock.uds"
int main (void) {
int create_socket, new_socket;
socklen_t addrlen;
char *buffer = malloc (BUF);
ssize_t size;
struct sockaddr_un address;
const int y = 1;
printf ("\e[2J");
if((create_socket=socket (AF_LOCAL, SOCK_STREAM, 0)) > 0)
printf ("Socket wurde angelegt\n");
unlink(UDS_FILE);
address.sun_family = AF_LOCAL;
strcpy(address.sun_path, UDS_FILE);
if (bind ( create_socket,
(struct sockaddr *) &address,
sizeof (address)) != 0) {
printf( "Der Port ist nicht frei – belegt!\n");
}
listen (create_socket, 5);
addrlen = sizeof (struct sockaddr_in);
while (1) {
new_socket = accept ( create_socket,
(struct sockaddr *) &address,
&addrlen );
if (new_socket > 0)
printf ("Ein Client ist verbunden ...\n");
do {
printf ("Nachricht zum Versenden: ");
fgets (buffer, BUF, stdin);
send (new_socket, buffer, strlen (buffer), 0);
size = recv (new_socket, buffer, BUF-1, 0);
if( size > 0)
buffer[size] = '\0';
printf ("Nachricht empfangen: %s\n", buffer);
} while (strcmp (buffer, "quit\n") != 0);
close (new_socket);
}
close (create_socket);
return EXIT_SUCCESS;
}
Jetzt noch das Beispiel zum Client:
/* uds_client.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUF 1024
#define UDS_FILE "/tmp/sock.uds"
int main (int argc, char **argv) {
int create_socket;
char *buffer = malloc (BUF);
struct sockaddr_un address;
int size;
printf ("\e[2J");
if((create_socket=socket (PF_LOCAL, SOCK_STREAM, 0)) > 0)
printf ("Socket wurde angelegt\n");
address.sun_family = AF_LOCAL;
strcpy(address.sun_path, UDS_FILE);
if (connect ( create_socket,
(struct sockaddr *) &address,
sizeof (address)) == 0)
printf ("Verbindung mit dem Server hergestellt\n");
do {
size = recv(create_socket, buffer, BUF-1, 0);
if( size > 0)
buffer[size] = '\0';
printf ("Nachricht erhalten: %s\n", buffer);
if (strcmp (buffer, "quit\n")) {
printf ("Nachricht zum Versenden: ");
fgets (buffer, BUF, stdin);
send(create_socket, buffer, strlen (buffer), 0);
}
} while (strcmp (buffer, "quit\n") != 0);
close (create_socket);
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
[ tty1 ]
$ gcc -o uds_server uds_server.c
$ gcc -o uds_client uds_client.c
$ ./uds_server
Socket wurde angelegt
[ tty2 ]
$ ./uds_client
Socket wurde angelegt
Verbindung mit dem Server hergestellt
[ tty1 ]
$ ./uds_server
Socket wurde angelegt
Ein Client ist verbunden ...
Nachricht zum Versenden: Hallo Client
[ tty2 ]
$ ./uds_client
Socket wurde angelegt
Verbindung mit dem Server hergestellt
Nachricht erhalten: Hallo Client
Nachricht zum Versenden: Hallo Server
[ tty1 ]
$ ./uds_server
Socket wurde angelegt
Ein Client ist verbunden ...
Nachricht zum Versenden: Hallo Client
Nachricht empfangen: Hallo Server
Nachricht zum Versenden: ...
11.18.2 Lokale Sockets erzeugen – socketpair()
Mit socketpair() haben Sie eine Funktion, womit Sie lokale Sockets erzeugen können:
#include <sys/types.h>
#include <sys/socket.h>
int socketpair(int domain,int typ,int protocol,int sv[2]);
Damit generieren Sie zwei unbenannte, miteinander verbundene Sockets. Für den Parameter domain muss man bei lokalen Sockets gewöhnlich AF_LOCAL bzw. PF_LOCAL oder auch AF_UNIX bzw. PF_UNIX angeben. Für protocol gibt man hier 0 und für den typ eben SOCK_STREAM oder SOCK_DGRAM an. Die Socket-Deskriptoren werden in sv[0] und sv[1] zurückgegeben. Bei Erfolg gibt diese Funktion 0, ansonsten bei einem Fehler -1 zurück. Auf den zweiten Blick dürfte Ihnen wohl eine Ähnlichkeit zur Funktion pipe() (gemischt mit socket()) aufgefallen sein. Und in der Tat kann diese Funktion ähnlich wie pipe() verwendet werden, nur mit dem entscheidenden Vorteil, dass die UNIX-Domain-Sockets vollduplex sind – Pipes hingegen sind ja nur halbduplex. Es kann also in beiden Socket-Deskriptoren gelesen und auch geschrieben werden. Hierzu ein einfaches Beispiel, das »vollduplex« und die Funktion socketpair() demonstrieren soll.
/* fullduplex.c */
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd. h.>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#define BUF 1024
int main(void) {
int sv[2], c;
char buf1[BUF], buf2[BUF];
if(socketpair(AF_LOCAL, SOCK_STREAM, 0, sv) == -1) {
perror("Fehler bei socketpair");
exit(EXIT_FAILURE);
}
printf("Eingabe machen: ");
fgets(buf1, BUF, stdin);
/* in sv[0] schreiben */
write(sv[0], buf1, sizeof(buf1));
/* aus sv[1] lesen */
c = read(sv[1], buf2, sizeof(buf2));
/* Terminieren */
buf2[c]='\0';
printf("sv[0] --> sv[1] : %s\n", buf2);
/* Jetzt das Ganze in die andere Richtung */
printf("Eingabe machen: ");
fgets(buf1, BUF, stdin);
/* in sv[1] schreiben */
write(sv[1], buf1, sizeof(buf1));
/* aus sv[1] lesen */
c = read(sv[0], buf2, sizeof(buf2));
/* Terminieren */
buf2[c]='\0';
printf("sv[1] --> sv[0] : %s\n", buf2);
close(sv[0]);
close(sv[1]);
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc -o fullduplex fullduplex.c
$ ./fullduplex
Eingabe machen: Teste socketpair zum Ersten
sv[0] --> sv[1] : Teste socketpair zum Ersten
Eingabe machen: Teste socketpair zum Zweiten
sv[1] --> sv[0] : Teste socketpair zum Zweiten
|