前回の記事 : 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」が入っているんだろう。
バックグラウンドの浮かむ瀬は止まっているんだろうか?
#長くなったので次の記事へ