Unix ソケット (UDS、Unix ドメイン ソケット)

【知識の紹介】

Linux システムにはプロセス間通信方式が多数あり、ソケットもその 1 つです。ただし、従来のソケットの使用は TCP/IP プロトコル スタックに基づいており、IP アドレスを指定する必要があります。もちろん、異なるホスト上の 2 つのプロセスが通信する場合、これには何の問題もありません。ただし、1 台のマシン上の 2 つの異なるプロセス間で通信する必要があるだけの場合、IP アドレスを使用するのは少しやりすぎです。

実際、ソケットには、この問題を解決するために特に使用される Unix ドメイン ソケットと呼ばれるカテゴリがあることを必ずしも知っている人は多くありません。API の呼び出し方法は通常の TCP/IP ソケットと基本的に同じですが、若干の違いがあります。

Unix ドメイン ソケットは、同じコンピュータ上のプロセス間通信に使用されます。インターネット ドメイン ソケットも同じ目的に使用できますが、Unix ドメイン ソケットの方が効率的です。Unix ドメイン ソケットはプロトコル処理を実行せず、ネットワーク ヘッダーの追加または削除、チェックサムの計算、シーケンス番号の生成、および確認メッセージの送信を必要としません。Unix ドメイン ソケットは、バイト ストリームとデータグラムの 2 つのデータ タイプを提供します。Unix ドメイン データグラム サービスは信頼性が高く、メッセージの損失やエラーの発生がありません。簡単に言えば、Unix ドメイン ソケットはソケットとパイプのハイブリッドです。

【工程比較】

 

上の図はストリーム指向のソケットを表し、TCP/IP ソケットの場合は TCP プロトコルを表し、下の図はパケット指向のソケットを表し、TCP/IP ソケットの場合は UDP プロトコルを表します。次に、これらの API のいくつかについて、特に通常の TCP/IP ソケットと Unix ドメイン ソケットの違いに焦点を当てて説明します。

【説明書】

1)ソケット()

最初のステップはソケットを作成することです。

1. int socket (int domain, int type, int protocol);

 

API 定義は同じですが、ここでの最初のパラメータ、つまりドメインは、通常の TCP/IP ソケットの AF_INET ではなく、AF_UNIX または AF_LOCAL に設定する必要があります。2 番目のパラメータはソケットのタイプを示し、ストリーム ソケット (SOCK_STREAM) とパケット ソケット (SOCK_DGRAM) に分けられます。通常の AF_INET ソケットとは異なり、カーネルを介してローカルで通信するため、SOCK_STREAM および SOCK_DGRAM は信頼性が高く、パケットの損失やパケットの送信順序と受信パケットの順序の不一致が発生しません。両者の違いは、SOCK_STREAM では送信されるデータ量に関係なく切り捨てられないのに対し、SOCK_DGRAM では送信されたデータがメッセージの最大長を超えるとデータが切り捨てられることです。最後のパラメータはプロトコルを表します。Unix ドメイン ソケットの場合は、0 に設定する必要があります。したがって、Unix ドメイン ソケットは通常、次の方法で作成されます。

1. int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  // 流式Unix域套接字  
2. int sockfd = socket(AF_UNIX,SOCK_DGRAM, 0);    // 数据包式套接字

2)バインド()

ストリーミング ソケットのサーバー側では、socket() 関数を使用して新しく作成されたソケットのファイル記述子を取得した後、それをアドレスにバインドする必要があります。

1. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

 

Unix ドメイン ソケットでは、ソケット アドレスは sockaddr_un 構造体で表され、その構造は次のとおりです。

struct sockaddr_un {
     sa_family_t  sun_family;       /* AF_UNIX 或 AF_LOCAL */
     char         sun_path[108];    /* pathname */
}

構造体の最初のフィールドは「AF_UNIX」に設定する必要があります。2 番目のフィールドはパス名を表します。したがって、Unix ドメイン ソケットをローカル アドレスにバインドするには、sockaddr_un 構造体を作成して初期化し、この構造体へのポインタを addr パラメータ (型変換が必要) としてbind() 関数に渡し、addrlen を設定する必要があります。パラメータをこの構造体の実際のサイズに設定します。

なお、このパス名は実は通常のパス名と抽象パス名の2種類に分かれていることにも触れておきたい。

まず、共通パス名について説明します。これは、Linux の基本的なファイル パスであり、NULL ('\0') で終わる必要があるため、理解しやすいです。Unix ドメインソケットをバインドすると、ファイルシステム内の対応する場所にファイルが作成され、このファイルのタイプは「ソケット」としてマークされるため、このファイルは open() 関数で開くことができません。この Unix ドメイン ソケットが不要になった場合は、remove() 関数または unlink() 関数を使用して、対応するファイルを削除できます。指定されたパス名のファイルがファイル システム内にすでに存在する場合、バインドは失敗します (エラー EADDRINUSE が返されます)。したがって、ソケットは 1 つのパスにのみバインドでき、同様に、パスは 1 つのソケットにのみバインドできます。

次に, 抽象パス名とは何かを見てみましょう. これは実際には Linux に固有の機能です. ファイル システム内にこの名前のファイルを作成せずに Unix ドメイン ソケットを名前にバインドできるようにします. 抽象名前空間バインディングを作成する場合は、sun_path フィールドの最初のバイトを NULL ('\0') に設定する必要があり、通常のファイル システムの名前空間とは異なり、システムは sun_path を使用して最初のバイトを分割します。 1 バイト以降のバイトは抽象名として扱われます。つまり、抽象パス名を解析するときは、通常のパス名を解析して最初の NULL が解析された時点で停止するのではなく、sun_path フィールド内のすべてのバイトを使用する必要があります。ファイル システム内にファイルが作成されなくなるため、抽象パス名に関してファイル システム内の既存のファイルとの名前の競合を心配する必要がなく、使用後にソケットを削除する必要もありません。このファイルはソケットとともに生成されます。 、この抽象名はソケットが閉じられると自動的に削除されます。

最後に、パーミッションの問題について触れておきます, 対応するファイルをファイル システムに作成する必要があるためです. 通常のパス名の場合、bind() 関数を使用するプロセスは、パス名のディレクトリ部分に対して書き込み可能およびアクセス可能なパーミッションを持っている必要があります。また、デフォルトでは、bind() 関数が呼び出されるとき、すべての権限 (つまり 777) が所有者、グループ、およびその他のユーザーに与えられます。この動作を変更したい場合は、bind() の後に作成されたファイルを変更できます。 . ファイルのアクセス許可と属性。

3)聞く()

ストリーミング ソケットのサーバー側では、listen() 関数は TCP/IP ソケットと Unix ドメイン ソケットで同じ方法で呼び出され、違いはありません。

int listen(int sockfd, int backlog);

4)受け入れる()

ストリーミング ソケットのサーバー側では、bind() を呼び出してローカル パスをバインドした後も、クライアントのリクエストを受信する必要があります。これは、accept() 関数を呼び出すことで実現されます。

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

 

通常の TCP/IP ソケットとは異なり、Unix ドメイン ソケットにはクライアント アドレスの問題がない (すべてが 1 つのマシン上にある) ため、ここの addr および addrlen パラメータは NULL に設定する必要があります。ここで、このサーバー側プロセスのような異なるプロセスによって送信されたストリーム データは、カーネル内で区別され、accept() によって作成されたソケットにバインドされます。データ パケット ソケットにはこの対応がないため、後で紹介するコード内で区別する必要があります。

5)接続()

ストリーミング ソケット クライアントの場合、socket() 関数を使用して新しく作成されたソケットのファイル記述子を取得した後、connect() 関数を呼び出してサーバーに接続できます。

int connect(int sockfd, struct sockaddr *addr,int addrlen);

 

この関数は、bind() 関数と同様に、アドレス addr が sockaddr_un 構造体で表される必要がある点を除いて、基本的に Unix ドメイン ソケットの場合と同じ方法で TCP/IP ソケットで呼び出されます。

6)read()とwrite()

ストリーミング ソケットのサーバー側では、read() 関数と write 関数が TCP/IP ソケットと Unix ドメイン ソケットで同じ方法で呼び出され、違いはありません。

1. ssize_t read(int sockfd, void *buf, size_t length);   
2. ssize_t write(int sockfd, const void *buf, size_t length);

 

7)recvfrom() と sendto()

データ パケット ソケットの場合、サーバー側の recvfrom() はクライアントから送信されたリクエストを受信するために使用され、クライアント側ではこの関数はサーバーから送信された応答を受信するために使用されます。

int recvfrom(int sockfd, void *buf, int length, unsigned int flags, struct sockaddr *addr, int *addrlen);

 

同時に、クライアント側の sendto() を使用してリクエスト データをサーバー側に送信し、サーバー側はこの関数を使用して応答データをクライアント側に送信します。

int sendto (int sockfd, const void *buf, int length, unsigned int flags, const struct sockaddr *addr, int addrlen);

前述したように、パケット ソケットの場合、サーバーは、対応する応答データを後で正しいクライアントに送信できるように、応答データを送信するときに自分がどのクライアントであるかを認識する必要があります。クライアントは、どのサーバーにデータを送信しているのか、または受信した応答データがどのサーバーから来たのかを知る必要もあります (もちろん、1 つのサーバーとの通信のみを保証する場合は、そのような問題は発生しません)。

ただし、通常のパケットソケットの作成・接続処理では、サーバー側でbind()関数を使ってアドレスをバインドするだけで、クライアントにはアドレスがありません。これはストリーミング ソケットでは問題ありません。クライアント接続を受信するためにサーバー側で accept() 関数が呼び出されるときに、カーネルは新しいソケットを作成し、それによってこの新しいソケットに 1 対 1 の対応をバインドします。の上。したがって、パケット ソケットの場合、クライアントは、bind() 関数を呼び出して再度バインドし、クライアント アドレスを人為的に作成する必要があります。このクライアント パス名アドレスは、明らかにサーバー パス名と同じであってはなりません。

残りは通常の TCP/IP ソケットと同じですが、アドレス addr が sockaddr_un 構造体で表される必要がある点が異なります。

【必要なヘッダファイル】

#include <sys/types.h>
#include <sys/socket.h>

【参考文献】

Unix ドメイン ソケット (Unix ドメイン ソケット) の紹介 [再投稿]_8465449 のテクノロジー ブログ_51CTO ブログUnix ドメイン ソケット (Unix ドメイン ソケット) の紹介 [再投稿], この記事はブロガーによるオリジナル記事であり、ブログの許可なく複製することはできません。ブロガー。著作権に関する声明: この記事はブロガーによるオリジナルの記事であり、ブロガーの許可なく複製することはできません。Linux システムには、Socket https://blog.51cto.com/u_8475449/5954776などのプロセス間通信方法が多数あります。

おすすめ

転載: blog.csdn.net/weixin_39538031/article/details/130562287