Unix Domain Socket: Verwendung von Datagrammkommunikation zwischen einem Serverprozess und mehreren Clientprozessen

Lesezeit: 9 Minuten

Benutzeravatar von BigMick
BigMick

Ich möchte unter Linux eine IPC-Verbindung zwischen mehreren Prozessen aufbauen. Ich habe noch nie UNIX-Sockets verwendet und weiß daher nicht, ob dies der richtige Ansatz für dieses Problem ist.

Ein Prozess empfängt Daten (unformatiert, binär) und verteilt diese Daten über einen lokalen AF_UNIX-Socket unter Verwendung des Datagrammprotokolls (dh ähnlich wie UDP mit AF_INET). Die von diesem Prozess an einen lokalen Unix-Socket gesendeten Daten sollen von mehreren Clients empfangen werden, die denselben Socket abhören. Die Anzahl der Empfänger kann variieren.

Um dies zu erreichen, wird der folgende Code verwendet, um einen Socket zu erstellen und Daten an ihn zu senden (der Serverprozess):

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
// buf contains the data, buflen contains the number of bytes
int bytes = write(socket, buf, buflen);
...
close(socket);
unlink(ipcFile.sun_path);

Dieser Schreibvorgang gibt -1 zurück, wobei die Fehlermeldung ENOTCONN (“Transportendpunkt ist nicht verbunden”) gemeldet wird. Ich denke, das liegt daran, dass derzeit kein empfangender Prozess auf diesen lokalen Socket lauscht, richtig?

Dann habe ich versucht, einen Client zu erstellen, der sich mit diesem Socket verbindet.

struct sockaddr_un ipcFile;
memset(&ipcFile, 0, sizeof(ipcFile));
ipcFile.sun_family = AF_UNIX;
strcpy(ipcFile.sun_path, filename.c_str());

int socket = socket(AF_UNIX, SOCK_DGRAM, 0);
bind(socket, (struct sockaddr *) &ipcFile, sizeof(ipcFile));
...
char buf[1024];
int bytes = read(socket, buf, sizeof(buf));
...
close(socket);

Hier schlägt der Bind fehl (“Adresse wird bereits verwendet”). Muss ich also einige Socket-Optionen festlegen oder ist dies im Allgemeinen der falsche Ansatz?

Vielen Dank im Voraus für alle Kommentare / Lösungen!

  • Überprüfen Sie hier auch php als Client und C als Server

    – Bernhard Ramos

    15. April 2017 um 3:26 Uhr

Adamlamars Benutzeravatar
Adamlamar

Es gibt einen Trick, um Unix Domain Socket mit Datagrammkonfiguration zu verwenden. Im Gegensatz zu Stream-Sockets (TCP- oder Unix-Domain-Sockets) benötigen Datagramm-Sockets Endpunkte, die sowohl für den Server als auch für den Client definiert sind. Wenn man in Stream-Sockets eine Verbindung herstellt, wird vom Betriebssystem implizit ein Endpunkt für den Client erstellt. Unabhängig davon, ob dies einem kurzlebigen TCP/UDP-Port oder einem temporären Inode für die Unix-Domäne entspricht, der Endpunkt für den Client wird für Sie erstellt. Aus diesem Grund müssen Sie normalerweise keinen Aufruf von bind() für Stream-Sockets im Client ausführen.

Der Grund, warum Sie „Adresse wird bereits verwendet“ sehen, liegt darin, dass Sie dem Client mitteilen, dass er sich an dieselbe Adresse wie der Server binden soll. bind() Es geht um die Behauptung der äußeren Identität. Zwei Sockets können normalerweise nicht denselben Namen haben.

Bei Datagramm-Sockets, insbesondere Unix-Domänen-Datagramm-Sockets, muss der Client dies tun bind() zu seinem besitzen Endpunkt also connect() zum Server Endpunkt. Hier ist Ihr Client-Code, leicht modifiziert, mit einigen anderen Leckereien:

char * server_filename = "/tmp/socket-server";
char * client_filename = "/tmp/socket-client";

struct sockaddr_un server_addr;
struct sockaddr_un client_addr;
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sun_family = AF_UNIX;
strncpy(server_addr.sun_path, server_filename, 104); // XXX: should be limited to about 104 characters, system dependent

memset(&client_addr, 0, sizeof(client_addr));
client_addr.sun_family = AF_UNIX;
strncpy(client_addr.sun_path, client_filename, 104);

// get socket
int sockfd = socket(AF_UNIX, SOCK_DGRAM, 0);

// bind client to client_filename
bind(sockfd, (struct sockaddr *) &client_addr, sizeof(client_addr));

// connect client to server_filename
connect(sockfd, (struct sockaddr *) &server_addr, sizeof(server_addr));

...
char buf[1024];
int bytes = read(sockfd, buf, sizeof(buf));
...
close(sockfd);

