LoginSignup
0
2

More than 5 years have passed since last update.

コンピュータ将棋ソフトとの対局サーバーを立てよう<その9>

Last updated at Posted at 2017-03-11

前回の記事 : http://qiita.com/muzudho1/items/798dfbf267340f3801e1

Windows 10 の Visual Studio 2015 で 浮かむ瀬 のソースをいじりたいので、改造することにする。

コンソール・アプリケーション

ソースを全部移動。

プリコンパイル・ヘッダーを OFF にしよう。

いわゆる stdafx.h のことで、使わないので OFF にする。

  • x86 を x64 に変える。
  • [Debug] - [~Properties...] - [Configuration Properties] - [C/C++] - [Precompiled Headers] - [Precompiled Header] を 「Not Using Precompiled Headers」に変える。

#if と #endif のペアがマッチしていないというエラーが出る

そんなことは無いが……。

使ってないんだったら [Ctrl]+[K]、[Ctrl]+[C] でコメントアウトしてしまおう。

コメントアウトしている行にエラーが出ている。

文字コード

Severity    Code    Description Project File    Line    Suppression State
Warning C4819   The file contains a character that cannot be represented in the current code page (932). Save the file in Unicode format to prevent data loss   ukamuse_sdt4_child4 e:\muzudhobusiness\workb\2017-03-04_shogiserver\その他\ukamuse_sdt4_チャイルド4(プロセス間通信)\ukamuse_sdt4_child4\usi.cpp  1   

(932) と言えば Shift-JIS だろう。BOM有のUTF-8に変えたらいいんだろうか?

[File] - [Save ~ As...] から [Save]の代わりに[Save with Encoding...] を選び、
「Unicode (UTF-8 with signature) - Codepage 65001」を選んで[OK]。

21 Error, 76 Worning が、21 Error, 75 Worning に減った。
あと 75回 やるのか。

[File] - [Advanced Save Options...] というサブ・メニューが増えた気がする。これを使うと少し楽。

エラーが減らない。おかしい。

stdafx.h、stdafx.cpp の2ファイルのエラーが75ファイル分 出てるんじゃないか?

そうでもない。

聞いてみた。

at now
流したいジョブ
Ctrl + D

C4819のワーニングをスルー

というのがあるらしい。

やってみよう。

/home/★user/shogi/ukamuse_sdt4/bin# at now
The program 'at' is currently not installed. You can install it by typing:
apt install at

よし、インストールしたぜ。

# at now
warning: commands will be executed using /bin/sh
at> man at
at> ./apery
at> <EOT>
job 1 at Sun Mar 12 04:03:00 2017

いったい どうなったのか?

# jobs
# ps aux | grep apery
root      2726  3.4  0.0 128660   920 ?        Sl   Mar11  40:46 /usr/bin/cli ./tamesi31_cs.exe ../ukamuse_sdt4/bin/apery
root     28267  0.0  0.0  12936   988 pts/6    S+   04:04   0:00 grep --color=auto apery

じゃあ、ログイン、ログアウトしてみるか。

# logout
$ logout
# ps aux | grep apery
root      2726  3.4  0.0 128660   920 ?        Sl   Mar11  40:54 /usr/bin/cli ./tamesi31_cs.exe ../ukamuse_sdt4/bin/apery
root     28367  0.0  0.0  12936   984 pts/6    S+   04:08   0:00 grep --color=auto apery

おっ、いた☆(^~^

Advanced Save Options

Visual Studio 2015 C++、文字コード変えて保存しても、[Save All] しないと 保存されてないのかだぜ? [Advanced Save Options] は、セーブのオプションであって、セーブではない?

[Unicode - Codepage 1200] にしてみよ。改行は [Windows (CR LF)] で。

全テキストファイル、そう設定して保存したら エラーが消えてワーニングだけ残った。

Aperyの標準入出力

common.hpp 抜粋

enum SyncCout {
    IOLock,
    IOUnlock
};
std::ostream& operator << (std::ostream& os, SyncCout sc);
#define SYNCCOUT std::cout << IOLock
#define SYNCENDL std::endl << IOUnlock

出力の << 演算子はオーバーライドしてあって、ロックするとき 0、ロックを解除するとき 1 をストリームの間に挟むよう つなげてあるんだが、これをプロセス間通信に置き換えられないものだろうか。

まず、Apery で std::cout を利用しているところを全検索してみよう。

ぱっと見 少なくとも 50個所はありそうだな。

じゃあ、gameserverというクラス・ファイルを作ろう。

gameserver.h

#pragma once

// バックグラウンド・プロセス用の出力
#define BGP_COUT std::cout
#define BGP_ENDL std::endl

class gameserver
{
public:
    gameserver();
    ~gameserver();
};

こんな感じでどうだろうか。ソースコードの std::cout、std::endl を全部置き換えていこう。

std::cout 89 個所。
std::endl 69 個所 が置き換わった。

#include "gameserver.hpp"

を置いていくのは手作業か。
拡張子はAperyに合わせて .h から .hpp に変えておいた。

ファイルの読み込み順序があるのか、エラーが消えない。

#include "gameserver.hpp"

はファイルの冒頭に書いてるんだが。

// バックグラウンド・プロセス用の出力
#define BGP_COUT BGP_COUT
#define BGP_ENDL BGP_ENDL

なるほど(^~^)

// バックグラウンド・プロセス用の出力
#define BGP_COUT std::cout
#define BGP_ENDL std::endl

修正。

じゃあ、このソースを Ubuntu に逆輸入して動くか調べてみよう

ソース・フォルダーの中に要らんファイルを入れてくる Visual Studio 2015 のマナーの悪さはあるものの、ファイルサイズは小さいし、丸上げするか。著作権爆弾とか無いだろな。

cd /home/★user/shogi/ukamuse_sdt4_child4
# make profgen_sse
Makefile:1: warning: NUL character seen; rest of line ignored
Makefile:1: *** missing separator.  Stop.

改行コードか。

「複数ファイルの改行コードを一括置換しちゃう」(Qiita)
http://qiita.com/kouchi67/items/958e74ef792eed0c157d

Makefile、.hpp、.cpp、.sfen、.txt をLFに置換

# find . \( -name \Makefile -o -name \*.hpp -o -name \*.cpp -o -name \*.sfen -o -name \*.txt \) -type f | xargs -n 10 nkf -Lu --overwrite

# brew install nkf
The program 'brew' is currently not installed. You can install it by typing:
apt install linuxbrew-wrapper
# apt-get install nkf
# find . \( -name \Makefile -o -name \*.hpp -o -name \*.cpp -o -name \*.sfen -o -name \*.txt \) -type f | xargs -n 10 nkf -Lu --overwrite
# make profgen_sse

よし。

# mv apery ../bin/apery

実行してみると 動いている。
幻のapery(※1) でも実行しているわけでなければ 大丈夫だろう。

(※1……同名の違うファイルのこと)

標準入出力と、プロセス間通信を どうやって置き換えるのか?

例えばこんなコードがあるわけだ。

        else if (token == "usi"      ) SYNCCOUT << "id name " << std::string(options["Engine_Name"])
                                                << "\nid author Hiraoka Takuya"
                                                << "\n" << options
                                                << "\nusiok" << SYNCENDL;

これを関数コール文の形に持っていきたい。例えば

Bgp.Enqueue( ★ );

みたいな。じゃあ、

Bgp.Enqueue( << "id name " << std::string(options["Engine_Name"])
                                                << "\nid author Hiraoka Takuya"
                                                << "\n" << options
                                                << "\nusiok" << );

こうすればいいのかというと、違う。文字列になってくれればいいんだが。

「数字や文字列を次々に連結してstringに出力するには?」(
C++ フリーでぷろぐらみんぐ)
http://ameblo.jp/nana-2007-july/entry-10098557843.html

じゃあ、

ostringstream oss;
oss << "id name " << std::string(options["Engine_Name"])
                                                << "\nid author Hiraoka Takuya"
                                                << "\n" << options
                                                << "\nusiok" <<
"";
Bgp.Enqueue( oss.str());

みたいな感じに置換したらいいのか? oss の変数名がソースコード中で重複しまくらないか?

oss.clear();

を置いて、変数を使いまわせばいいか?

// バックグラウンド・プロセス用の出力
#define BGP_COUT bgp_oss.clear(); std::cout 
#define BGP_ENDL std::endl; bgp_enqueue (bgp_oss.str())

こう書くと、セミコロンが邪魔になって

            for (File f = File9; File1 <= f; --f)
                BGP_COUT << (this->isSet(makeSquare(f, r)) ? "  X" : "  .");

こういうケースで for文のターゲットが変わってしまうので、f 変数が未定義になってしまう。

// バックグラウンド・プロセス用の出力
#define BGP_COUT bgp_oss.clear(), std::cout 
#define BGP_ENDL std::endl, bgp_enqueue (bgp_oss.str())

カンマで区切ると、bgp_enqueue( ) 関数が値を返さないじゃないか、とエラーになる。

int bgp_enqueue(std::string message)
{
    std::cout << message << std::endl;
    return 0;
}

じゃあ 0 でも返しておくか。

gameserver.hpp

#pragma once

#include <iostream> // std::cout
#include <sstream> // std::ostringstream

// バックグラウンド・プロセス用の出力
// #define BGP_COUT std::cout
// #define BGP_ENDL std::endl
#define BGP_COUT bgp_oss.clear(), std::cout 
#define BGP_ENDL std::endl, bgp_enqueue (bgp_oss.str())

// 文字列ストリーム出力を、文字列に置換するもの
static std::ostringstream bgp_oss;
// メッセージキューにエンキュー
static int bgp_enqueue(std::string message){
    std::cout << message << std::endl;
    return 0;
    //bgp_oss.clear();
    //bgp_oss << "あいうえお" << "";
    //bgp_oss.str();
}

class gameserver
{
public:
    gameserver();
    ~gameserver();
};

これでコンパイルは通る。

あれっ? FileZilla で転送したら ファイルサイズが全部 0 だ。

# chmod 777 src

なんで 0 になってしまうんだろな?

# chmod 757 ukamuse_sdt4_child4

「FTPで0バイトでアップされる」(1-1/3+1/5-1/7+1/9-・・・)
http://blog.livedoor.jp/sire2/archives/50480369.html

容量制限?

# df -ah

見方が分からない。

# less /etc/fstab

見方が分からない。 [q]をタイプして抜ける。

# edquota ★user
The program 'edquota' is currently not installed. You can install it by typing:
apt install quota

無い。

「[楽天GOLD]FTPソフト(Filezilla)を使用して、」(YAHOO!JAPAN知恵袋)
http://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q11129189821

容量が問題か。じゃあ ukamuse_sdt4_child を削除しよう。

# rm -rf ukamuse_sdt4_child/

あっ! 転送でけた!

800メガバイトの評価ファイルを4つも置いてると いっぱいなのか。

# find . \( -name \Makefile -o -name \*.hpp -o -name \*.cpp -o -name \*.sfen -o -name \*.txt \) -type f | xargs -n 10 nkf -Lu --overwrite
# make profgen_sse
# mv apery ../bin/apery

usi を入れてみたが、動いているぜ。

プロセス間通信のpublishのコードを書こう

NuGet。

PM> Install-Package RabbitMQ.Client -Version 4.1.1
Error   C1083   Cannot open include file: 'ev.h': No such file or directory

さて、なぜか。

C++用のライブラリは入ってないのか? じゃあ Ubuntu上で実験するか。

//#define UBUNTU

#ifdef UBUNTU
// プロセス間通信用
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>
#endif
#ifdef UBUNTU
// メッセージキューにエンキュー
static int bgp_enqueue(std::string message){

    auto* loop = EV_DEFAULT;

    AMQP::LibEvHandler handler{ loop };
    AMQP::Address address{ "amqp://localhost:5672" };
    AMQP::TcpConnection connection{ &handler, address };
    AMQP::TcpChannel channel{ &connection };

    std::string exchange_name = "myexchange";
    std::string queue_name = "1111";
    std::string routing_key = "";

    channel.declareQueue(queue_name)
        .onError([](const char* errMsg) {
            std::cerr<< "error declaring queue: " << errMsg << "\n";
        });

    channel.bindQueue(exchange_name, queue_name, routing_key)
        .onSuccess([&connection, &channel, &exchange_name, &routing_key, &message]() {

            if (! channel.publish(exchange_name, routing_key, message.c_str(), message.size())) {
                std::cerr<< "failed to publish?\n";
            }

            // break in ev loop.
            connection.close();
        });

    // We will monitor until the connection is lost. Execute channel.declareQueue( ... ).
    ev_run(loop);

    return 0;
}
#else
static int bgp_enqueue(std::string message) {
    std::cout << message << std::endl;
    return 0;
    //bgp_oss.clear();
    //bgp_oss << "あいうえお" << "";
    //bgp_oss.str();
}
#endif

こんな感じで。エラー出力は吐くが、それ以外は吐かない。

# rm gameserver.hpp
# nano gameserver.hpp
# make profgen_sse
# mv apery ../bin/apery

あっ、

#define UBUNTU

コメントアウトを外してやりなおし。

make[2]: Entering directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
g++ -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -fprofile-generate -lgcov -DNDEBUG -DHAVE_SSE4 -DHAVE_SSE42 -msse4.2  -o ../obj/main.o -c main.cpp
In file included from /usr/include/amqpcpp.h:56:0,
                 from gameserver.hpp:14,
                 from bitboard.hpp:25,
                 from main.cpp:23:
/usr/include/amqpcpp/message.h: In member function ‘void AMQP::Message::setBodySize(uint64_t)’:
/usr/include/amqpcpp/message.h:75:117: error: exception handling disabled, use -fexceptions to enable
 ::max() < size) throw std::runtime_error("message is too big for this system");
                                                                              ^

