44

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

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

コメント/解決策をお寄せいただきありがとうございます!

4

7 に答える 7

58

Unix Domain Socket をデータグラム構成で使用するための秘訣があります。ストリーム ソケット (tcp または UNIX ドメイン ソケット) とは異なり、データグラム ソケットには、サーバーとクライアントの両方に対して定義されたエンドポイントが必要です。ストリーム ソケットで接続を確立すると、クライアントのエンドポイントがオペレーティング システムによって暗黙的に作成されます。これがエフェメラル TCP/UDP ポートに対応するか、UNIX ドメインの一時 i ノードに対応するかに関係なく、クライアントのエンドポイントが作成されます。これが、通常、クライアントのストリーム ソケットに対して bind() の呼び出しを発行する必要がない理由です。

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

データグラム ソケット、特に UNIX ドメイン データグラム ソケットでは、クライアントはbind()独自エンドポイントに接続し、次にサーバーのconnect()エンドポイントに接続する必要があります。以下は、わずかに変更されたクライアント コードで、その他の機能が追加されています。

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()後で a を発行する必要があります。物事がうまくいかないときに大いに役立ちます。一般に、次のようなパターンを使用します。

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

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

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

ちなみに参考までに

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

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

于 2012-04-21T16:32:46.983 に答える
8

エラーの主な原因はwrite()、データの送信先がわからないことですソケットのあなたのbind()側の名前を設定します-つまり。データがどこから来ているか。ソケットの宛先側を設定するには、次のいずれかを使用できます。の代わりに使用することもできます。connect()sendto()write()

もう 1 つのエラー (「アドレスは既に使用されています」) は、1 つのプロセスしかbind()アドレスにアクセスできないためです。

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

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

于 2010-07-25T04:14:38.583 に答える
5

質問が(私が理解しているように)ブロードキャストに関するものである場合、unix(4) - UNIX-domain protocol familyによると、ブロードキャストは UNIX ドメイン ソケットでは利用できません。

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

マルチキャストはオプションかもしれませんが、Linux は UNIX Domain Socket マルチキャストをサポートしていますが、POSIX では利用できないことを知っているように感じます。

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

于 2013-03-29T17:26:16.933 に答える
0

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

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

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

詳細については、unix(7) を参照してください。

于 2016-05-25T06:02:07.477 に答える
-1

共有メモリや名前付きパイプを使用する方が簡単ではないでしょうか。ソケットは、(同じマシンまたは異なるマシン上の)2つのプロセス間の接続です。マスコミの方法ではありません。

複数のクライアントに何かを提供したい場合は、接続を待機するサーバーを作成すると、すべてのクライアントが接続できるようになり、情報が提供されます。プログラムをマルチスレッドにするか、プロセスをフォークすることにより、同時接続を受け入れることができます。サーバーは、複数のクライアントが接続する1つのソケットではなく、複数のクライアントとの複数のソケットベースの接続を確立します。

于 2010-07-24T10:14:35.183 に答える
-3

Unix ドメインのものではなく、IP マルチキャストを調べる必要があります。現在、あなたはどこにも書き込もうとしているだけです。また、1 つのクライアントに接続すると、そのクライアントにのみ書き込みます。

このようなものは、あなたが思っているようには機能しません。

于 2010-07-24T10:05:58.163 に答える
-6

次のコードでバインド エラーを解決できます。

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

UDP プロトコルでは、またはconnect()を使用する場合は呼び出す必要があり、それ以外の場合は代わりに使用する必要があります。write()send()sendto()

要件を達成するには、次の疑似コードが役立つ場合があります。

sockfd = socket(AF_INET, SOCK_DGRAM, 0)
set RESUSEADDR with setsockopt
bind()
while (1) {
   recvfrom()
   sendto()
}
于 2010-09-02T06:50:18.690 に答える