web-dev-qa-db-ja.com

Unixドメインソケット:1つのサーバープロセスと複数のクライアントプロセス間のデータグラム通信の使用

Linux上の複数のプロセス間でIPC接続を確立したい。これまでUNIXソケットを使用したことがないので、これがこの問題に対する正しいアプローチであるかどうかわからない。

1つのプロセスがデータ(未フォーマット、バイナリ)を受信し、データグラムプロトコル(つまり、AF_INETを使用したUDPと同様)を使用して、ローカルAF_UNIXソケットを介してこのデータを配信します。このプロセスからローカルUnixソケットに送信されたデータは、同じソケットでリッスンしている複数のクライアントによって受信されます。受信機の数は異なる場合があります。

これを実現するために、次のコードを使用してソケットを作成し、そこにデータを送信します(サーバープロセス)。

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);

この書き込みは、ENOTCONN( "トランスポートエンドポイントが接続されていません")を報告するerrnoで-1を返します。これは、受信プロセスが現在このローカルソケットをリッスンしていないためだと思います。

次に、このソケットに接続するクライアントを作成しようとしました。

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);

ここでは、バインドが失敗します(「アドレスは既に使用中」)。だから、私はいくつかのソケットオプションを設定する必要がありますか、これは一般的に間違ったアプローチですか?

コメント/解決策を事前に感謝します!

33
BigMick

UNIXデータグラムソケットを使用するコツがあります。ストリームソケット(tcpまたはUNIXドメイン)とは異なり、データグラムソケットにはサーバーとクライアントの両方に定義されたエンドポイントが必要です。ストリームソケットで接続を確立すると、クライアントのエンドポイントがオペレーティングシステムによって暗黙的に作成されます。これが一時的なTCP/UDPポートに対応する場合でも、Unixドメインの一時iノードに対応する場合でも、クライアントのエンドポイントが作成されます。そのため、通常、クライアントのストリームソケットに対してbind()を呼び出す必要はありません。

「アドレスは既に使用されています」と表示されるのは、サーバーと同じアドレスにバインドするようにクライアントに指示しているためです。 bind()は、外部IDのアサートに関するものです。通常、2つのソケットに同じ名前を付けることはできません。

データグラムソケット、特にUNIXドメインデータグラムソケットでは、クライアントはbind()をそのownエンドポイントに、次にconnect()server'sに=エンドポイント。クライアントコードを少し変更して、他のいくつかの機能を追加します。

_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);
_

この時点で、ソケットは完全にセットアップされているはずです。理論的にはread()/write()を使用できると思いますが、通常はデータグラムソケットにsend()/recv()を使用します。

通常、これらの各呼び出しの後にエラーをチェックし、後でperror()を発行します。物事がうまくいかないとき、それはあなたを大いに助けます。一般に、次のようなパターンを使用します。

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

これは、ほとんどすべてのCシステムコールに当てはまります。

これに関する最良のリファレンスは、Stevenの「Unix Network Programming」です。第3版のセクション15.4の415〜419ページには、いくつかの例が示されており、多くの警告がリストされています。

ところで、を参照して

これは、受信プロセスが現在このローカルソケットをリッスンしていないためだと思います。

サーバーのwrite()からのENOTCONNエラーについては正しいと思います。 UDPソケットは、クライアントプロセスがリッスンしているかどうかを知る機能がないため、通常は文句を言いません。ただし、UNIXドメインデータグラムソケットは異なります。実際、クライアントの受信バッファーがいっぱいになると、パケットをドロップするのではなく、write()が実際にブロックします。これにより、Unixドメインデータグラムソケットは、IPCに対してUDPよりもはるかに優れたものになります。なぜなら、UDPは、localhostであっても、負荷がかかっている場合にパケットを確実にドロップするからです。作家と遅い読者。

38
adamlamar

エラーの原因は、write()がデータの送信先toを知らないことです。 bind()は、ソケットのyourサイドの名前を設定します-すなわち。データが来る場所from。ソケットの宛先側を設定するには、connect();を使用できます。または、sendto()の代わりにwrite()を使用できます。

もう1つのエラー(「アドレスは既に使用中」)は、1つのプロセスだけがアドレスにbind()できるためです。

これを考慮するには、アプローチを変更する必要があります。サーバーは、bind()で設定された既知のアドレスでリッスンする必要があります。クライアントは、データグラムの受信に関心があることを登録するために、このアドレスのサーバーにメッセージを送信する必要があります。サーバーは、recvfrom()を使用してクライアントから登録メッセージを受信し、各クライアントが使用するアドレスを記録します。メッセージを送信する場合、sendto()を使用して各クライアントに順番にメッセージを送信し、知っているすべてのクライアントをループする必要があります。

または、UNIXドメインソケットの代わりにローカルIPマルチキャストを使用できます(UNIXドメインソケットはマルチキャストをサポートしていません)。

7
caf

質問がブロードキャストに関するものである場合(私が理解しているとおり)、 nix(4)-UNIXドメインプロトコルファミリ によると、ブロードキャストはUNIXドメインソケットでは使用できません。

Unix Nsドメインプロトコルファミリは、受信メッセージでのブロードキャストアドレッシングまたは「ワイルドカード」マッチングをサポートしていません。すべてのアドレスは、他のUnix Nsドメインソケットの絶対パス名または相対パス名です。

マルチキャストもオプションかもしれませんが、 LinuxはUNIXドメインソケットマルチキャストをサポートしています ですが、POSIXでは使用できないことを知っていると感じています。

参照: マルチキャストUnixソケットの紹介

2
Hibou57

Bind()ファイルの関連付けのリンク解除/削除の前にサーバーまたはクライアントが停止するために発生します。このバインドパスを使用しているクライアント/サーバーのいずれかで、サーバーを再実行してください。

解決策:再度バインドする場合は、ファイルが既に関連付けられていることを確認してから、そのファイルのリンクを解除します。手順:最初にaccess(2)でこのファイルのアクセスを確認します。はいの場合、unlink(2)します。 bind()呼び出しの前にこのコードの平和を置き、位置は独立しています。

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

詳細については、unix(7)をお読みください。

0
Pintu Patel