サイズが大きいって何のことだろう。

/usr/include/amqpcpp/message.h

    /**
     *  Set the body size
     *  This field is set when the header is received
     *  @param  uint64_t
     */
    void setBodySize(uint64_t size)
    {
        // safety-check: on 32-bit platforms size_t is obviously also a 32-bit dword
        // in which case casting the uint64_t to a size_t could result in truncation
        // here we check whether the given size fits inside a size_t
        if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error("message is too big for this system");

        // store the new size
        _bodySize = size;
    }

32bitシステム?

「std::numeric_limits::max」(cppreference.com)
http://en.cppreference.com/w/cpp/types/numeric_limits/max

サイズが短い文字列とか あるんだろうか?

「size_tは環境によって定義が変わるという話」(おおたの物置)
https://ota42y.com/blog/2014/11/08/size-t/

「64bit OS と 32bit OS でのデータ型の相違一覧」(drk7.jp)
http://www.drk7.jp/MT/archives/000851.html

「C++ string クラスの size や length は strlen とは違う」(ルーチェ's Homepage)
https://www.ruche-home.net/boyaki/2011-06-30/Cstrings

setBodySize( ) はどこで呼ばれているのか?

/usr/include/amqpcpp/ ディレクトリー下のソースには コール文は見つからない。

AMQP-CPP ライブラリが 64bit に対応してないんじゃないか?

fexceptions

「fexceptions」(Ogawa's Home Page)
http://www2.kobe-u.ac.jp/~lerl2/l_cc_p_10.1.008/doc/main_cls/mergedProjects/copts_cls/common_options/option_fexceptions.htm

Apery は -fno-exceptions を使っていて、例外処理は使いませんよ、と指定しているんだが、AMQP-CPP は例外処理を使っているということだろうか?

Makefile

CFLAGS   = -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp

fno-exceptions を使ってるな。外すか。

#CFLAGS   = -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp
# 例外処理を使うライブラリを使ったので-fno-exceptions を外した。
CFLAGS   = -std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp

コンパイルが進んでいる。

collect2: error: ld returned 1 exit status
Makefile:31: recipe for target 'apery' failed
make[2]: *** [apery] Error 1
make[2]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:43: recipe for target 'sse' failed
make[1]: *** [sse] Error 2
make[1]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:64: recipe for target 'profgen_sse' failed
make: *** [profgen_sse] Error 2

エラー。

SOURCES  = main.cpp bitboard.cpp init.cpp mt64bit.cpp position.cpp evalList.cpp \
           move.cpp movePicker.cpp square.cpp usi.cpp generateMoves.cpp evaluate.cpp \
           search.cpp hand.cpp tt.cpp timeManager.cpp book.cpp benchmark.cpp \
           thread.cpp common.cpp pieceScore.cpp

ここに足す必要があるんじゃないか?

SOURCES  = gameserver.cpp main.cpp bitboard.cpp init.cpp mt64bit.cpp position.cpp evalList.cpp \
# make profgen_sse
make CFLAGS='-std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -fprofile-generate -lgcov' LDFLAGS='-lpthread  -fprofile-generate -lgcov' sse
make[1]: Entering directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
make CFLAGS='-std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -fprofile-generate -lgcov -DNDEBUG -DHAVE_SSE4 -DHAVE_SSE42 -msse4.2' LDFLAGS='-lpthread  -fprofile-generate -lgcov -flto' apery
make[2]: Entering directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
g++ -o apery  -lpthread  -fprofile-generate -lgcov -flto -std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -fprofile-generate -lgcov -DNDEBUG -DHAVE_SSE4 -DHAVE_SSE42 -msse4.2
/usr/lib/gcc/x86_64-linux-gnu/5/../../../x86_64-linux-gnu/crt1.o: In function `_start':
(.text+0x20): undefined reference to `main'
collect2: error: ld returned 1 exit status
Makefile:32: recipe for target 'apery' failed
make[2]: *** [apery] Error 1
make[2]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:44: recipe for target 'sse' failed
make[1]: *** [sse] Error 2
make[1]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:65: recipe for target 'profgen_sse' failed
make: *** [profgen_sse] Error 2

メインがないのか?

SOURCES  = main.cpp gameserver.cpp bitboard.cpp init.cpp mt64bit.cpp position.cpp evalList.cpp \

これでもダメ。

Makefile:34: recipe for target '../obj/gameserver.o' failed
make[2]: *** [../obj/gameserver.o] Error 1
make[2]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:43: recipe for target 'sse' failed
make[1]: *** [sse] Error 2
make[1]: Leaving directory '/home/★user/shogi/ukamuse_sdt4_child4/src'
Makefile:64: recipe for target 'profgen_sse' failed
make: *** [profgen_sse] Error 2

「[翻訳] 64ビット環境移植における20個の問題 」(Geospatialにっき)
http://yukinarit.blog11.fc2.com/blog-entry-27.html

「20 issues of porting C++ code to the 64-bit platform」(PVS-Studio)
https://www.viva64.com/en/a/0004/#ID0EMEBK

    void setBodySize(uint64_t size)
    {
        // safety-check: on 32-bit platforms size_t is obviously also a 32-bit dword
        // in which case casting the uint64_t to a size_t could result in truncation
        // here we check whether the given size fits inside a size_t
//        if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error("message is too big for this system");
        if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error("message is too big for this system max=" + std::to_string(std::numeric_limits<size_t>::max()) + " size=" + std::to_string(size) );

        // store the new size
        _bodySize = size;
    }

こんなことしていいんだろうか?

「【C++】数値型をstring型に変換する複数の方法【int/double to string】」(MaryCore)
http://marycore.jp/prog/cpp/convert-number-to-string/

/usr/include/amqpcpp/message.h: In member function ‘void AMQP::Message::setBodySize(uint64_t)’:
/usr/include/amqpcpp/message.h:76:210: error: exception handling disabled, use -fexceptions to enable
         if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error("message is too big for this system max=" + std::to_string(std::numeric_limits<size_t>::max()) + " size=" + std::to_string(size) );

あれっ、こんな書き方はできない?

「文字列で学ぶC++入門」(Qiita)
http://qiita.com/7shi/items/cac7b3e9b90bf91b00cc

        if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error( ("message is too big for this system max=" + std::to_string(std::numeric_limits<size_t>::max()) + " size=" + std::to_string(size)).c_str() );

じゃあ、こうか。

/usr/include/amqpcpp/message.h: In member function ‘void AMQP::Message::setBodySize(uint64_t)’:
/usr/include/amqpcpp/message.h:76:221: error: exception handling disabled, use -fexceptions to enable
         if (std::numeric_limits<size_t>::max() < size) throw std::runtime_error( ("message is too big for this system max=" + std::to_string(std::numeric_limits<size_t>::max()) + " size=" + std::to_string(size)).c_str() );
                                                                                                                                                                                                                             ^

なんのこっちゃ。

-fno-exceptions

これ 付けたり外したりしてたらダメか。外しとこう。

benchmark.cpp:(.text+0xe0e): undefined reference to `ev_default_loop'
benchmark.cpp:(.text+0xe87): undefined reference to `AMQP::TcpConnection::TcpConnection(AMQP::TcpHandler*, AMQP::Address const&)'
benchmark.cpp:(.text+0xede): undefined reference to `AMQP::ChannelImpl::ChannelImpl()'
benchmark.cpp:(.text+0xfbe): undefined reference to `AMQP::ChannelImpl::attach(AMQP::Connection*)'
benchmark.cpp:(.text+0x11c8): undefined reference to `ev_run'
collect2: error: ld returned 1 exit status

そういえば、コンパイラオプションがあった気がするな。

-lev -lamqpcpp -pthread

この3つを付け足さないと。

Makefile

#CFLAGS   = -std=c++11 -fno-exceptions -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp
# 例外処理を使うライブラリ(AMQP-CPP)を使ったので-fno-exceptions を外した。
# RabbitMQ を使うので-lev -lamqpcpp を追加。
CFLAGS   = -std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -lev -lamqpcpp

コンパイルが通ったような。じゃあ また -fno-exceptions を付けてみよう。

やっぱエラーが出た。
やっぱり外す。

# ./consume.exe
found box      = [1111]
My ID watching = [amq.ctag-JpyKshTMjD_bM6_anhh7Qw]
find message   = []
find message   = []

Apery は空文字列を メッセージ・キュー「1111」に入れたみたいだ。なぜ空文字列なのだろう。

if (! channel.publish(exchange_name, routing_key, message.c_str(), strlen(message.c_str()) )) {
                                std::cerr << "failed to publish?\n";
                        }

適当にいじりすぎたか。

if (! channel.publish(exchange_name, routing_key, message.c_str(), message.size() )) {
                                std::cerr << "failed to publish?\n";
                        }

これでよかったか。

# make clean
# make profgen_sse
# ./consume.exe
found box      = [1111]
My ID watching = [amq.ctag-DYpzEdeU8No8uh0KoVn6fg]
find message   = []
find message   = []

publish はしてるんだが空っぽなのか。もっと調べてみよう。

std::string msg2 = "AiueoKakikukeko";
if (! channel.publish(exchange_name, routing_key, msg2.c_str(), msg2.size() )) {

これで試してみよう。

# ./consume.exe
found box      = [1111]
My ID watching = [amq.ctag-Clba0qSp8znhy88dGmUvcw]
find message   = [AiueoKakikukeko]
find message   = [AiueoKakikukeko]

ここは いけてるのか。

じゃあ 次は

#define BGP_COUT bgp_oss.clear(), std::cout 
#define BGP_ENDL std::endl, bgp_enqueue (bgp_oss.str())

これなんだが、

enum SyncCout {
    IOLock,
    IOUnlock
};
std::ostream& operator << (std::ostream& os, SyncCout sc);
#define SYNCCOUT BGP_COUT << IOLock
#define SYNCENDL BGP_ENDL << IOUnlock

これと組み合わせると

#define SYNCCOUT bgp_oss.clear(), std::cout << IOLock
#define SYNCENDL std::endl, bgp_enqueue (bgp_oss.str()) << IOUnlock

アンロックの場所がおかしくなってしまう。
BGP_ENDL だけ分解するか。

#define BGP_ENDL BGP_ENDL1, BGP_ENDL2
#define BGP_ENDL1 std::endl
#define BGP_ENDL2 bgp_enqueue (bgp_oss.str())

こうして、

#define SYNCENDL BGP_ENDL1 << IOUnlock, BGP_ENDL2

こう。

# ./consume.exe
found box      = [1111]
My ID watching = [amq.ctag-hpyixpnOKUhN5foCbpqdCQ]
find message   = []
find message   = []
find message   = []
find message   = []
find message   = []
find message   = []
find message   = []

それだけではダメか。

Windows 10 / Visual Studio 2015

[Configuration Properties] - [C/C++] - [Command Line] の [Additional Options]欄に

 -lev -lamqpcpp

と書いたら ev.h とか読込んでくれないだろうか?

cl : Command line warning D9002: ignoring unknown option '-lev'
1>cl : Command line warning D9002: ignoring unknown option '-lamqpcpp'

g++ 用の書き方だろうか。

コーディング・ミス発見

#define BGP_COUT bgp_oss.clear(), std::cout 

これは、

#define BGP_COUT bgp_oss.clear(), bgp_oss

こうだろう。

# ./consume.exe
found box      = [1111]
My ID watching = [amq.ctag-vhBo9rHCF9Cjil7N-iioTg]
find message   = [id name ukamuse_SDT4
id author Hiraoka Takuya

option name Best_Book_Move type check default false
option name Book_File type string default book/20150503/book.bin
option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
option name Clear_Hash type button
option name Draw_Ply type spin default 256 min 1 max 2147483647
option name Engine_Name type string default ukamuse_SDT4
option name Max_Book_Ply type spin default 32767 min 0 max 32767
option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
option name Max_Random_Score_Diff_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Score type spin default -180 min -32601 max 32601
option name Minimum_Thinking_Time type spin default 20 min 0 max 2147483647
option name Move_Overhead type spin default 30 min 0 max 5000
option name MultiPV type spin default 1 min 1 max 594
option name OwnBook type check default true
option name Slow_Mover type spin default 89 min 1 max 1000
option name Slow_Mover_10 type spin default 10 min 1 max 1000
option name Slow_Mover_16 type spin default 20 min 1 max 1000
option name Slow_Mover_20 type spin default 40 min 1 max 1000
option name Threads type spin default 2 min 1 max 256
option name Time_Margin type spin default 4500 min 0 max 2147483647
option name USI_Hash type spin default 256 min 1 max 1048576
option name USI_Ponder type check default true
usiok
]

どうよ!

浮かむ瀬に プロセス間通信のメッセージを受信させるには?

-現状 : 無限ループの中に ReadLine() を置いて制御をブロックしている
-これを : 丸ごと consume スレッドのループの中に移動させたい

このあたりは改造するしかない。

usi.cpp の doUSICommandLoop(...)関数の中に無限ループがあるんだが、これを関数化して外に出す。

search.hpp struct Searcher

    STATIC bool doUSICommandLoop_inner(int argc, char* argv[], Position pos, std::string cmd);

usi.cpp

void Searcher::doUSICommandLoop_inner(int argc, char* argv[], bool evalTableIsRead, std::string cmd, std::string token) {

}
void Searcher::doUSICommandLoop(int argc, char* argv[]) {
    Position pos(DefaultStartPositionSFEN, threads.main(), thisptr);

    std::string cmd;
    for (int i = 1; i < argc; ++i)
        cmd += std::string(argv[i]) + " ";

    // プロセス間通信のメッセージ受信方法に置き換えたい。
    bool continued;
    do {
        continued = Searcher::doUSICommandLoop_inner(argc, argv, pos, cmd);
    } while (continued);

    threads.main()->waitForSearchFinished();
}
bool evalTableIsRead = false;
bool Searcher::doUSICommandLoop_inner(int argc, char* argv[], Position pos, std::string cmd) {
    // ★中略
}

うーん、Searcher を引数にしたいな。

コード改造はダメだwwwww 壊してしまう。

メッセージの受信は 別スレッドで 回転式バッファーに入れておくようにしようwwwww

        std::istringstream ssCmd(cmd);

このインプット・ストリーム、

        else if (token == "go") go(pos, ssCmd);
        else if (token == "position") setPosition(pos, ssCmd);

さらに サブルーチンに回されてるんだが 何に使われてるんだろな。

void go(const Position& pos, std::istringstream& ssCmd) {
    LimitsType limits;
    std::string token;

    limits.startTime.restart();

    while (ssCmd >> token) {
        if      (token == "ponder"     ) limits.ponder = true;
        else if (token == "btime"      ) ssCmd >> limits.time[Black];
        else if (token == "wtime"      ) ssCmd >> limits.time[White];
        else if (token == "binc"       ) ssCmd >> limits.inc[Black];
        else if (token == "winc"       ) ssCmd >> limits.inc[White];
        else if (token == "infinite"   ) limits.infinite = true;
        else if (token == "byoyomi" || token == "movetime") ssCmd >> limits.moveTime;
        else if (token == "mate"       ) ssCmd >> limits.mate;
        else if (token == "depth"      ) ssCmd >> limits.depth;
        else if (token == "nodes"      ) ssCmd >> limits.nodes;
        else if (token == "searchmoves") {
            while (ssCmd >> token)
                limits.searchmoves.push_back(usiToMove(pos, token));
        }
    }
    if      (limits.moveTime != 0)
        limits.moveTime -= pos.searcher()->options["Byoyomi_Margin"];
    else if (pos.searcher()->options["Time_Margin"] != 0)
        limits.time[pos.turn()] -= pos.searcher()->options["Time_Margin"];
    pos.searcher()->threads.startThinking(pos, limits, pos.searcher()->states);
}

go 関数の中でさらに ループがあるのか。多重ループを1重ループに改造したいぜ。だが、その改造をするとまた壊してしまうwwwww

インプットストリームを グローバル変数にするか?

「文字列ストリームからの読み込み」(C++編【標準ライブラリ】 第29章 文字列ストリーム)
http://ppp-lab.sakura.ne.jp/ProgrammingPlacePlus/cpp/library/029.html

C++の入出力の使い方の勘が無くてつらい。

std::istringstream ssCmd(cmd);

これと、

std::istringstream ssCmd;

これで挙動が違うのな。C++分からん。

ラッピングできないのか

困ったときはラッピングを考えよう

しかし インプット・ストリングはなんで初期値を取ってるんだ?
標準入力から拾ってるんじゃないのか?

if (argc == 1 && !std::getline(std::cin, cmd))
    cmd = "quit";

標準入力の本体はこっちの、

std::getline(std::cin, cmd)

こいつじゃないか?

std::istringstream はただの、取り出すのが便利な 文字列なだけじゃないか?

// 「./apery」のように引数無しで打鍵して実行された場合に限り、なんども標準入力を待つ。
// 文字が打たれたらその文字を、空文字を打たれたら "quit" 扱いとする。
if (argc == 1 && !std::getline(std::cin, cmd))
    cmd = "quit";

つまりこう。

static std::istringstream bgp_dequeue(){
    std::string sourceString;
    if (!std::getline(std::cin, sourceString))
    {
        sourceString = "quit";
    }
    std::istringstream ssCmd(sourceString);
    return ssCmd;
}

こんな書き方でいいんだろうか? いや、

static std::string bgp_dequeue(){
    std::string sourceString;
    if (!std::getline(std::cin, sourceString))
    {
        sourceString = "quit";
    }
    return sourceString;
}

もっとシンプルに こうだろう。

if (argc == 1)
{
    cmd = bgp_dequeue();
}

これでは 動かない。ちょっと いじると壊れてしまう。

if (argc == 1)
{
    if (!std::getline(std::cin, cmd))
    {
        cmd = "quit";
    }
}

これだと動く。何が違うのか。

static std::string bgp_dequeue(std::string sourceString){
    if (!std::getline(std::cin, sourceString))
    {
        sourceString = "quit";
    }
    return sourceString;
}

こうしてもダメ。違いが分からん。sourceString は getline で上書きされるのだろうから関係ないか。

static std::basic_istream<char, std::char_traits<char>>& bgp_getline(std::string& sourceString) {
    return std::getline(std::cin, sourceString);
}

この難しい返り値は何だろう?
&が付いてるのでアドレスなんだろうか。該当がなければヌルで、ヌルは 0 で偽だ、とか そういうことなのか?

if (argc == 1)
{
    if (!bgp_getline(cmd))
    {
        cmd = "quit";
    }
    //if (!std::getline(std::cin, cmd))
    //{
    //  cmd = "quit";
    //}
}

これで結果が同じ。

static bool bgp_getline2(std::string& sourceString) {
    return !std::getline(std::cin, sourceString);
}

こうして、

if (argc == 1)
{
    //if (!bgp_getline(cmd))
    //{
    //  cmd = "quit";
    //}
    if (bgp_getline2(cmd))
    {
        cmd = "quit";
    }
}

こうすると結果が違う。どこが変わったのか。

標準出力の改造に 失敗してしまったんだろうか?
例えばここ。

static int bgp_enqueue(std::string message) {
    std::cout << message << std::endl;
    return 0;
}
static int bgp_enqueue(std::string message) {
    std::cout << IOLock << message << std::endl << IOUnlock;
    return 0;
}

こうした方がいいんだろうか?

だめだ~。

usi
id name Apery Debug Build
id author Hiraoka Takuya

option name Best_Book_Move type check default false
option name Book_File type string default book/20150503/book.bin
option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
option name Clear_Hash type button
option name Draw_Ply type spin default 256 min 1 max 2147483647
option name Engine_Name type string default Apery Debug Build
option name Max_Book_Ply type spin default 32767 min 0 max 32767
option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
option name Max_Random_Score_Diff_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Score type spin default -180 min -32601 max 32601
option name Minimum_Thinking_Time type spin default 20 min 0 max 2147483647
option name Move_Overhead type spin default 30 min 0 max 5000
option name MultiPV type spin default 1 min 1 max 594
option name OwnBook type check default true
option name Slow_Mover type spin default 89 min 1 max 1000
option name Slow_Mover_10 type spin default 10 min 1 max 1000
option name Slow_Mover_16 type spin default 20 min 1 max 1000
option name Slow_Mover_20 type spin default 40 min 1 max 1000
option name Threads type spin default 4 min 1 max 256
option name Time_Margin type spin default 4500 min 0 max 2147483647
option name USI_Hash type spin default 256 min 1 max 1048576
option name USI_Ponder type check default true
usiok

isready
id name Apery Debug Build
id author Hiraoka Takuya

option name Best_Book_Move type check default false
option name Book_File type string default book/20150503/book.bin
option name Byoyomi_Margin type spin default 500 min 0 max 2147483647
option name Clear_Hash type button
option name Draw_Ply type spin default 256 min 1 max 2147483647
option name Engine_Name type string default Apery Debug Build
option name Max_Book_Ply type spin default 32767 min 0 max 32767
option name Max_Random_Score_Diff type spin default 0 min 0 max 32600
option name Max_Random_Score_Diff_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Ply type spin default 32767 min 0 max 32767
option name Min_Book_Score type spin default -180 min -32601 max 32601
option name Minimum_Thinking_Time type spin default 20 min 0 max 2147483647
option name Move_Overhead type spin default 30 min 0 max 5000
option name MultiPV type spin default 1 min 1 max 594
option name OwnBook type check default true
option name Slow_Mover type spin default 89 min 1 max 1000
option name Slow_Mover_10 type spin default 10 min 1 max 1000
option name Slow_Mover_16 type spin default 20 min 1 max 1000
option name Slow_Mover_20 type spin default 40 min 1 max 1000
option name Threads type spin default 4 min 1 max 256
option name Time_Margin type spin default 4500 min 0 max 2147483647
option name USI_Hash type spin default 256 min 1 max 1048576
option name USI_Ponder type check default true
usiok
info string start setting eval table

おかしいだろ。

//#define SYNCCOUT BGP_COUT << IOLock
//#define SYNCENDL BGP_ENDL1 << IOUnlock, BGP_ENDL2
#define SYNCCOUT BGP_COUT
#define SYNCENDL BGP_ENDL1, BGP_ENDL2

これでどうか。ロックを外して BGP_ENDL2 の中でロックするんだぜ。

#define BGP_COUT bgp_oss.clear(), bgp_oss.flush(), bgp_oss

フラッシュとか要るのだろうか。

だめ。

#define BGP_ENDL2 bgp_oss.flush(), bgp_enqueue (bgp_oss.str())

じゃあ、こっちか?

だめか。

出力した全文を もう1回 出力している感じの不具合なのだろうか?

clear() が効いてないんじゃないか?

「How do you clear a stringstream variable?」(stack overflow)
http://stackoverflow.com/questions/20731/how-do-you-clear-a-stringstream-variable

static int bgp_enqueue(std::string message) {
    std::cout << IOLock << message << std::endl << IOUnlock;
    bgp_oss.str(std::string());// 空文字列を入れてクリアーする
    return 0;
}

これでどうか。

よしおっけ!

static std::basic_istream<char, std::char_traits<char>>& bgp_getline(std::string& sourceString) {
    return std::getline(std::cin, sourceString);
}
static bool bgp_getline2(std::string& sourceString) {
    return !std::getline(std::cin, sourceString);
}
static std::string bgp_dequeue(){
    std::string cmd;
    if (bgp_getline2(cmd))
    {
        cmd = "quit";
    }
    return cmd;
}

こんな感じにラッピングして。

プリコンパイル・ヘッダー

Error   C1010   unexpected end of file while looking for precompiled header. Did you forget to add '#include "stdafx.h"' to your source?

なんかまた こういうコンパイル・エラーが出てきた。

Debug を Release に変えたから設定も変わったのか。

Windows 10 と Ubuntu

Windows 10 版は IDE でテストできるが、Ubuntu 版は nano と g++ でやってるので プログラムが書けてるのが よくわからない。

gameserver.hpp

#pragma once

#include <string>
#include <iostream> // std::cout
#include <sstream> // std::ostringstream
#include "common.hpp"


// #define UBUNTU


// バックグラウンド・プロセス用の出力
// #define BGP_COUT std::cout
// #define BGP_ENDL std::endl
#define BGP_COUT bgp_oss
#define BGP_ENDL BGP_ENDL1, BGP_ENDL2
#define BGP_ENDL1 std::endl
#define BGP_ENDL2 bgp_enqueue (bgp_oss.str())

// 文字列ストリーム出力を、文字列に置換するもの
static std::ostringstream bgp_oss;


#ifdef UBUNTU
// OS      : Ubuntu 16.04
// Library : libev
//         : Install   : Command  : sudo apt-get update
//                                : sudo apt-get install libev-dev
// Service : RabbitMQ
//         : Reference : Web site : Top page http://www.rabbitmq.com/
//         : Install   : Web site : Installing on Debian / Ubuntu http://www.rabbitmq.com/install-debian.html
//         : Manual    : Command  : man rabbitmqctl
//         : Start     : Command  : rabbitmq-server
//         : Stop      : Command  : rabbitmqctl stop
//         : Check     : Command  : rabbitmqctl status
//         :           : Command  : rabbitmqctl list_queues
// Library : AMQP-CPP
//         : Reference : Web site : AMQP-CPP README.md https://github.com/CopernicaMarketingSoftware/AMQP-CPP
//
// gameserver.hpp

// プロセス間通信用
#include <ev.h>
#include <amqpcpp.h>
#include <amqpcpp/libev.h>

/// <summary>
/// 回転式バッファー。
/// これはメイン・スレッドに置く。
/// デキューのスレッドでエンキューすることはできない。
/// デキュー処理は、回転式バッファーを仲介にしてエンキュー処理にメッセージを渡す。
/// </summary>
namespace RotationBuffer
{
    const int bufferSize = 100;
    std::string* buffer = new std::string[bufferSize];
    int* bufferCursors = new int[] { 0, 0 };
    const int PUT_INDEX = 0;
    const int GET_INDEX = 1;
    void putMessage(std::string message)
    {
        buffer[bufferCursors[PUT_INDEX]] = message;
        bufferCursors[PUT_INDEX]++;
        if (!(bufferCursors[PUT_INDEX] < bufferSize))
        {
            bufferCursors[PUT_INDEX] = 0;
        }
    }
    std::string getMessage()
    {
        if ("" != buffer[bufferCursors[GET_INDEX]])
        {
            std::string message = buffer[bufferCursors[GET_INDEX]];
            buffer[bufferCursors[GET_INDEX]] = "";
            bufferCursors[GET_INDEX]++;
            if (!(bufferCursors[GET_INDEX] < bufferSize))
            {
                bufferCursors[GET_INDEX] = 0;
            }
            return message;
        }
        return "";
    }
}

const std::string destinationQueueName = "1113";

// メッセージキューにエンキュー
static int bgp_enqueue(std::string message){

    auto* loop = EV_DEFAULT;

    AMQP::LibEvHandler handler{ loop };
    AMQP::Address address{ "amqp://localhost:5672" };
    AMQP::TcpConnection connection{ &handler, address };
    AMQP::TcpChannel channel{ &connection };

    std::string exchange_name = "myexchange";
    std::string queue_name = "destinationQueueName";
    std::string routing_key = "";

    channel.declareQueue(queue_name)
        .onError([](const char* errMsg) {
            std::cerr << "error declaring queue: " << errMsg << "\n";
        });

    channel.bindQueue(exchange_name, queue_name, routing_key)
        .onSuccess([&connection, &channel, &exchange_name, &routing_key, &message]() {

            if (! channel.publish(exchange_name, routing_key, message.c_str(), message.size())) {
                std::cerr << "failed to publish?\n";
            }

            // break in ev loop.
            connection.close();
        });

    // We will monitor until the connection is lost. Execute channel.declareQueue( ... ).
    ev_run(loop);

    return 0;
}

// bgp_startConsume() しておくこと。
static std::string bgp_dequeue() {
    std::string message;

    while ("" == message)
    {
        message = RotationBuffer.getMessage();
    }

    return message;
}

static std::string bgp_dequeue() {
    std::string cmd;
    if (!std::getline(std::cin, cmd))
    {
        cmd = "quit";
    }
    return cmd;
}

const std::string sourceQueueName = "1112";

// メッセージ・キューの監視を開始
static void bgp_startConsume()
{
    // Connect to the AMQP service.
    auto *loop = EV_DEFAULT;
    AMQP::LibEvHandler handler(loop);
    AMQP::TcpConnection connection(&handler, AMQP::Address("amqp://localhost/"));
    AMQP::TcpChannel channel(&connection);

    // I will go to the front of the box named "1111".
    channel.declareQueue(sourceQueueName);

    // I look inside the box.
    auto errorCb = [](const char *errMsg) {
        std::cerr << "My ID watching failed [" << errMsg << "]" << std::endl;
    };
    auto messageCb = [&channel](const AMQP::Message &message, uint64_t deliveryTag, bool redelivered) {
        RotationBuffer.putMessage(message);

        channel.ack(deliveryTag);
    };
    channel.consume(sourceQueueName)
        .onReceived(messageCb)
        .onError(errorCb);

    // I will keep on forever.
    ev_run(loop, 0);

    // I will not come here.
    return;
}

#else
static int bgp_enqueue(std::string message) {
    std::cout << IOLock << message << std::endl << IOUnlock;
    bgp_oss.str(std::string());// 空文字列を入れてクリアーする
    return 0;
}

static std::string bgp_dequeue() {
    std::string cmd;
    if (!std::getline(std::cin, cmd))
    {
        cmd = "quit";
    }
    return cmd;
}

// メッセージ・キューの監視を開始
static void bgp_startConsume()
{
    // なにもしない
}
#endif

class gameserver
{
public:
    gameserver();
    ~gameserver();
};

(2017-03-13 追記: 上プログラムの std::string queue_name = "destinationQueueName"; は、std::string queue_name = destinationQueueName; の誤り)

腹も減ってきたしつらい。

C++の文法を知らなくてつらい。

In file included from bitboard.hpp:25:0,
                 from main.cpp:23:
gameserver.hpp:57:31: error: expected primary-expression before ‘]’ token
  int* bufferCursors = new int[] { 0, 0 };
                               ^
gameserver.hpp:57:40: error: too many initializers for ‘int [1]’
  int* bufferCursors = new int[] { 0, 0 };
                                        ^
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:130:27: error: expected primary-expression before ‘.’ token
   message = RotationBuffer.getMessage();
                           ^
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:136:20: error: redefinition of ‘std::__cxx11::string bgp_dequeue()’
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp:125:20: note: ‘std::__cxx11::string bgp_dequeue()’ previously defined here
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp: In lambda function:
gameserver.hpp:164:17: error: expected primary-expression before ‘.’ token
   RotationBuffer.putMessage(message);
                 ^

配列の初期化

「配列の要素の初期化」(自明でない日記)
http://d.hatena.ne.jp/tt4cs/20120219/1329636595

int bufferCursors[2] = { 0, 0 };

こんなんでいいのか。

const int bufferSize = 100;
std::string buffer[bufferSize] = {};

これもありか。

他のエラーはこんな感じ。

In file included from bitboard.hpp:25:0,
                 from main.cpp:23:
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:130:27: error: expected primary-expression before ‘.’ token
   message = RotationBuffer.getMessage();
                           ^
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:136:20: error: redefinition of ‘std::__cxx11::string bgp_dequeue()’
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp:125:20: note: ‘std::__cxx11::string bgp_dequeue()’ previously defined here
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp: In lambda function:
gameserver.hpp:164:17: error: expected primary-expression before ‘.’ token
   RotationBuffer.putMessage(message);
                 ^

ネームスペースとメソッドをつなぐのは「::」だったかな。

まだ残っているコンパイルエラーは。

In file included from bitboard.hpp:25:0,
                 from main.cpp:23:
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:136:20: error: redefinition of ‘std::__cxx11::string bgp_dequeue()’
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp:125:20: note: ‘std::__cxx11::string bgp_dequeue()’ previously defined here
 static std::string bgp_dequeue() {
                    ^
gameserver.hpp: In lambda function:
gameserver.hpp:164:37: error: could not convert ‘message’ from ‘const AMQP::Message’ to ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’
   rotationBuffer::putMessage(message);
                                     ^

同じメソッドで2個も作ってるじゃないか。コピペ忘れか。直した。

残ってるエラーは。

In file included from bitboard.hpp:25:0,
                 from main.cpp:23:
gameserver.hpp: In lambda function:
gameserver.hpp:155:37: error: could not convert ‘message’ from ‘const AMQP::Message’ to ‘std::__cxx11::string {aka std::__cxx11::basic_string<char>}’
   rotationBuffer::putMessage(message);
                                     ^

AMQP::Message 型は .body() で文字列を取れるんだったか。

        std::string myString(message.body(), message.bodySize());
        rotationBuffer::putMessage(myString);

こうだったか。直した。
まだエラーが出る。

../obj/bitboard.o:(.bss+0x891e40): multiple definition of `rotationBuffer::buffer[abi:cxx11]'
../obj/main.o:(.bss+0x20): first defined here
../obj/bitboard.o: In function `rotationBuffer::getMessage[abi:cxx11]()':

これと似たのがたくさん。重複定義がどこかにあるのか。

const int bufferSize = 100;
static std::string buffer[bufferSize] = {};

グローバル変数には static を付けないとダメなんだろうか?

グローバル関数にも static が必要そうだ。

修正して コンパイルは通った。

    対局
    ====
        usi
            usiok
        isready
            readyok
        usinewgame
        position sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1 moves
        go
            例) bestmove 2g2f ponder 1c1d
        quit

何も反応のない画面に向かって、タイミングを開けながら このコマンドを打ちたい。

# ./apery
usi
isready
usinewgame
position sfen lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1 moves
go
quit

quit が効かないので あやしい。

# rabbitmqctl list_queues
Listing queues ...
1112    0
1111    0
1113    0
myqueue 25

投げる先を間違えているのだろうか?

tamesi33a2_cs.cs がエンキュー、デキューの両方の機能を持っていただろうか。それを使ってテストしてみよう。

./apery はバックグラウンドで起動させる。

# ./apery &
[1] 16835
root@tk2-217-18401:/home/★user/shogi/ukamuse_sdt4_child4/bin# jobs
[1]+  Running                 ./apery &

tamesi33a2_cs.cs は対話タイプではなかったようだ。
対話型は tamesi33a1_cs.cs では?

tamesi33a1_cs.cs は 自分のエンキューしたメッセージに、daze を付けてデキューするだけなので対話的でない。

tamesi34_cs.cs を次回に作りたい。

C#でテスト用の tamesi34_cs.cs を作る。

Visual Studio 2015 で AMQP-CPP のテストができる C# 版でひとまず用意することにする。

  • エンキュー先メッセージ・キュー名を指定する
  • デキュー先メッセージ・キュー名を指定する
  • デキューは常に監視し、割込みさせる
  • 常に 標準入力は エンキューさせる

ソースコードはこんな感じでどうか?

// OS  : Windows 10
// IDE : Visual Studio 2015
//       Install : NuGet   : Install-Package RabbitMQ.Client -Version 4.1.1
//
// OS  : Ubuntu 16.04
//       Compile : Command : mcs /r:RabbitMQ.Client.dll -define:UBUNTU tamesi34_cs.cs
//               : Command : chmod 755 tamesi34_cs.cs
//       Execute : Command : // フォアグラウンドで実行する
//                         : ./tamesi34_cs.exe
//       Check   : Command : // キューの中身の数を調べる
//                         : rabbitmqctl list_queues
//--------------------------------------------------------------------------------
// tamesi34_cs.cs

// Ubuntu の RabbitMQ はソースのバージョンが古いのか、API が異なった。
// #define UBUNTU

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

namespace UsagiMQ
{
    /// <summary>
    /// メッセージを エンキュー、デキューします。
    /// エンキューは「1112」、デキューは「1113」キューに向けて行います。
    /// 
    /// 参照 : QueueDeclare http://docs.spring.io/spring-amqp-net/docs/1.0.x/api/html/Spring.Messaging.Amqp.Rabbit~Spring.Messaging.Amqp.Rabbit.Connection.CachedModel~QueueDeclare(String,Boolean,Boolean,Boolean,Boolean,Boolean,IDictionary).html
    /// 参照 : EventingBasicConsumer https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.Events.EventingBasicConsumer.html
    /// 参照 : BasicConsume https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.IModel.html#method-M:RabbitMQ.Client.IModel.BasicConsume(System.UInt16,System.String,System.Boolean,System.Collections.IDictionary,RabbitMQ.Client.IBasicConsumer)
    /// 参照 : C#でconstな配列を実現する (もっとクールにプログラミング) http://pgnote.net/?p=885
    /// </summary>
    class Program
    {
        public const string HOST_NAME = "localhost";
        public static string[] QUEUE_NAMES = new string[2];
        public const int ENQUEUE_INDEX = 0;
        public const int DEQUEUE_INDEX = 1;

        public static ConnectionFactory GetFactory()
        {
            if (null == m_factory_)
            {
                m_factory_ = new ConnectionFactory() { HostName = HOST_NAME };
            }
            return m_factory_;
        }
        static ConnectionFactory m_factory_;

        public static IConnection GetConnection()
        {
            if (null == m_connection_)
            {
                m_connection_ = GetFactory().CreateConnection();
            }
            return m_connection_;
        }
        static IConnection m_connection_;

        public static IModel GetChannel(int index)
        {
            if (null == m_channels_[index])
            {
                m_channels_[index] = GetConnection().CreateModel();
#if UBUNTU
                // Ubuntuでは何故か Spring.Messaging.Amqp.Rabbit の引数 7 つのやつになっている。
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], false, false, false, false, false, null);
#else
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], false, false, false, null);
#endif
            }
            return m_channels_[index];
        }
        static IModel[] m_channels_ = new IModel[2];

        public static EventingBasicConsumer GetConsumer(int index)
        {
            if (null == m_consumers_[index])
            {
#if UBUNTU
                // Ubuntuでは何故か v1.4.0 の引数が 0 個のやつになっている。調べたが引数が1個~6個のものは無かった。
                m_consumers_[index] = new EventingBasicConsumer();
#else
                m_consumers_[index] = new EventingBasicConsumer(GetChannel(index));
#endif

            }
            return m_consumers_[index];
        }
        static EventingBasicConsumer[] m_consumers_ = new EventingBasicConsumer[2];

        /// <summary>
        /// 受信できたときに割り込んでくる処理
        /// </summary>
#if UBUNTU
        public static BasicDeliverEventHandler GetDequeueHandler()
#else
        public static EventHandler<BasicDeliverEventArgs> GetDequeueHandler()
#endif
        {
            if (null == m_dequeueHandler_)
            {
#if UBUNTU
                m_dequeueHandler_ = new BasicDeliverEventHandler((model, ea) =>
#else
                m_dequeueHandler_ = new EventHandler<BasicDeliverEventArgs>((model, ea) =>
#endif
                {
                    byte[] body = ea.Body;
                    string message = Encoding.UTF8.GetString(body);
                    Console.WriteLine("<---- [interrupt!] Dequeue(^q^) {0}", message);
                });
            }

            return m_dequeueHandler_;
        }

#if UBUNTU
        static BasicDeliverEventHandler m_dequeueHandler_;
#else
        static EventHandler<BasicDeliverEventArgs> m_dequeueHandler_;
#endif

        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseConnection()
        {
            if (null != m_connection_)
            {
                m_connection_.Close();
                m_connection_ = null;
            }
        }
        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseChannel(int index)
        {
            if (null != m_channels_[index])
            {
                m_channels_[index].Close();
                m_channels_[index] = null;
            }
        }

        static void Main(string[] args)
        {
            string line;
            Console.WriteLine(@"エンキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)");
            line = Console.ReadLine();
            QUEUE_NAMES[ENQUEUE_INDEX] = line;

            Console.WriteLine(@"デキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)");
            line = Console.ReadLine();
            QUEUE_NAMES[DEQUEUE_INDEX] = line;

            StartConsume();

            Console.WriteLine(@"終了するときは[Ctrl]+[C]キーを押せだぜ☆(^~^)
エンキューするときはメッセージを打ち込んで[Enter]キーを押せだぜ☆(^◇^)");

            for (;;)
            {
                // "Hello World!" などを入力
                line = Console.ReadLine();
                Enqueue(line);
            }

            // ここには来ない
            // CloseConnection();
        }

        static void Enqueue(string message)
        {
            IModel channel = GetChannel(ENQUEUE_INDEX);

            byte[] body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish("", QUEUE_NAMES[ENQUEUE_INDEX], null, body);

            Console.WriteLine(" Enqueue(^q^) {0}", message);

            // 対応するオープンは無いが、ちゃんと閉じないと、レシーブしてくれない。
            CloseChannel(ENQUEUE_INDEX);
        }

        static void StartConsume()
        {
            IModel channel = GetChannel(DEQUEUE_INDEX);
            EventingBasicConsumer consumer = GetConsumer(DEQUEUE_INDEX);

            // 受信できたときに割り込んでくる処理
            consumer.Received += GetDequeueHandler();

#if UBUNTU
            // Ubuntuでは何故か引数が 5 個のやつになっている。
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, "", null, consumer);
#else
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, consumer);
#endif
            // 終了はさせない
            // consumer.Received -= GetReceiveHandler();
            // CloseChannel(DEQUEUE_INDEX);
        }
    }
}

これでどうか。

# ps aux | grep apery
root      2726  3.3  0.0 128660    48 ?        Sl   Mar11  88:51 /usr/bin/cli ./tamesi31_cs.exe ../ukamuse_sdt4/bin/apery
root     16835  0.0 14.9 1408108 151936 ?      Sl   Mar12   0:00 ./apery
root     22496  0.0  0.0  12936   988 pts/7    S+   03:47   0:00 grep --color=auto apery

apery は生きてるように見えるが反応はない。一度止めてみるか。

# rabbitmqctl list_queues
Listing queues ...
1112    0
1111    0
1113    0
myqueue 25

myqueue の内容もデキューしとこ。

# ./tamesi34_cs.exe
エンキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
myqueue
デキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
myqueue

Unhandled Exception:
RabbitMQ.Client.Exceptions.OperationInterruptedException: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text="PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'myqueue' in vhost '/': received 'false' but current is 'true'", classId=50, methodId=10, cause=
  at RabbitMQ.Client.Impl.SimpleBlockingRpcContinuation.GetReply () <0x41c3e0f0 + 0x00103> in <filename unknown>:0
  at RabbitMQ.Client.Impl.ModelBase.ModelRpc (RabbitMQ.Client.Impl.MethodBase method, RabbitMQ.Client.Impl.ContentHeaderBase header, System.Byte[] body) <0x41c3bfa0 + 0x000fd> in <filename unknown>:0
  at RabbitMQ.Client.Framing.Impl.v0_8.Model.QueueDeclare (System.String queue, Boolean passive, Boolean durable, Boolean exclusive, Boolean autoDelete, Boolean nowait, IDictionary arguments) <0x41c402b0 + 0x00113> in <filename unknown>:0
  at UsagiMQ.Program.GetChannel (Int32 index) <0x41c01c30 + 0x000ce> in <filename unknown>:0
  at UsagiMQ.Program.StartConsume () <0x41c01b70 + 0x0000f> in <filename unknown>:0
  at UsagiMQ.Program.Main (System.String[] args) <0x41bfe000 + 0x0008f> in <filename unknown>:0
[ERROR] FATAL UNHANDLED EXCEPTION: RabbitMQ.Client.Exceptions.OperationInterruptedException: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text="PRECONDITION_FAILED - inequivalent arg 'durable' for queue 'myqueue' in vhost '/': received 'false' but current is 'true'", classId=50, methodId=10, cause=
  at RabbitMQ.Client.Impl.SimpleBlockingRpcContinuation.GetReply () <0x41c3e0f0 + 0x00103> in <filename unknown>:0
  at RabbitMQ.Client.Impl.ModelBase.ModelRpc (RabbitMQ.Client.Impl.MethodBase method, RabbitMQ.Client.Impl.ContentHeaderBase header, System.Byte[] body) <0x41c3bfa0 + 0x000fd> in <filename unknown>:0
  at RabbitMQ.Client.Framing.Impl.v0_8.Model.QueueDeclare (System.String queue, Boolean passive, Boolean durable, Boolean exclusive, Boolean autoDelete, Boolean nowait, IDictionary arguments) <0x41c402b0 + 0x00113> in <filename unknown>:0
  at UsagiMQ.Program.GetChannel (Int32 index) <0x41c01c30 + 0x000ce> in <filename unknown>:0
  at UsagiMQ.Program.StartConsume () <0x41c01b70 + 0x0000f> in <filename unknown>:0
  at UsagiMQ.Program.Main (System.String[] args) <0x41bfe000 + 0x0008f> in <filename unknown>:0

接続した メッセージ・キュー と設定の違う方法でアクセスしたときに起こるエラーだろう。対応するコードを考える。

キューの寿命も質問するコード

これでどうだろうか?

// OS  : Windows 10
// IDE : Visual Studio 2015
//       Install : NuGet   : Install-Package RabbitMQ.Client -Version 4.1.1
//
// OS  : Ubuntu 16.04
//       Compile : Command : mcs /r:RabbitMQ.Client.dll -define:UBUNTU tamesi34_cs.cs
//               : Command : chmod 755 tamesi34_cs.cs
//       Execute : Command : // フォアグラウンドで実行する
//                         : ./tamesi34_cs.exe
//       Check   : Command : // キューの中身の数を調べる
//                         : rabbitmqctl list_queues
//
//       Library : Website : CopernicaMarketingSoftware/AMQP-CPP https://github.com/CopernicaMarketingSoftware/AMQP-CPP
//--------------------------------------------------------------------------------
// tamesi34_cs.cs

// Ubuntu の RabbitMQ はソースのバージョンが古いのか、API が異なった。
// #define UBUNTU

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

namespace UsagiMQ
{
    /// <summary>
    /// メッセージを エンキュー、デキューします。
    /// キューの名前は指定してください。
    /// デキューは割込みを受け付けます。
    /// 
    /// 参照 : QueueDeclare (v1.0) http://docs.spring.io/spring-amqp-net/docs/1.0.x/api/html/Spring.Messaging.Amqp.Rabbit~Spring.Messaging.Amqp.Rabbit.Connection.CachedModel~QueueDeclare(String,Boolean,Boolean,Boolean,Boolean,Boolean,IDictionary).html
    /// 参照 : EventingBasicConsumer https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.Events.EventingBasicConsumer.html
    /// 参照 : BasicConsume https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.IModel.html#method-M:RabbitMQ.Client.IModel.BasicConsume(System.UInt16,System.String,System.Boolean,System.Collections.IDictionary,RabbitMQ.Client.IBasicConsumer)
    /// 参照 : C#でconstな配列を実現する (もっとクールにプログラミング) http://pgnote.net/?p=885
    /// </summary>
    class Program
    {
        const string HOST_NAME = "localhost";
        static string[] QUEUE_NAMES = new string[2];
        /// <summary>
        /// キューの寿命
        /// (0) durable    : RabbitMQが止まってもキューを残す
        /// (1) autodelete : コンシューマーが1人も接続していなかったら消す
        /// (2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
        /// (3) exclusive  : この接続でだけ使える。この接続が切れたら消す
        /// </summary>
        static int[] lifeSpans_queue = new int[] { 0, 0 };
        static bool[]
            durable_lifeSpans = new bool[2],
            autodelete_lifeSpans = new bool[2],
            passive_lifeSpans = new bool[2],
            exclusive_lifeSpans = new bool[2];
        const int ENQUEUE_INDEX = 0;
        const int DEQUEUE_INDEX = 1;
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index_queue"></param>
        /// <param name="name_queue"></param>
        /// <param name="lifeSpan">
        /// (0) durable
        /// (1) autodelete
        /// (2) passive
        /// (3) exclusive
        /// </param>
        static void SetLifeSpan(int index_queue, string name_queue, int lifeSpan)
        {
            QUEUE_NAMES[index_queue] = name_queue;
            lifeSpans_queue[index_queue] = lifeSpan;

            // 一旦クリアー
            durable_lifeSpans[index_queue] = false;
            autodelete_lifeSpans[index_queue] = false;
            passive_lifeSpans[index_queue] = false;
            exclusive_lifeSpans[index_queue] = false;

            switch (lifeSpan)
            {
                case 0: // durable
                    durable_lifeSpans[index_queue] = true;
                    break;
                case 1: // autodelete
                    autodelete_lifeSpans[index_queue] = true;
                    break;
                case 3: // exclusive
                    exclusive_lifeSpans[index_queue] = true;
                    break;
                default: // passive
                    passive_lifeSpans[index_queue] = true;
                    break;
            }
        }


        public static ConnectionFactory GetFactory()
        {
            if (null == m_factory_)
            {
                m_factory_ = new ConnectionFactory() { HostName = HOST_NAME };
            }
            return m_factory_;
        }
        static ConnectionFactory m_factory_;

        public static IConnection GetConnection()
        {
            if (null == m_connection_)
            {
                m_connection_ = GetFactory().CreateConnection();
            }
            return m_connection_;
        }
        static IConnection m_connection_;

        public static IModel GetChannel(int index)
        {
            if (null == m_channels_[index])
            {
                m_channels_[index] = GetConnection().CreateModel();
#if UBUNTU
                // Ubuntuでは何故か Spring.Messaging.Amqp.Rabbit の引数 7 つのやつになっている。
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], passive_lifeSpans[index], durable_lifeSpans[index], exclusive_lifeSpans[index], autodelete_lifeSpans[index], false, null);
#else
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], durable_lifeSpans[index], exclusive_lifeSpans[index], autodelete_lifeSpans[index], null);
#endif
            }
            return m_channels_[index];
        }
        static IModel[] m_channels_ = new IModel[2];

        public static EventingBasicConsumer GetConsumer(int index)
        {
            if (null == m_consumers_[index])
            {
#if UBUNTU
                // Ubuntuでは何故か v1.4.0 の引数が 0 個のやつになっている。調べたが引数が1個~6個のものは無かった。
                m_consumers_[index] = new EventingBasicConsumer();
#else
                m_consumers_[index] = new EventingBasicConsumer(GetChannel(index));
#endif

            }
            return m_consumers_[index];
        }
        static EventingBasicConsumer[] m_consumers_ = new EventingBasicConsumer[2];

        /// <summary>
        /// 受信できたときに割り込んでくる処理
        /// </summary>
#if UBUNTU
        public static BasicDeliverEventHandler GetDequeueHandler()
#else
        public static EventHandler<BasicDeliverEventArgs> GetDequeueHandler()
#endif
        {
            if (null == m_dequeueHandler_)
            {
#if UBUNTU
                m_dequeueHandler_ = new BasicDeliverEventHandler((model, ea) =>
#else
                m_dequeueHandler_ = new EventHandler<BasicDeliverEventArgs>((model, ea) =>
#endif
                {
                    byte[] body = ea.Body;
                    string message = Encoding.UTF8.GetString(body);
                    Console.WriteLine("<---- [interrupt!] Dequeue(^q^) {0}", message);
                });
            }

            return m_dequeueHandler_;
        }

#if UBUNTU
        static BasicDeliverEventHandler m_dequeueHandler_;
#else
        static EventHandler<BasicDeliverEventArgs> m_dequeueHandler_;
#endif

        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseConnection()
        {
            if (null != m_connection_)
            {
                m_connection_.Close();
                m_connection_ = null;
            }
        }
        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseChannel(int index)
        {
            if (null != m_channels_[index])
            {
                m_channels_[index].Close();
                m_channels_[index] = null;
            }
        }

        static void Main(string[] args)
        {
            Console.Write(@"エンキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
Queue name? > ");
            string queueName_enqueue = Console.ReadLine();

            Console.Write(@"エンキュー先のメッセージ・キューの寿命を選んで入れろだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > ");
            int lifeSpan_enqueue = int.Parse( Console.ReadLine());
            SetLifeSpan(ENQUEUE_INDEX, queueName_enqueue, lifeSpan_enqueue);

            Console.Write(@"デキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
Queue name? > ");
            string queueName_dequeue = Console.ReadLine();

            Console.Write(@"デキュー先のメッセージ・キューの寿命を選んで入れろだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > ");
            int lifeSpan_dequeue = int.Parse(Console.ReadLine());

            StartConsume(queueName_dequeue, lifeSpan_dequeue);

            Console.Write(@"終了するときは[Ctrl]+[C]キーを押せだぜ☆(^~^)
エンキューするときはメッセージを打ち込んで[Enter]キーを押せだぜ☆(^◇^)
Enqueue? > ");

            for (;;)
            {
                // "Hello World!" などを入力
                string line = Console.ReadLine();
                Enqueue(line);
                Console.Write(@"Enqueue? > ");
            }

            // ここには来ない
            // CloseConnection();
        }

        static void Enqueue(string message)
        {
            IModel channel = GetChannel(ENQUEUE_INDEX);

            byte[] body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish("", QUEUE_NAMES[ENQUEUE_INDEX], null, body);

            Console.WriteLine(" Enqueue(^q^) {0}", message);

            // 対応するオープンは無いが、ちゃんと閉じないと、レシーブしてくれない。
            CloseChannel(ENQUEUE_INDEX);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name_queue">メッセージ・キューの名前</param>
        /// <param name="lifeSpan_queue">既存のメッセージ・キューの場合、メッセージ・キューの設定は合わせる必要がある。
        /// durable    : RabbitMQが止まってもキューを残す
        /// autodelete : コンシューマーが1人も接続していなかったら消す
        /// passive    : キューが存在するかどうかチェックするだけで、中身を見ない時はこれ
        /// exclusive  : この接続でだけ使える。この接続が切れたら消す
        /// </param>
        static void StartConsume(string name_queue, int lifeSpan_queue)
        {
            SetLifeSpan(DEQUEUE_INDEX, name_queue, lifeSpan_queue);

            IModel channel = GetChannel(DEQUEUE_INDEX);
            EventingBasicConsumer consumer = GetConsumer(DEQUEUE_INDEX);

            // 受信できたときに割り込んでくる処理
            consumer.Received += GetDequeueHandler();

#if UBUNTU
            // Ubuntuでは何故か引数が 5 個のやつになっている。
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, "", null, consumer);
#else
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, consumer);
#endif
            // 終了はさせない
            // consumer.Received -= GetReceiveHandler();
            // CloseChannel(DEQUEUE_INDEX);
        }
    }
}
デキュー先のメッセージ・キューの寿命を選んで入れろだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > 0

Unhandled Exception:
RabbitMQ.Client.Exceptions.OperationInterruptedException: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text="PRECONDITION_FAILED - inequivalent arg 'durable' for queue '1113' in vhost '/': received 'true' but current is 'false'", classId=50, methodId=10, cause=
  at RabbitMQ.Client.Impl.SimpleBlockingRpcContinuation.GetReply () <0x40246570 + 0x00107> in <filename unknown>:0
  at RabbitMQ.Client.Impl.ModelBase.ModelRpc (RabbitMQ.Client.Impl.MethodBase method, RabbitMQ.Client.Impl.ContentHeaderBase header, System.Byte[] body) <0x402442f0 + 0x00101> in <filename unknown>:0
  at RabbitMQ.Client.Framing.Impl.v0_8.Model.QueueDeclare (System.String queue, Boolean passive, Boolean durable, Boolean exclusive, Boolean autoDelete, Boolean nowait, IDictionary arguments) <0x402487b0 + 0x00113> in <filename unknown>:0
  at UsagiMQ.Program.GetChannel (Int32 index) <0x40209f90 + 0x00138> in <filename unknown>:0
  at UsagiMQ.Program.StartConsume (System.String name_queue, Int32 lifeSpan_queue) <0x40209eb0 + 0x0002b> in <filename unknown>:0
  at UsagiMQ.Program.Main (System.String[] args) <0x40205d50 + 0x000df> in <filename unknown>:0
[ERROR] FATAL UNHANDLED EXCEPTION: RabbitMQ.Client.Exceptions.OperationInterruptedException: The AMQP operation was interrupted: AMQP close-reason, initiated by Peer, code=406, text="PRECONDITION_FAILED - inequivalent arg 'durable' for queue '1113' in vhost '/': received 'true' but current is 'false'", classId=50, methodId=10, cause=
  at RabbitMQ.Client.Impl.SimpleBlockingRpcContinuation.GetReply () <0x40246570 + 0x00107> in <filename unknown>:0
  at RabbitMQ.Client.Impl.ModelBase.ModelRpc (RabbitMQ.Client.Impl.MethodBase method, RabbitMQ.Client.Impl.ContentHeaderBase header, System.Byte[] body) <0x402442f0 + 0x00101> in <filename unknown>:0
  at RabbitMQ.Client.Framing.Impl.v0_8.Model.QueueDeclare (System.String queue, Boolean passive, Boolean durable, Boolean exclusive, Boolean autoDelete, Boolean nowait, IDictionary arguments) <0x402487b0 + 0x00113> in <filename unknown>:0
  at UsagiMQ.Program.GetChannel (Int32 index) <0x40209f90 + 0x00138> in <filename unknown>:0
  at UsagiMQ.Program.StartConsume (System.String name_queue, Int32 lifeSpan_queue) <0x40209eb0 + 0x0002b> in <filename unknown>:0
  at UsagiMQ.Program.Main (System.String[] args) <0x40205d50 + 0x000df> in <filename unknown>:0

寿命を何に設定したのか 忘れたときはどうするのか……。

キュー削除機能を付けたものの、属性が分からない

属性が分からないと削除できないんだろうか?

// OS  : Windows 10
// IDE : Visual Studio 2015
//       Install : NuGet   : Install-Package RabbitMQ.Client -Version 4.1.1
//
// OS  : Ubuntu 16.04
//       Compile : Command : mcs /r:RabbitMQ.Client.dll -define:UBUNTU tamesi34_cs.cs
//               : Command : chmod 755 tamesi34_cs.cs
//       Execute : Command : // フォアグラウンドで実行する
//                         : ./tamesi34_cs.exe
//       Check   : Command : // キューの中身の数を調べる
//                         : rabbitmqctl list_queues
//
// Library : RabbitMQ
//           Refference : Website : RabbitMQ http://www.rabbitmq.com/
//                      : Website : RabbitMQ管理コマンド(rabbitmqctl)使い方 (Qiita) http://qiita.com/tamikura@github/items/5293cda4c0026b2d7022
//                      : Website : amqpを使ってRabbitMQのキューを操作する (Qiita) http://qiita.com/tamikura@github/items/a268afa51c5537ca4fe6
//
// Library : AMQP-CPP
//           Refference : Website : CopernicaMarketingSoftware/AMQP-CPP https://github.com/CopernicaMarketingSoftware/AMQP-CPP
//--------------------------------------------------------------------------------
// tamesi34_cs.cs

// Ubuntu の RabbitMQ はソースのバージョンが古いのか、API が異なった。
// #define UBUNTU

using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using System;
using System.Text;

namespace UsagiMQ
{
    /// <summary>
    /// メッセージを エンキューします。
    /// キューの名前は指定してください。
    /// デキューは割込みを受け付けます。
    /// 
    /// 参照 : QueueDeclare (v1.0) http://docs.spring.io/spring-amqp-net/docs/1.0.x/api/html/Spring.Messaging.Amqp.Rabbit~Spring.Messaging.Amqp.Rabbit.Connection.CachedModel~QueueDeclare(String,Boolean,Boolean,Boolean,Boolean,Boolean,IDictionary).html
    /// 参照 : EventingBasicConsumer https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.Events.EventingBasicConsumer.html
    /// 参照 : BasicConsume https://www.rabbitmq.com/releases/rabbitmq-dotnet-client/v1.4.0/rabbitmq-dotnet-client-1.4.0-net-2.0-htmldoc/type-RabbitMQ.Client.IModel.html#method-M:RabbitMQ.Client.IModel.BasicConsume(System.UInt16,System.String,System.Boolean,System.Collections.IDictionary,RabbitMQ.Client.IBasicConsumer)
    /// 参照 : C#でconstな配列を実現する (もっとクールにプログラミング) http://pgnote.net/?p=885
    /// </summary>
    class Program
    {
        const int ENQUEUE_INDEX = 0;
        const int DEQUEUE_INDEX = 1;
        const int DELETEQUEUE_INDEX = 2;
        const int NUM_INDEX = 3;
        const string HOST_NAME = "localhost";
        static string[] QUEUE_NAMES = new string[NUM_INDEX];
        /// <summary>
        /// キューの寿命
        /// (0) durable    : RabbitMQが止まってもキューを残す
        /// (1) autodelete : コンシューマーが1人も接続していなかったら消す
        /// (2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
        /// (3) exclusive  : この接続でだけ使える。この接続が切れたら消す
        /// </summary>
        static int[] lifeSpans_queue = new int[NUM_INDEX];
        static bool[]
            durable_lifeSpans = new bool[NUM_INDEX],
            autodelete_lifeSpans = new bool[NUM_INDEX],
            passive_lifeSpans = new bool[NUM_INDEX],
            exclusive_lifeSpans = new bool[NUM_INDEX];
        /// <summary>
        /// 
        /// </summary>
        /// <param name="index_queue"></param>
        /// <param name="name_queue"></param>
        /// <param name="lifeSpan">
        /// (0) durable
        /// (1) autodelete
        /// (2) passive
        /// (3) exclusive
        /// </param>
        static void SetLifeSpan(int index_queue, string name_queue, int lifeSpan)
        {
            QUEUE_NAMES[index_queue] = name_queue;
            lifeSpans_queue[index_queue] = lifeSpan;

            // 一旦クリアー
            durable_lifeSpans[index_queue] = false;
            autodelete_lifeSpans[index_queue] = false;
            passive_lifeSpans[index_queue] = false;
            exclusive_lifeSpans[index_queue] = false;

            switch (lifeSpan)
            {
                case 0: // durable
                    durable_lifeSpans[index_queue] = true;
                    break;
                case 1: // autodelete
                    autodelete_lifeSpans[index_queue] = true;
                    break;
                case 3: // exclusive
                    exclusive_lifeSpans[index_queue] = true;
                    break;
                default: // passive
                    passive_lifeSpans[index_queue] = true;
                    break;
            }
        }


        public static ConnectionFactory GetFactory()
        {
            if (null == m_factory_)
            {
                m_factory_ = new ConnectionFactory() { HostName = HOST_NAME };
            }
            return m_factory_;
        }
        static ConnectionFactory m_factory_;

        public static IConnection GetConnection()
        {
            if (null == m_connection_)
            {
                m_connection_ = GetFactory().CreateConnection();
            }
            return m_connection_;
        }
        static IConnection m_connection_;

        public static IModel GetChannel(int index)
        {
            if (null == m_channels_[index])
            {
                m_channels_[index] = GetConnection().CreateModel();
#if UBUNTU
                // Ubuntuでは何故か Spring.Messaging.Amqp.Rabbit の引数 7 つのやつになっている。
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], passive_lifeSpans[index], durable_lifeSpans[index], exclusive_lifeSpans[index], autodelete_lifeSpans[index], false, null);
#else
                m_channels_[index].QueueDeclare(QUEUE_NAMES[index], durable_lifeSpans[index], exclusive_lifeSpans[index], autodelete_lifeSpans[index], null);
#endif
            }
            return m_channels_[index];
        }
        static IModel[] m_channels_ = new IModel[NUM_INDEX];

        public static EventingBasicConsumer GetConsumer(int index)
        {
            if (null == m_consumers_[index])
            {
#if UBUNTU
                // Ubuntuでは何故か v1.4.0 の引数が 0 個のやつになっている。調べたが引数が1個~6個のものは無かった。
                m_consumers_[index] = new EventingBasicConsumer();
#else
                m_consumers_[index] = new EventingBasicConsumer(GetChannel(index));
#endif

            }
            return m_consumers_[index];
        }
        static EventingBasicConsumer[] m_consumers_ = new EventingBasicConsumer[NUM_INDEX];

        /// <summary>
        /// 受信できたときに割り込んでくる処理
        /// </summary>
#if UBUNTU
        public static BasicDeliverEventHandler GetDequeueHandler()
#else
        public static EventHandler<BasicDeliverEventArgs> GetDequeueHandler()
#endif
        {
            if (null == m_dequeueHandler_)
            {
#if UBUNTU
                m_dequeueHandler_ = new BasicDeliverEventHandler((model, ea) =>
#else
                m_dequeueHandler_ = new EventHandler<BasicDeliverEventArgs>((model, ea) =>
#endif
                {
                    byte[] body = ea.Body;
                    string message = Encoding.UTF8.GetString(body);
                    Console.WriteLine("<---- [interrupt!] Dequeue(^q^) {0}", message);
                });
            }

            return m_dequeueHandler_;
        }

#if UBUNTU
        static BasicDeliverEventHandler m_dequeueHandler_;
#else
        static EventHandler<BasicDeliverEventArgs> m_dequeueHandler_;
#endif

        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseConnection()
        {
            if (null != m_connection_)
            {
                m_connection_.Close();
                m_connection_ = null;
            }
        }
        /// <summary>
        /// 対応するオープンは無いけれど、開けたら閉める、を完璧に対応する必要がある。
        /// </summary>
        static void CloseChannel(int index)
        {
            if (null != m_channels_[index])
            {
                m_channels_[index].Close();
                m_channels_[index] = null;
            }
        }

        static void Main(string[] args)
        {
            //----------------------------------------
            // Delete
            //----------------------------------------
            for (;;)
            {
                Console.Write(@"削除したいキューがあれば名前を、無ければ空文字列を入れろだぜ☆(^~^)
キュー名を入力    : キューを削除します
空文字列で[Enter] : 次のステップへ進む
Name or empty ? > ");
                string queueName_delete = Console.ReadLine();
                if (""== queueName_delete.Trim())
                {
                    break;
                }

                Console.Write(@"削除するメッセージ・キューの寿命を選べだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > ");
                int lifeSpan_delete = int.Parse(Console.ReadLine());
                SetLifeSpan(DELETEQUEUE_INDEX, queueName_delete, lifeSpan_delete);
                uint result = DeleteQueue();
                Console.WriteLine(@"["+ queueName_delete + "]キューを削除したはずだぜ☆(^~^) result=["+ result + "]");
            }

            //----------------------------------------
            // Enqueue settings
            //----------------------------------------
            for (;;)
            {
                Console.Write(@"エンキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
Queue name? > ");
                string queueName_enqueue = Console.ReadLine();

                Console.Write(@"エンキュー先のメッセージ・キューの寿命を選べだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > ");
                int lifeSpan_enqueue;
                if(int.TryParse(Console.ReadLine(),out lifeSpan_enqueue))
                {
                    SetLifeSpan(ENQUEUE_INDEX, queueName_enqueue, lifeSpan_enqueue);
                    break;
                }
            }

            //----------------------------------------
            // Enqueue settings
            //----------------------------------------
            for (;;)
            {
                Console.Write(@"デキュー先のメッセージ・キューの名前を入れろだぜ☆(^~^)
Queue name? > ");
                string queueName_dequeue = Console.ReadLine();

                Console.Write(@"デキュー先のメッセージ・キューの寿命を選べだぜ☆(^~^)
(0) durable    : RabbitMQが止まってもキューを残す
(1) autodelete : コンシューマーが1人も接続していなかったら消す
(2) passive    : キューが存在するかどうかチェックするだけ。中身見ない時これ
(3) exclusive  : この接続でだけ使える。この接続が切れたら消す
Number ? > ");
                int lifeSpan_dequeue;
                if(int.TryParse(Console.ReadLine(),out lifeSpan_dequeue))
                {
                    StartConsume(queueName_dequeue, lifeSpan_dequeue);
                    break;
                }
            }

            Console.Write(@"終了するときは[Ctrl]+[C]キーを押せだぜ☆(^~^)
エンキューするときはメッセージを打ち込んで[Enter]キーを押せだぜ☆(^◇^)
Enqueue? > ");
            for (;;)
            {
                // "Hello World!" などを入力
                string line = Console.ReadLine();
                Enqueue(line);
                Console.Write(@"Enqueue? > ");
            }

            // ここには来ない
            // CloseConnection();
        }

        static uint DeleteQueue()
        {
            IModel channel = GetChannel(DELETEQUEUE_INDEX);
            uint result = channel.QueueDelete(QUEUE_NAMES[DELETEQUEUE_INDEX],true,true);

            // 対応するオープンは無いが、ちゃんと閉じないと、レシーブしてくれない。
            CloseChannel(DELETEQUEUE_INDEX);
            return result;
        }

        static void Enqueue(string message)
        {
            IModel channel = GetChannel(ENQUEUE_INDEX);

            byte[] body = Encoding.UTF8.GetBytes(message);
            channel.BasicPublish("", QUEUE_NAMES[ENQUEUE_INDEX], null, body);

            Console.WriteLine(" Enqueue(^q^) {0}", message);

            // 対応するオープンは無いが、ちゃんと閉じないと、レシーブしてくれない。
            CloseChannel(ENQUEUE_INDEX);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="name_queue">メッセージ・キューの名前</param>
        /// <param name="lifeSpan_queue">既存のメッセージ・キューの場合、メッセージ・キューの設定は合わせる必要がある。
        /// (0) durable    : RabbitMQが止まってもキューを残す
        /// (1) autodelete : コンシューマーが1人も接続していなかったら消す
        /// (2) passive    : キューが存在するかどうかチェックするだけで、中身を見ない時はこれ
        /// (3) exclusive  : この接続でだけ使える。この接続が切れたら消す
        /// </param>
        static void StartConsume(string name_queue, int lifeSpan_queue)
        {
            SetLifeSpan(DEQUEUE_INDEX, name_queue, lifeSpan_queue);

            IModel channel = GetChannel(DEQUEUE_INDEX);
            EventingBasicConsumer consumer = GetConsumer(DEQUEUE_INDEX);

            // 受信できたときに割り込んでくる処理
            consumer.Received += GetDequeueHandler();

#if UBUNTU
            // Ubuntuでは何故か引数が 5 個のやつになっている。
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, "", null, consumer);
#else
            channel.BasicConsume( QUEUE_NAMES[DEQUEUE_INDEX], true, consumer);
#endif
            // 終了はさせない
            // consumer.Received -= GetReceiveHandler();
            // CloseChannel(DEQUEUE_INDEX);
        }
    }
}

でも、これ2匹で プロセス間通信のテストができるようになった。

サンプル・プログラムを別記した

記事 : http://qiita.com/muzudho1/items/dd85b10c139255a28f43

じゃあ、これを使って Apery をバックグラウンドで動かしてテストしてみる。

Apery の方にも コマンドライン引数に キュー名と寿命を付けたいが

例えば

--RabbitMQ 1114 durable 1115 durable

みたいな。書き足せないだろうか。

--MsgQueue 1114 durable 1115 durable

の方が汎用的か?

改造中の浮かむ瀬の

    do {
        // 「./apery」のように引数無しで打鍵して実行された場合に限り、なんども標準入力を待つ。
        // 文字が打たれたらその文字を、空文字を打たれたら "quit" 扱いとする。
        if (argc == 1)
        {
            cmd = bgp_dequeue();
        }

この部分の argc == 1 に改造がいる。

恐らく スペース区切りで分割すると「--MsgQueue」「1114」「durable」「1115」「durable」みたいに細切れにされてしまうだろう。

ところで浮かむ瀬は

    for (int i = 1; i < argc; ++i)
    {
        cmd += std::string(argv[i]) + " ";
    }

と書いているが、argv[0] を飛ばしているので、コマンドライン引数だけを cmd に入れなおしているのだろう。

そして コマンドライン引数を 最初に入力されるコマンド として解釈しているのだろう。

Unicode の Code page 1200 だと UBUNTU で文字化け

やっぱ UTF-8のBOM有り Code page 65001 の方がいいのか。

浮かむ瀬のコマンドライン引数とコマンド

なんか

  • コマンドライン引数を付けずに ./apery で実行したら対話式、
  • コマンドライン引数を付けたら コマンド1個で実行終了

という縛りがあるんで、これを崩す。

    bool dialogue = false;
    // 「./apery」のように引数無しで打鍵して実行された場合に限り、なんども標準入力を待つ。
    if (argc == 1)
    {
        dialogue = true;
    }
        if (token == "--msgqueue") {
#if UBUNTU
            ssCmd >> bgp_queueName_enqueue;
            ssCmd >> bgp_lifeSpan_enqueue;
            ssCmd >> bgp_queueName_dequeue;
            ssCmd >> bgp_lifeSpan_dequeue;
            dialogue = true;
#else
            std::cerr << "Command line parameter error : \"--msgqueue\" option for UBUNTU.";
#endif
        }
    } while (token != "quit" && dialogue);

なんかこんな感じで改造。

あっ、オプションをつけ忘れていた。

Makefile

# Windows10 と Ubuntu ではライブラリのAPIが異なったので、-DUBUNTU も追加。
CFLAGS   = -std=c++11 -fno-rtti -Wextra -Ofast -MMD -MP -fopenmp -lev -lamqpcpp -DUBUNTU

するとエラーが出た。

In file included from bitboard.hpp:25:0,
                 from main.cpp:23:
gameserver.hpp: In function ‘std::__cxx11::string bgp_dequeue()’:
gameserver.hpp:150:13: error: ‘RotationBuffer’ was not declared in this scope
   message = RotationBuffer.getMessage();
             ^
# ./apery --msgqueue 1114 durable 1115 durable &
[1] 28591
root@tk2-217-18401:/home/csg10/shogi/ukamuse_sdt4_child4/bin# jobs
[1]+  Running                 ./apery --msgqueue 1114 durable 1115 durable &

ひとまず稼働。
ログイン、ログアウトしてみる。

# ps aux | grep apery
root      2726  3.3  0.0 128660    48 ?        Sl   Mar11 100:11 /usr/bin/cli ./tamesi31_cs.exe ../ukamuse_sdt4/bin/apery
root     16835  0.0 14.9 1408108 151936 ?      Sl   Mar12   0:00 ./apery
root     28591  0.0 14.3 1408116 145876 ?      Sl   09:19   0:00 ./apery --msgqueue 1114 durable 1115 durable
root     28745  0.0  0.0  12936   984 pts/8    S+   09:26   0:00 grep --color=auto apery

とりあえず うしろで割り込み待機状態になっているようだ。

root@tk2-217-18401:~# kill 2726
root@tk2-217-18401:~# kill 16835
root@tk2-217-18401:~# ps aux | grep apery
root     28591  0.0 14.3 1408116 145876 ?      Sl   09:19   0:00 ./apery --msgqueue 1114 durable 1115 durable
root     28771  0.0  0.0  12936   984 pts/8    S+   09:28   0:00 grep --color=auto apery

この浮かむ瀬と プロセス間通信できれば 話しは早いんだが。

終了するときは[Ctrl]+[C]キーを押せだぜ☆(^~^)
エンキューするときはメッセージを打ち込んで[Enter]キーを押せだぜ☆(^◇^)
Enqueue? > usi
 Enqueue(^q^) usi
Enqueue? >

反応はない。これは 長そうだ。

# rabbitmqctl list_queues
Listing queues ...
1112    0
1111    0
1113    0
myqueue 25
1115    1
1114    0

1115 には多分「usi」が入っているんだろう。
バックグラウンドの浮かむ瀬は止まっているんだろうか?

長くなったので次の記事へ

0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2