POSIXメッセージキューを実装した時のメモ_2【構造体編】

# 目次
1. 前置き
2. 構造体の送受信
3. まとめ

# 1. 前置き
・[前回](https://qiita.com/Y_lab_nk/items/a6074317c020643fb49b)の続き
・筆者のITスキルレベルは脱初心者くらい
・Linuxの理解度は初心者に毛が生えた程度

# 2. 構造体の送受信
構造体を送る方法について少し考えてみる。まずは[Man page of MQ_OVERVIEW](https://linuxjm.osdn.jp/html/LDP_man-pages/man7/mq_overview.7.html)から引用した`mq_send`の定義。

```
int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned int msg_prio);
```

送るデータは第2引数の`msg_ptr`になるが、型がconst char *になっている。

こいつ...文字列しか送れないのか...？

そんなことはない。[ポインタ](http://wisdom.sakura.ne.jp/programming/cs/cs67.html)に書いてあるけど、CやC++のポインタの型キャストで型を変換させれば問題ないようにできている。第3引数でサイズをわざわざ要求するのもこの背景があるからだろう。（というかC++だと構造体に関数入れられるんだね）

ということで以上を考慮して、[いつもの場所](https://github.com/y-lab-nk/posix_message_queue/tree/main/posix_struct)からコードを引用。長くなるので、ここでは`qsend.cpp`だけ載せます。

```cpp:qsend.cpp
#include <mqueue.h>
#include <string.h>
#include <iostream>
#include "posix_mes_q.h"

int main(){
    const char * que_name = "/sample";
    struct mq_attr attr;
    mqd_t q;
    int error_check;

    SAMPLE_OBJ buf = {"taro yamada", 20, 75};

    // queue open (writeonly or create)
    q = mq_open(que_name, O_WRONLY | O_CREAT | O_NONBLOCK, S_IRWXU, NULL);
    // error check
    if (q == -1){
        Error_execute(q,que_name,"Error : mq_open");
        return -1;
    }
    
    // queue set message
    error_check = mq_send( q, (const char *)(&buf) , sizeof(SAMPLE_OBJ) , 0);
    // error check
    if (error_check == -1){
        Error_execute(q,que_name,"Error : mq_send");
        return -1;
    }

    // if queue don't closed, can't open new queue and can't unliunk
    error_check = mq_close(q);
    // error check
    if (error_check == -1){
        Error_execute(q,que_name,"Error : mq_check");
        return -1;
    }

    // process stop
    std::cout << "please Enter" << std::endl;
    std::cin.get();

    // queue kill
    mq_unlink(que_name);

    return 0;
}
```

## 最小構成からの変更点

簡単に変更点を列挙すると

・送るデータを構造体にした
・エラーチェックを追加
**・O_NONBLOCKの追加**

O_NONBLOCKの追加だけ解説する。O_NONBLOCKは非停止 (nonblocking) モードでキューをオープンするという意味である。したがって、 mq_receiveとmq_sendが通常は停止する状況においてエラーを吐くようになる。
わざわざこの設定にしたのは、mq_receiveの仕様でキューが空だと待機して操作が返ってこないのが原因である。空だったらさっさとエラーを吐いて返って来てほしい場合は、この設定を忘れないように。

## 実行結果
ゲストOSでコードを入力するのしんどかったので、ホストOSの方でコード入力するように開発環境を整備した。前回とはパス変わってるけど特に実行結果に影響しないので無視して結構です。

```
/sf_cpp_workspace/POSIX/POSIX_struct$ ./qsend
please Enter
```
```
/sf_cpp_workspace/POSIX/POSIX_struct$ ./qrecv
name =taro yamada
age  =20
score=75
/sf_cpp_workspace/POSIX/POSIX_struct$ ./qrecv
Error : mq_receive
```

1回目の`qrecv`でキューに入れた構造体のデータが返ってくることがわかる。ついでに2回目はmq_receiveでエラーが起きてるが、それはキューの中身が空になっているからである。

## tips集
**・サイズミス**
[前回の記事](https://qiita.com/Y_lab_nk/items/a6074317c020643fb49b)でmq_openの第4引数をNULLにするとサイズが8129バイトに初期設定されると書いたことを覚えているだろうか？
下手に構造体のサイズを大きくすると、余裕でこの上限を超えてしまってエラーを吐くことになる（一敗）。初歩的だがサイズを隠蔽している今回のプログラムだと気付くまで時間がかかった...

**・エラー処理**
今回のエラー処理で考慮するのはメモリリークが起きないようにすることである。つまり今回だとmq_unlinkを確実に通すことと同義。
mq_unlinkを通すには、mq_closeも通す必要がある（mq_unlinkの仕様でキューがopenのままだと削除しない）。したがって以下のようにエラー処理を書いた。
```
void Error_execute(mqd_t q ,const char * que_name ,const char * mess){
    std::cout << mess << std::endl;
    mq_close(q);
    mq_unlink(que_name);
};
```
上記内容は`posix_mes_q.h`にあるので、必要に応じて参照すること。

正直なことを言うと、mq_closeとmq_unlinkがエラーを起こすことは考慮していない。というかその2つがエラー起こしたらどうしようもないのでは、と思ってる。多分解決策はあるんだろうけど、わざわざ仕様を理解してコードに起こすのが億劫である。

あと今更だが、OS搭載してるんだからメモリリークとか自動で解決するんじゃ...。前回のメモリ不足の原因はメモリリークじゃない...？
更に追加でtry catch使えばもっと簡単に書けたね。

# 4. まとめ

今回は構造体の送受信について記載した。これでメッセージキューの基本的な機能は抑えたと考えてよいだろう。

あとは必要に応じてこのPOSIXメッセージキューを使うことになるのだが、如何せん仕様を把握しないと使えない部分が多い。要するに使い勝手が悪いので、次回は使いやすくなるようにメッセージキューのクラスを設計していこう。

【余談】
本当は今回でPOSIXメッセージキューを終わらせようと思ったけど、クラス設計で書きたいこと長くなるので分割した