web-dev-qa-db-ger.com

Unix Domain Socket: Verwenden der Datagramm-Kommunikation zwischen einem Serverprozess und mehreren Clientprozessen

Ich möchte eine IPC - Verbindung zwischen mehreren Prozessen unter Linux herstellen. 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 (nicht formatiert, binär) und verteilt diese Daten über einen lokalen AF_UNIX-Socket unter Verwendung des Datagram-Protokolls (d. H. Ähnlich wie UDP mit AF_INET). Die Daten, die von diesem Prozess an einen lokalen Unix-Socket gesendet werden, müssen 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 ein Socket zu erstellen und Daten an ihn (den Serverprozess) zu senden:

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 mit der Fehlermeldung ENOTCONN zurück ("Transportendpunkt ist nicht verbunden"). Ich vermute, das liegt daran, dass derzeit kein Empfangsprozess auf diesen lokalen Socket lauscht, oder?

Dann habe ich versucht, einen Client zu erstellen, der eine Verbindung zu diesem Socket herstellt.

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 die Bindung fehl ("Adresse wird bereits verwendet"). Muss ich also einige Socket-Optionen einstellen, oder ist dies generell der falsche Ansatz?

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

31
BigMick

Es gibt einen Trick bei der Verwendung von Unix-Datagramm-Sockets. Im Gegensatz zu Stream-Sockets (TCP- oder Unix-Domäne) benötigen Datagramm-Sockets Endpunkte, die sowohl für den Server als auch für den Client definiert sind. Wenn eine Verbindung in Stream-Sockets hergestellt wird, wird vom Betriebssystem implizit ein Endpunkt für den Client erstellt. 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 bereits verwendet" sehen, ist, weil Sie dem Client sagen, dass er sich an dieselbe Adresse wie der Server binden soll. Bei bind() geht es darum, die externe Identität geltend zu machen. Zwei Steckdosen können normalerweise nicht denselben Namen haben.

Bei Datagramm-Sockets, insbesondere Unix-Domain-Datagramm-Sockets, muss der Client an seinem eigenen - Endpunkt bind(), dann an dem Server - Endpoint connect(). Hier ist Ihr Client-Code, etwas 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 Ihre Steckdose vollständig eingerichtet sein. Ich denke theoretisch, dass Sie read()/write() verwenden können, aber normalerweise würde ich send()/recv() für Datagramm-Sockets verwenden.

Normalerweise sollten Sie nach jedem dieser Aufrufe den Fehler überprüfen und anschließend eine perror() ausgeben. 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 fast alle C-Systemaufrufe.

Die beste Referenz dafür ist Steven's "Unix Network Programming". In der 3. Ausgabe, Abschnitt 15.4, Seiten 415-419, finden Sie einige Beispiele und eine Auflistung der Vorbehalte.

Übrigens in Bezug auf

Ich vermute, das liegt daran, dass derzeit kein Empfangsprozess auf diesen lokalen Socket lauscht, oder?

Ich denke, Sie haben Recht mit dem ENOTCONN-Fehler von write() im Server. Ein UDP-Socket würde sich normalerweise nicht beschweren, da er keine Möglichkeit hat, zu erfahren, ob der Client-Prozess überwacht. Unix-Domänendatagramm-Sockets unterscheiden sich jedoch. Tatsächlich wird write() tatsächlich blockiert, wenn der Empfangspuffer des Clients voll ist, anstatt das Paket abzulegen. Dadurch sind Unix-Domänendatagramm-Sockets für IPC UDP weit überlegen, da UDP Pakete unter Last, selbst unter localhost, höchstwahrscheinlich verwirft. Andererseits bedeutet das, dass Sie bei schnellen Schreibern und langsamen Lesern vorsichtig sein müssen.

37
adamlamar

Die unmittelbare Ursache Ihres Fehlers ist, dass write() nicht weiß, wohin Sie die Daten an senden möchten. bind() setzt den Namen der Ihrer Seite des Sockels - dh. Woher die Daten kommen von . Um die Zielseite des Sockets festzulegen, können Sie entweder connect() verwenden. oder Sie können sendto() anstelle von write() verwenden.

Der andere Fehler ("Adresse wird bereits verwendet") liegt darin, dass nur ein Prozess bind() an eine Adresse senden kann.

Sie müssen Ihren Ansatz ändern, um dies zu berücksichtigen. Ihr Server muss eine bekannte Adresse abhören, die mit bind() festgelegt ist. Ihre Clients müssen an diese Adresse eine Nachricht an den Server senden, um ihr Interesse am Empfang von Datagrammen zu registrieren. Der Server empfängt die Registrierungsnachrichten von Clients mit recvfrom() und zeichnet die von jedem Client verwendete Adresse auf. Wenn er eine Nachricht senden möchte, muss er alle ihm bekannten Clients durchlaufen, wobei er die Funktion sendto() verwendet, um die Nachricht an die einzelnen Clients zu senden.

Alternativ können Sie lokales IP-Multicast anstelle von UNIX-Domänensockets verwenden (UNIX-Domänensockets unterstützen Multicast nicht).

7
caf

Wenn es sich bei der Frage um Broadcasting handelte (so wie ich es verstehe), dann ist Broadcasting gemäß unix (4) - UNIX-Domänenprotokollfamilie bei UNIX Domain Sockets nicht verfügbar:

Die Unix-Ns-Domain-Protokollfamilie unterstützt nicht Broadcast-Adressierung oder irgendeine Form von "Wildcard", die mit .__ übereinstimmt. bei eingehenden Nachrichten. Alle Adressen sind absolut oder relative Pfadnamen anderer Unix Ns-Domainsockel.

Möglicherweise ist Multicast eine Option, aber ich bin mir sicher, dass POSIX nicht verfügbar ist, obwohl Linux unterstützt UNIX Domain Socket Multicast .

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

2
Hibou57

Es wird wegen .__ geschehen. Server oder Client sterben vor dem Verknüpfen/Entfernen für bind () - Dateizugehöriger. Wenn ein Client/Server diesen Bindepfad verwendet, versuchen Sie, den Server erneut auszuführen.

solutions: Wenn Sie erneut binden möchten, überprüfen Sie, ob die Datei bereits zugeordnet ist, und heben Sie die Verknüpfung dieser Datei auf . Schritt: Überprüfen Sie zunächst den Zugriff auf diese Datei mit Zugriff (2); dann unlink (2) it . setze diesen Code vor dem bind () - Aufruf, Position ist unabhängig.

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

für mehr Referenz lesen Sie Unix (7)

0
Pintu Patel