Zu diesem Zeitpunkt sollte Ihr Socket vollständig eingerichtet sein. Ich denke, theoretisch können Sie verwenden read()/write()aber normalerweise würde ich verwenden send()/recv() für Datagramm-Sockets.

Normalerweise sollten Sie nach jedem dieser Aufrufe den Fehler überprüfen und a ausgeben perror() danach. Es wird Ihnen sehr helfen, wenn etwas schief geht. Verwenden Sie im Allgemeinen ein Muster wie dieses:

if ((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) {
    perror("socket failed");
}

Dies gilt für so ziemlich alle C-Systemaufrufe.

Die beste Referenz dafür ist Stevens “Unix Network Programming”. In der 3. Auflage, Abschnitt 15.4, zeigen die Seiten 415-419 einige Beispiele und führen viele der Vorbehalte auf.

Übrigens in Bezug auf

Ich denke, das liegt daran, dass derzeit kein empfangender Prozess auf diesen lokalen Socket lauscht, richtig?

Ich gebe dir bezüglich des ENOTCONN-Fehlers Recht write() im Server. Ein UDP-Socket würde sich normalerweise nicht beschweren, da er nicht wissen kann, ob der Client-Prozess zuhört. Unix-Domain-Datagramm-Sockets sind jedoch anders. Tatsächlich ist die write() blockiert tatsächlich, wenn der Empfangspuffer des Clients voll ist, anstatt das Paket zu verwerfen. Dies macht Unix-Domänen-Datagramm-Sockets UDP für IPC viel überlegen, da UDP mit Sicherheit Pakete unter Last verwirft, sogar auf localhost. Andererseits bedeutet es, dass Sie mit schnellen Schreibern und langsamen Lesern vorsichtig sein müssen.

  • Ich konnte die Punkte mit dieser Antwort, der von @caf, und den letzten 2 Quelldateien unter verbinden thomasstover.com/uds.html (Beachten Sie, dass dieser Code einige kleinere Fehler enthält). Der Servercode in der Frage funktioniert nicht, ohne die Clientadresse(n) zu erhalten, was mir beim Lesen der Antwort von caf klar wurde.

    – hBrent

    25. Juni 2014 um 17:10 Uhr


  • Die Sätze, die mit „Ein UDP-Socket würde sich normalerweise nicht beschweren“ und „Dies macht Unix-Domänen-Datagramm-Sockets viel besser als UDP“ beginnen, sind beide falsch. Das meiste, wenn nicht alles, was Sie über Unix-Domänen-Datagramm-Sockets gesagt haben, gilt gleichermaßen für IP-Domänen-UDP-Sockets: insbesondere, dass sie zur Verwendung verbunden sein müssen write(), und dass sie blockieren write() oder send() solange der Sendepuffer voll ist. -1 für Fehlinformationen.

    – Benutzer207421

    21. August 2014 um 1:15 Uhr

  • @EJP: Ich glaube, Sie haben einige meiner Antworten falsch verstanden. Ich wollte nicht andeuten, dass Steckdosen währenddessen getrennt werden könnten write(), und ich habe nichts über einen vollen Sendepuffer erwähnt (nur einen vollen Empfangspuffer). Ich habe den von Ihnen unten erwähnten Absatz verschoben, weil er eine Nebenfrage behandelt, die Teil der Verwirrung sein könnte.

    – Adamlamar

    24. August 2014 um 21:00 Uhr

  • Ich verstehe den Wunsch, mich von allem fernzuhalten, was mit Unix-Domänen zu tun hat. Ich vermute, dass viele Zuschauer dieser Frage wegen Hausaufgaben hier sind und diese Option nicht wirklich haben. Außerdem sind Unix-Domänen-Datagramm-Sockets technisch unzuverlässig, aber sie sind viel, viel zuverlässiger als UDP für die Kommunikation zwischen Prozessen. Ich habe versucht, UDP in meinem Projekt zu messen anreihbar, aber UDP hat einen großen Prozentsatz der Pakete verworfen. Wenn Sie keine sehr geringe Latenz benötigen, verwenden Sie besser TCP – zumindest unter Linux schneidet es nach den Messungen des Tools am besten ab.

    – Adamlamar

    24. August 2014 um 21:21 Uhr

  • Die Domain thomasstover.com/uds.html verweist nicht mehr auf diesen Artikel, aber es gibt eine Version darin das Archiv und auch ein migrierte Domäne mit diesem Artikel, wenn Sie nach dem vollständigen Beispiel suchen.

    – NCode

    27. Juli 2019 um 10:44 Uhr

Die unmittelbare Ursache Ihres Fehlers ist das write() nicht weiß, wohin Sie die Daten senden möchten zu. bind() setzt den Namen von dein Seite der Steckdose – dh. wo die Daten herkommen aus. Um die Zielseite des Sockels festzulegen, können Sie entweder verwenden connect(); oder Sie können verwenden sendto() Anstatt von write().

Der andere Fehler (“Adresse wird bereits verwendet”) liegt daran, dass nur ein Prozess dies kann bind() an eine Adresse.

Sie müssen Ihren Ansatz ändern, um dies zu berücksichtigen. Ihr Server muss eine bekannte Adresse abhören, die mit eingestellt ist bind(). Ihre Kunden müssen eine Nachricht an den Server an diese Adresse senden, um ihr Interesse am Empfang von Datagrammen zu bekunden. Der Server empfängt die Registrierungsnachrichten von Clients, die verwenden recvfrom(), und zeichnen Sie die von jedem Client verwendete Adresse auf. Wenn es eine Nachricht senden möchte, muss es alle ihm bekannten Clients durchlaufen und verwenden sendto() um die Nachricht nacheinander an jeden zu senden.

Alternativ können Sie lokales IP-Multicast anstelle von UNIX-Domain-Sockets verwenden (UNIX-Domain-Sockets unterstützen kein Multicast).

Benutzeravatar von Hibou57
Hibou57

Wenn es um Rundfunk geht (so wie ich es verstehe), dann laut unix(4) – UNIX-Domain-Protokollfamiliedie Übertragung ist mit UNIX-Domain-Sockets nicht verfügbar:

Die Unix-Ns-Domain-Protokollfamilie unterstützt keine Broadcast-Adressierung oder irgendeine Form von “Wildcard”-Abgleich bei eingehenden Nachrichten. Alle Adressen sind absolute oder relative Pfadnamen anderer Unix-Ns-Domain-Sockets.

Multicast könnte eine Option sein, aber ich weiß, dass es mit POSIX nicht verfügbar ist Linux unterstützt UNIX-Domain-Socket-Multicast.

Siehe auch: Einführung von Multicast-Unix-Sockets.

Benutzeravatar von Pintu Patel
Pintu Patel

Dies geschieht, weil der Server oder Client vor dem Aufheben der Verknüpfung/Entfernen für die bind()-Dateizuordnung stirbt. Wenn ein Client/Server diesen Bindungspfad verwendet, versuchen Sie, den Server erneut auszuführen.

Lösungen: Wenn Sie erneut binden möchten, überprüfen Sie einfach, ob die Datei bereits verknüpft ist, und heben Sie dann die Verknüpfung dieser Datei auf. Vorgehensweise: Überprüfen Sie zuerst den Zugriff auf diese Datei durch access(2); wenn ja, dann entlinke(2) es. Setzen Sie diesen Codeabschnitt vor den Aufruf von bind(), die Position ist unabhängig.

 if(!access(filename.c_str()))
    unlink(filename.c_str());

für mehr referenz lesen sie unix(7)

Benutzeravatar von BobTurbo
BobTurbo

Wäre es nicht einfacher, Shared Memory oder Named Pipes zu verwenden? Ein Socket ist eine Verbindung zwischen zwei Prozessen (auf derselben oder einer anderen Maschine). Es ist keine Massenkommunikationsmethode.

Wenn Sie mehreren Clients etwas geben möchten, erstellen Sie einen Server, der auf Verbindungen wartet, und dann können sich alle Clients verbinden und ihnen die Informationen geben. Sie können gleichzeitige Verbindungen akzeptieren, indem Sie das Programm multithreading machen oder Prozesse verzweigen. Der Server stellt multiple Socket-basierte Verbindungen mit mehreren Clients her, anstatt einen Socket zu haben, mit dem sich mehrere Clients verbinden.

Sie sollten sich mit IP-Multicasting statt mit Unix-Domänen irgendetwas befassen. Derzeit versuchen Sie nur, ins Nirgendwo zu schreiben. Und wenn Sie sich mit einem Client verbinden, schreiben Sie nur an diesen Client.

Dieses Zeug funktioniert nicht so, wie du denkst.

Benutzeravatar von ylzhang
ylzhang

Sie können den Bindungsfehler mit dem folgenden Code lösen:

int use = yesno;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (char*)&use, sizeof(int));

Beim UDP-Protokoll müssen Sie aufrufen connect() wenn Sie verwenden möchten write() oder send()andernfalls sollten Sie verwenden sendto() stattdessen.

Um Ihre Anforderungen zu erfüllen, kann folgender Pseudo-Code hilfreich sein:

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
set RESUSEADDR with setsockopt
bind()
while (1) {
   recvfrom()
   sendto()
}

  • OP fragte nach einer Lösung für AF_UNIX, nicht AF_INET

    – Fließen

    27. Januar 2012 um 12:07 Uhr

1404870cookie-checkUnix Domain Socket: Verwendung von Datagrammkommunikation zwischen einem Serverprozess und mehreren Clientprozessen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy