POSIX メッセージキュー for Windows

Linux BSD Unix には POSIX メッセージキューという便利な API があります。この便利な POSIX メッセージキューを Windows C/C++ でも使えるように実装しました。

POSIX メッセーキューはプロセス間通信 IPC:Inter-Process Communication 技術の 1 つで 複数のプロセス間でメッセージのやりとりを行うことができます。

メッセージキューとソケット通信の違い

POSIX メッセージキューはソケット通信とは異なり送信側と受信側が同時に存在していなくても良い という特徴があります。ソケット通信の場合には一方のプロセスがもう一方のプロセスにソケットを接続して通信をおこなうため 2 つのプロセスは同時に存在している必要があります。ソケット通信では受信側プロセスが存在しない状態では送信側プロセスはデータを送信することができないわけです。

POSIX メッセージキューの場合はプロセスの生存期間とは無関係に永続性を持つメッセージキューが存在します。送信側プロセスは受信側プロセスの有無に関わらずメッセージキューにデータを書き込むことができます。同様に受信側プロセスは送信側プロセスの有無に関わらずメッセージキューからデータを読み出すことができます。

また POSIX メッセージキューにはメッセージの優先度というものがあります。ソケット通信では送信された順序でしかデータを受信することはできませんが メッセージキューでは優先度の高いメッセージが優先度の低いメッセージを追い越して先にメッセージキューから読み出されます。優先度を上手く使うと緊急性の高い処理や例外的な処理を優先的に扱うようにアプリケーションを設計できます。

ダウンロード

mqueue-win32.zip
ダウンロード

▶ ソースコードリポジトリ SVN はこちら

ソースコードは mqueue.c mqueue.h のみです。Visual Studio VC だけでなく MinGW GCC でもコンパイルできるようになっています。

ライブラリとしてビルドすることもできますし ライブラリとしてビルドせずに mqueue.c mqueue.h をプロジェクトに追加して そのままアプリケーションと一緒にビルドすることもできます。

使い方

POSIX メッセージキューの使い方は Linux 実装と同じです。Linux POSIX メッセージキューの概要については以下を参照してください。Windows 版でも API の使用方法は同じなので Windows POSIX メッセージキューを使う時にも役に立つ情報です。

POSIX メッセージキューを構成する関数のうち mq_open mq_close mq_send mq_receive 4 つの関数の使い方を覚えればメッセージキューを使えるようになります。

メッセージキューをオープンする

メッセージキューを読み書きするプロセスは はじめに mq_open 関数を使ってメッセージキューをオープンする必要があります。

/my-queue1 という名前のメッセージキューをオープンする
int oflag = O_CREAT | O_RDWR; int mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; mqd_t mqdes = mq_open(name.c_str(), oflag, mode, NULL); if(mqdes == (mqd_t)-1) { //open error }

mq_open 関数の戻り値はメッセージキュー記述子 ディスクリプタ です。ただし メッセージキューのオープンに失敗した場合は有効なメッセージキュー記述子ではなく -1 が返されます。

メッセージを送信する

メッセージの送信には mq_send 関数を使用します。

"HELLO" というメッセージを送信する
int ret = mq_send(mqdes, "HELLO", 7, 0); if(ret == -1) { //send error }

これは “HELLO” というメッセージを送信する例です。

POSIX メッセージキューでは文字列に限らず任意のバイナリデータをメッセージとして送信することができます。文字列を送信する場合にはヌル終端文字も送信されるように文字列の長さ +1 をデータの長さとして指定する必要があります。この例では “HELLO” の長さ 6 にヌル終端文字の分を加えて 7 となります

POSIX ではメッセージの優先度として上限を少なくとも 31 と定めていますが 多くの実装でより大きな値を扱えるようになっています。この Windows 実装では優先度の型を unsigned int としているので 0 ~ 4,294,967,295 の範囲で優先度を指定することができます。

メッセージを受信する

メッセージを受信するには mq_receive 関数を使用します。

メッセージを受信する
char msg[BUFSIZ]; unsigned int msg_prio; ssize_t len; len = mq_receive(mqdes, msg, BUFSIZ, &msg_prio); if(len == -1) { //receive error }

これはメッセージキューに溜まっているメッセージを受信する例です。

簡略化するためにバッファのサイズを BUFSIZ としていますが 実際にはメッセージキューのメッセージ最大サイズを格納できるバッファを用意する必要があります。mq_getattr 関数を使用してメッセージキューの属性情報を取得することで 正確なメッセージ最大サイズを知ることができます。

mq_receive の戻り値は受信したメッセージデータの長さです。エラーの場合には -1 が返されます。

メッセージキューが空の状態で mq_receive を呼び出した場合の動作は mq_open 関数でメッセージキューをオープンする時に O_NONBLOCK を指定していたかどうかで異なります。

mq_open 関数で O_NONBLOCK を指定していなかった場合はブロッキングモードです。つまり メッセージキューが空の状態で mq_receive を呼び出すと 関数からすぐに復帰せずに待機状態になります。他のプロセスがメッセージキューにデータを書き込むと mq_receive 関数の呼び出しから復帰します。

mq_open 関数で O_NONBLOCK を指定していた場合はノンブロッキングモードです。メッセージキューが空の状態で mq_receive を呼び出すと 関数からすぐに復帰して -1 を返します。このとき errno には EAGAIN が設定されるので errno の値を確認することで空状態による復帰か その他のエラーによる復帰かを区別することができます。

メッセージキュー記述子をクローズする

メッセージキュー記述子をクローズするには mq_close を使用します。

メッセージキュー記述子をクローズする
mq_close(mqdes);

引数として mq_open 関数で取得したメッセージキュー記述子を指定します。

この関数はメッセージキュー自体を閉じるのではなく メッセージキュー記述子を閉じるものであることに注意してください。mq_open 関数でメッセージキュー記述子を取得すると 内部では malloc 関数によってヒープメモリが確保されたり メッセージキューファイルのハンドル 同期制御用のミューテックスハンドルなどのリソースが作成されます。mq_close 関数は これらのメッセージキュー記述子に紐づけされたリソースを解放します。

mq_close を呼び出してもメッセージキュー自体は影響を受けません。つまり 書き込みプロセスが mq_close を呼び出しても 別の読み込みプロセスが mq_open で取得したメッセージキュー記述子は引き続き有効でメッセージキューからの読み取り操作をおこなうことができます。

他にもいろいろな関数があります

POSIX メッセージキュー API には他にも多くの関数があります。

mq_getattr
メッセージキューの属性を取得します。
mq_setattr
メッセージキューの属性を設定します。
mq_timedsend
メッセージキューにメッセージを送信します。ブロッキングモードで待機状態になっても指定時間で復帰します。
mq_timedreceive
メッセージキューからメッセージを受信します。ブロッキングモードで待機状態になっても指定時間で復帰します。
mq_unlink
メッセージキュー自体を削除します。

これらの関数は Windows 実装においても Linux と同様に動作しますので 関数の仕様や使い方については Linux のドキュメント POSIX メッセージキューの概要 を参考にしてみてください。