2015年4月21日火曜日

伊豆市リトルシニア 静岡蒲原シニアと練習試合やりました

久しぶりの野球ネタ


既に入団して早1年が過ぎ去りました...時間が経つのも早いものです。
入団以来、野球ネタは書いてなかったのですが、これからちょっとづつ書こうかとは思います。

今日は天城グラウンドで静岡蒲原シニアさんとの練習試合でした、若干曇りぎみ、雨もちょっとパラつきましたが、試合に支障ない程度で良かってです。

 

なんと!、女子が入団しました


まじかっ! 女子の入団だってぇー!

本日はたまたまだったのですが、新1年生で女子が入団するということで、みんなとはちょっと遅れた入団式だったのですが立ち会うことができました。


練習だって結構ハードなのに、この中で野球をやっていこうって凄い決断ですね、頭が下がります、たいしたもんです。

入団おめでとうございます、3年間頑張ってください。

男どもも負けてられないですね...

試合のほう


本日3試合、私は審判として出場しました(体力がないので交代制ですが)、野球のことはあまり詳しく知らないので試合の気の利いた説明とかはできませんが、1年前に比べたらみんなプレーが格好良く見えました。


とりあえず、お疲れさまでした、こう皆で並ぶとなかなか逞しくなったなぁーと少し感じる近頃です。


本日も審判業務が終了しました、で、最後はこれで締めましょう...


皆さん、朝早くから1日ご苦労さまでした、また頑張ります...

2015年4月18日土曜日

MySQL-5.6.24へQ4Mを入れ込んだgentooのebuildをつくりました

Q4Mを再び...


Socket遊びが終わらない和人くんはほっといて、Q4Mの新しいバージョンをgentooで使えるようにebuildを作成しました。 前回はMySQLの5.1用のプラグインとして作りましたが、もう古いということで新しめのMySQLとQ4Mで作り直しました。



インストール


既にMySQLをインストールされている方は気をつけてください まずこうして
karky7 ~ # layman -a karky7
バージョン指定でこうする
karky7 ~ # emerge -u "=dev-db/mysql-5.6.24::karky7"
サービスを開始
karky7 ~ # systemctl start mysqld
Q4Mのプラグインをインストール
karky7 ~ # mysql -u root < /usr/share/mysql/support-files/install.sql
確認
karky7 ~ # mysql -u root
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 4
Server version: 5.6.24-log Source distribution

Copyright (c) 2000, 2015, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show plugins;
+----------------------------+--------+--------------------+--------------------+---------+
| Name                       | Status | Type               | Library            | License |
+----------------------------+--------+--------------------+--------------------+---------+
| binlog                     | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| mysql_native_password      | ACTIVE | AUTHENTICATION     | NULL               | GPL     |
| mysql_old_password         | ACTIVE | AUTHENTICATION     | NULL               | GPL     |
| sha256_password            | ACTIVE | AUTHENTICATION     | NULL               | GPL     |
| MEMORY                     | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| MRG_MYISAM                 | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| CSV                        | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| MyISAM                     | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| PERFORMANCE_SCHEMA         | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| BLACKHOLE                  | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| InnoDB                     | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| INNODB_TRX                 | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_LOCKS               | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_LOCK_WAITS          | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMP                 | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMP_RESET           | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMPMEM              | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMPMEM_RESET        | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMP_PER_INDEX       | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_CMP_PER_INDEX_RESET | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_BUFFER_PAGE         | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_BUFFER_PAGE_LRU     | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_BUFFER_POOL_STATS   | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_METRICS             | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_DEFAULT_STOPWORD | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_DELETED          | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_BEING_DELETED    | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_CONFIG           | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_INDEX_CACHE      | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_FT_INDEX_TABLE      | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_TABLES          | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_TABLESTATS      | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_INDEXES         | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_COLUMNS         | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_FIELDS          | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_FOREIGN         | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_FOREIGN_COLS    | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_TABLESPACES     | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| INNODB_SYS_DATAFILES       | ACTIVE | INFORMATION SCHEMA | NULL               | GPL     |
| ARCHIVE                    | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| partition                  | ACTIVE | STORAGE ENGINE     | NULL               | GPL     |
| QUEUE                      | ACTIVE | STORAGE ENGINE     | libqueue_engine.so | GPL     |
+----------------------------+--------+--------------------+--------------------+---------+
42 rows in set (0.00 sec)
でQUEUEが入ります
 

Q4Mの罠


Q4Mのソースを追加してビルドをすると、ディフォルトのebuildでは以下のエラーでビルドできませんした。
...
...
...
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/static_assert.hpp:119:10: 備考: in expansion of macro ‘BOOST_JOIN’
          BOOST_JOIN(boost_static_assert_typedef_, __LINE__)
          ^
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/optional/optional.hpp:333:8: 備考: in expansion of macro ‘BOOST_STATIC_ASSERT’
        BOOST_STATIC_ASSERT ( ::boost::mpl::not_<is_reference_predicate>::value ) ;
        ^
In file included from /usr/include/features.h:366:0,
                 from /usr/include/assert.h:35,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.3/include/g++-v4/cassert:43,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.cc:1:
/usr/include/bits/mathcalls.h: 大域スコープ:
/usr/include/bits/mathcalls.h:153:1: エラー: declaration of C function ‘long double pow(long double, long double)’ conflicts with
 __MATHCALL (pow,, (_Mdouble_ __x, _Mdouble_ __y));
 ^
/usr/include/bits/mathcalls.h:153:1: エラー: previous declaration ‘double pow(double, double)’ here
 __MATHCALL (pow,, (_Mdouble_ __x, _Mdouble_ __y));
 ^
In file included from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.3/include/g++-v4/complex:44:0,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/type_traits/is_complex.hpp:12,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/type_traits.hpp:81,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/spirit/home/classic/core/parser.hpp:12,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/spirit/home/classic/core.hpp:29,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/spirit/include/classic_core.hpp:11,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/boost/spirit/core.hpp:25,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.cc:4:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.3/include/g++-v4/cmath: 関数 ‘long double std::pow(long double, long double)’ 内:
/usr/lib/gcc/x86_64-pc-linux-gnu/4.8.3/include/g++-v4/cmath:411:39: エラー: ‘long double std::pow(long double, long double)’ conflicts with previous using declaration ‘long double pow(long double, long double)’
   pow(long double __x, long double __y)
                                       ^
In file included from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.cc:13:0:
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.h: メンバ関数 ‘queue_cond_t::value_t queue_cond_t::pow_func::bop(const queue_cond_t::value_t&, const queue_cond_t::value_t&) const’ 内:
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.h:274:69: エラー: オーバーロードされている ‘pow(const long long int&, const long long int&)’ の呼び出しが曖昧です
       return value_t::int_value(static_cast<long long>(powl(x.l, y.l)));
                                                                     ^
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.h:274:69: 備考: 候補:
In file included from /usr/include/features.h:366:0,
                 from /usr/include/assert.h:35,
                 from /usr/lib/gcc/x86_64-pc-linux-gnu/4.8.3/include/g++-v4/cassert:43,
                 from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.cc:1:
/usr/include/bits/mathcalls.h:153:1: 備考: double pow(double, double)
 __MATHCALL (pow,, (_Mdouble_ __x, _Mdouble_ __y));
 ^
<コマンドライン>:0:6: 備考: long double pow(long double, long double)
In file included from /home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.cc:13:0:
/home/cuomo/workspace/mysql/mysql-5.6.23/storage/q4m/src/queue_cond.h:275:5: 警告: 制御が非 void 関数の終りに到達しました [-Wreturn-type]
     }
     ^
storage/q4m/CMakeFiles/queue.dir/build.make:57: ターゲット 'storage/q4m/CMakeFiles/queue.dir/src/queue_cond.cc.o' のレシピで失敗しました
make[2]: *** [storage/q4m/CMakeFiles/queue.dir/src/queue_cond.cc.o] エラー 1
CMakeFiles/Makefile2:596: ターゲット 'storage/q4m/CMakeFiles/queue.dir/all' のレシピで失敗しました
make[1]: *** [storage/q4m/CMakeFiles/queue.dir/all] エラー 2
Makefile:136: ターゲット 'all' のレシピで失敗しました
make: *** [all] エラー 2
...
...
pow()関数がconflictしてると起こられます、色々調べてみましたがどうやら、cmakeのオプションの -DWITH_ZLIB=system がQ4Mだとダメらしく、これを -DWITH_ZLIB=bundled にする必要があるようです、このオプションはCOMPRESS()関数などで使うらしいのですが、これをMySQL提供のzlibにするか、システムにインストールされているzlibを利用するかの選択のオプションで、ディフォルトだとsystemになっています。

gentooのディフォルトのebuildが-DWITH_ZLIB=systemでハードコードされていたので、これを修正しました。 多分、どちらのzlibでも動くと思うのですが、たぶんヘッダーのincludeに問題があるような気がします、RedHatでも同じエラーが出てるような気がしました。 時間があれば、ソースコードを追っかけてみます、っていってそのままが多いのですが...

最後にcmakeのオプションサンプル


cmakeのオプションはこんな感じ、参考程度に、MySQL-5.6.23ですが....
cmake --no-warn-unused-cli \
-DCMAKE_INSTALL_PREFIX=/usr \
-DCMAKE_C_FLAGS_RELWITHDEBINFO=-DNDEBUG \
-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=-DNDEBUG \
-DCMAKE_INSTALL_PREFIX=/usr \
-DMYSQL_DATADIR=/var/lib/mysql \
-DSYSCONFDIR=/etc/mysql \
-DINSTALL_BINDIR=bin \
-DINSTALL_DOCDIR=share/doc/mysql-5.6.23 \
-DINSTALL_DOCREADMEDIR=share/doc/mysql-5.6.23 \
-DINSTALL_INCLUDEDIR=include/mysql \
-DINSTALL_INFODIR=share/info \
-DINSTALL_LIBDIR=lib64 \
-DINSTALL_ELIBDIR=lib64/mysql \
-DINSTALL_MANDIR=share/man \
-DINSTALL_MYSQLDATADIR=/var/lib/mysql \
-DINSTALL_MYSQLSHAREDIR=share/mysql \
-DINSTALL_MYSQLTESTDIR=share/mysql/mysql-test \
-DINSTALL_PLUGINDIR=lib64/mysql/plugin \
-DINSTALL_SBINDIR=sbin \
-DINSTALL_SCRIPTDIR=share/mysql/scripts \
-DINSTALL_SQLBENCHDIR=share/mysql \
-DINSTALL_SUPPORTFILESDIR=/usr/share/mysql \
-DWITH_UNIT_TESTS=OFF \
-DWITH_LIBEDIT=0 \
-DWITH_ZLIB=system \
-DWITHOUT_LIBWRAP=1 \
-DENABLED_LOCAL_INFILE=1 \
-DMYSQL_UNIX_ADDR=/var/run/mysqld/mysqld.sock \
-DINSTALL_UNIX_ADDRDIR=/var/run/mysqld/mysqld.sock \
-DWITH_SSL=system \
-DWITH_DEFAULT_COMPILER_OPTIONS=0 \
-DWITH_DEFAULT_FEATURE_SET=0 \
-DENABLE_DTRACE=OFF \
-DWITH_EDITLINE=bundled \
-DDEFAULT_CHARSET=utf8 \
-DDEFAULT_COLLATION=utf8_general_ci \
-DEXTRA_CHARSETS=all \
-DMYSQL_USER=mysql \
-DMYSQL_UNIX_ADDR=/var/run/mysqld/mysqld.sock \
-DDISABLE_SHARED=OFF \
-DWITH_debug=OFF \
-DWITH_DEBUG=OFF \
-DWITH_Debug=OFF \
-DWITH_EMBEDDED_SERVER=OFF \
-DWITH_profiling=OFF \
-DWITH_PROFILING=OFF \
-DWITH_Profiling=OFF \
-DENABLE_DTRACE=OFF \
-DWITH_ARCHIVE_STORAGE_ENGINE=1 \
-DWITH_BLACKHOLE_STORAGE_ENGINE=1 \
-DWITH_CSV_STORAGE_ENGINE=1 \
-DWITH_HEAP_STORAGE_ENGINE=1 \
-DWITH_INNOBASE_STORAGE_ENGINE=1 \
-DWITH_MYISAMMRG_STORAGE_ENGINE=1 \
-DWITH_MYISAM_STORAGE_ENGINE=1 \
-DWITH_PARTITION_STORAGE_ENGINE=1 \
-DWITH_FEDERATED_STORAGE_ENGINE=OFF \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_INSTALL_DO_STRIP=OFF



2015年4月12日日曜日

和人くんのSO_REUSEADDRオプション癖が抜けない件

和人くん、マジで知ってる!? socketのSO_REUSEADDRオプション


最近マジ、和人のソケット使いが荒すぎて困ってます、そんな時は

「まだ使われていますので、後ほどご利用ください...」

と丁寧にお断りしてください。


 ※画像はイメージです

それでは本題です


サーバー系のコードを見たりしてると、socketを作成しbind()する前にsetsockopt()でSO_REUSEADDRするのがほとんどですが、素直に...

「何でだよ、何でだよ、」

って2回ぐらいおもってしまいましたので、調べました、というか、ほとんど忘れてました。

後から気づきましたが、このソケットオプションを試すのに、こんなやりすぎたコードは必要なかったのですが、書いてしまったので載せます、コードが汚いので何をやっているのか先に図をご確認ください


動作の流れは、

  • 「和人かどうかわからないプロセス」「たぶん和人だと思われるプロセス」をforkします(これがサーバープロセスです)
  • 「たぶん和人だと思われるプロセス」はポート9000でLISTENします
  • 「和人かどうかわからないプロセス」はその後さらに「和人の子」をforkします(これがクライアントプロセスです)
  • 「和人の子」は最大5個まで創られます(どうやって創られたかは不明です)
  • 「和人の子」がいなくなると「和人かどうかわからないプロセス」はSIGCHLDシグナルを受けとりますので、しっかりwaipid()で受け止めて葬り去ります 
process数が腐らないようにsleep()で逃げたり、シグナルハンドラの中でこの関数呼んじゃダメでしょ、とか突っ込まないようにお願いします。


ビルド方法


Linux
$ gcc -g -O0 so_reuseaddr.c -o no_reuse ・・・ SO_REUSEADDRなし
$ gcc -g -O0 so_reuseaddr.c -o no_reuse -DKAZUTO・・・SO_REUSEADDRあり

Solaris
cc -g -O0 so_reuseaddr.c -lsocket -lnsl -o no_reuse
cc -g -O0 so_reuseaddr.c -lsocket -lnsl -DKAZUTO -o reuse
とやりますとそれぞれの実行可能形式のファイルが出来上がります。

試してみる


* SO_REUSEADDRオプションを有効にしない場合
cuomo@karky7 ~ $ ./no_reuse 
和人は創りました(1号) pid=4243
和人の子 pid=4243 は暇人なので 9秒だけ寝るw..
和人は創りました(2号) pid=4245
和人の子 pid=4245 は暇人なので 9秒だけ寝るw..
和人は創りました(3号) pid=4246
和人の子 pid=4246 は暇人なので 6秒だけ寝るw..
和人は創りました(4号) pid=4264
和人の子 pid=4264 は暇人なので 7秒だけ寝るw..
和人は創りました(5号) pid=4265
和人の子 pid=4265 は暇人なので 3秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58599 <-- accept()が起きた
和人の子供がオワタ(5号) 4246
和人は創りました(5号) pid=4266
和人の子 pid=4266 は暇人なので 3秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58600
...
...
和人が親なんて信じられるかっ!! もう出るわ!
和人の子供がオワタ(5号) 4266
和人は創りました(5号) pid=4273
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58605
和人が親なんて信じられるかっ!! もう出るわ!
和人の子供がオワタ(5号) 4269
和人は創りました(5号) pid=4274
割り込み
...
...
cuomo@karky7 ~ $ ./no_reuse <----- 2回目の実行
和人は創りました(1号) pid=4277
ああぁー、やっちまった...: Address already in use <---- まだ使えない
和人の子供がオワタ(1号) 4276
和人は創りました(1号) pid=4278
和人の子 pid=4278 は暇人なので 3秒だけ寝るw..
和人の子 pid=4277 は暇人なので 3秒だけ寝るw..
和人は創りました(2号) pid=4280
和人の子 pid=4280 は暇人なので 3秒だけ寝るw..
割り込み
...
...
cuomo@karky7 ~ $ netstat -tpn
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 192.168.254.21:58603      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:9000       192.168.254.21:58605      TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58602      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:9000       192.168.254.21:58604      TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58599      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58600      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58601      192.168.254.21:9000       TIME_WAIT   -                   
cuomo@karky7 ~ $

SO_REUSEADDRオプションをつけないと2回め以降のbind()が「Address already in use」で失敗し、サーバープロセスが起動できません。よって和人の子は孤児のまま彷徨う事になります。

ようするに

 「192.168.254.21:9000へbind()したいのですが、まだそのコネクションがTIME_WAIT状態で残っているのでダメですよ」

ってこと、じゃSO_REUSEADDRをつけるとどうなるのと言えば...

* SO_REUSEADDRを有効にした場合

「和人! Socketをリユーズするんだ!」でソケットにオプションを追加しています
~ $ ./reuse
和人は創りました(1号) pid=5966
和人! Socketをリユーズするんだ! <----- SO_REUSEADDRをいれる
和人の子 pid=5966 は暇人なので 6秒だけ寝るw..
和人は創りました(2号) pid=5967
和人の子 pid=5967 は暇人なので 3秒だけ寝るw..
和人は創りました(3号) pid=5969
和人の子 pid=5969 は暇人なので 7秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58689
和人の子供がオワタ(3号) 5967
和人は創りました(3号) pid=5970
...
...
和人の子供がオワタ(4号) 5969
和人は創りました(4号) pid=5977
和人の子 pid=5977 は暇人なので 8秒だけ寝るw..
和人は創りました(5号) pid=5994
和人の子 pid=5994 は暇人なので 8秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58694
和人の子供がオワタ(5号) 5973
和人は創りました(5号) pid=5996
和人の子 pid=5996 は暇人なので 4秒だけ寝るw..
割り込み
~ $ ./reuse
和人は創りました(1号) pid=5999
和人! Socketをリユーズするんだ!
和人の子 pid=5999 は暇人なので 5秒だけ寝るw..
和人は創りました(2号) pid=6001
和人の子 pid=6001 は暇人なので 8秒だけ寝るw..
和人は創りました(3号) pid=6002
和人の子 pid=6002 は暇人なので 8秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58695
和人の子供がオワタ(3号) 5999
和人は創りました(3号) pid=6003
...
...
和人の子 pid=6028 は暇人なので 3秒だけ寝るw..
和人が親なんて信じられるかっ!! もう出るわ!
接続してきた和人の子 ... 192.168.254.21, 和人ポート 58702
和人の子供がオワタ(4号) 6026
和人は創りました(4号) pid=6029
和人の子 pid=6029 は暇人なので 3秒だけ寝るw..
~ $ ./reuse
和人! Socketをリユーズするんだ!
和人は創りました(1号) pid=6033
和人の子 pid=6033 は暇人なので 9秒だけ寝るw..
和人は創りました(2号) pid=6034
和人の子 pid=6034 は暇人なので 7秒だけ寝るw..
和人は創りました(3号) pid=6036
和人は創りました(4号) pid=6037
...
...

~ $ netstat -tpn
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp        0      0 192.168.254.21:58698      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58700      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58704      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58699      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58703      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:9000       192.168.254.21:58702      TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58701      192.168.254.21:9000       TIME_WAIT   -                   
tcp        0      0 192.168.254.21:58702      192.168.254.21:9000       TIME_WAIT   -    
TIME_WAITが残ってても、必ずbind()が成功する。

サーバーを再起動とかかけたときにSO_REUSEADDRをしてないと、コネクションが綺麗に始末されるまでサーバーを起動できないって事になりかねませんね、これは

危ない!

ということで、サーバーを書くときはこのオプションをつけましょうというお話、

まぁ和人はどうでもいいが...

もう、和人のSocketネタはつまらないからやめる...

最後にコード


#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <time.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

/* Solaris: cc -g -O0 so_reuseaddr.c -lsocket -lnsl -o no_reuse */
/*          cc -g -O0 so_reuseaddr.c -lsocket -lnsl -DKAZUTO -o reuse */

/* Linux:   gcc -g -O0 so_reuseaddr.c -o no_reuse */
/*          gcc -g -O0 so_reuseaddr.c -DKAZUTO -o reuse */
int process = 0;

void sig_child(int);
void child_func(void);
void server_func(void);
void set_sockaddrin(struct sockaddr_in *, int);

int main(void)
{
  pid_t pid;

  struct sigaction sa, osa;
  sigemptyset(&sa.sa_mask);
  sa.sa_flags = 0;
  sa.sa_handler = &sig_child;
  sa.sa_flags |= SA_RESTART;
  sigaction(SIGCHLD, &sa, &osa);

  pid = fork();
  if(pid == 0) {
    server_func();
  } else if(pid < 0) {
    perror("しょっぱなから無理でしょ");
    return -1;
  }

  while(1) {
    if(process < 5) {
      pid = fork();
      if(pid == 0) {
        child_func();
      } else if(pid < 0) {
        perror("和人は小作りに失敗しました");
      } else {
        process++;
        printf("和人は創りました(%d号) pid=%d\n", process, pid);
      }
    }
    sleep(2);
  }
  return 1;
}

void server_func(void)
{
  int sock, result, con;
  socklen_t len;
  struct sockaddr_in serv, cli;
  char buff[1024];
  const char *p;
#ifdef KAZUTO
  /* SO_REUSEADDRを有効にする場合は1、無効にする場合は0をセット */
  int reuseaddr = 1;
#endif

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if(sock == -1) {
    perror("ソケット...どうしてくれんだ");
    exit(EXIT_FAILURE);
  }
  set_sockaddrin(&serv, sizeof(serv));

#ifdef KAZUTO
  puts("和人! Socketをリユーズするんだ!");
  result = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
        (const void *) &reuseaddr, sizeof(int));
  if(result == -1) {
    perror("和人はできまい、、がセットできないよぉー\n");
    exit(EXIT_FAILURE);
  }
#endif

  if (bind(sock, (struct sockaddr*)&serv, sizeof(serv)) == -1) {
    perror("ああぁー、やっちまった...");
    exit(EXIT_FAILURE);
  }

  if(listen(sock, 15) < 0) {
    perror("ああぁー、リッスンゴレライ...");
    exit(EXIT_FAILURE);
  }
  
  while(1) {
    len = sizeof(cli);
    con = accept(sock, (struct sockaddr*)&cli, &len);
    if(con < 0) {
      perror("なにが起こった和人くん!");
    } else {
      p = inet_ntop(AF_INET, &cli.sin_addr, buff, sizeof(buff));
      printf("接続してきた和人の子 ... %s, 和人ポート %d\n",
             p,
             ntohs(cli.sin_port));
      close(con);
    }
  }
}

void child_func(void)
{
  int sock;
  int r, result;
  time_t t;
  pid_t pid;
  struct sockaddr_in serv;
  
  pid = getpid();

  sock = socket(AF_INET, SOCK_STREAM, 0);
  if(sock == -1) {
    perror("ソケット...そそそ");
    exit(EXIT_FAILURE);
  }
  set_sockaddrin(&serv, sizeof(serv));

  r = 0;
  while(r < 3) {
    t = time(NULL); 
    srand(t);
    r = rand() % 10;
  }

  printf("和人の子 pid=%d は暇人なので %d秒だけ寝るw..\n", pid, r);
  sleep(r);

  if(connect(sock, (struct sockaddr*)&serv, sizeof(serv)) < 0) {
    perror("和人め裏切ったか...");
    exit(1);
  }

  puts("和人が親なんて信じられるかっ!! もう出るわ!");
  close(sock);
  exit(1);
}

void sig_child(int no)
{
  pid_t pid;
  int stat;
  while((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
    printf("和人の子供がオワタ(%d号) %d\n", process, pid);
    if(process > 0) {
      process--;
    }
  }
  return;
}

void set_sockaddrin(struct sockaddr_in *serv, int size)
{
  bzero(serv, size);
  serv->sin_family = AF_INET;
  inet_pton(AF_INET, "192.168.254.21", &serv->sin_addr.s_addr);
  serv->sin_port = htons(9000);
}


Socket遊びは止めるんだ... 和人...


2015年4月9日木曜日

和人君とUnix Domain Socketでプロセス間通信をやってみた

ソケットのままでも通信できるんですね

成長しないHaskellばかりの毎日ですが、訳あってソケットまわりを調べる機会が訪れてしまい、ほぼソケットまわりを忘れていまして再度調べたのでメモ...

Unixドメインソケット


プロセス間通信だとpipeとかありますが、ドメインソケットでもそれっぽい事ができます。

プロセスを2つ作って、互いに通信するよくあるコードですが...コードが単調でグチャグチャになってしまったので先にやってることを図にしますと、こうなってます

まず、和人プロセスが実行されます

和人プロセスは、socketpair()でペアなソケットを作成します、関数の説明は「manを見てください」と言うことで、こんな感じです、socketpair()でSOCK_STREAMを指定してストリームパイプを作成してます、この関数はUnixドメインソケットで利用可能です。

それから和人プロセスは、子供を作ります、どうやって創ったか...は不思議ですが、するとこうなります


和人プロセスは、子供を生んだ直後に「いらないコネクション」閉じちゃいます、でも和人プロセスはもう一本あることを知っています、これを使って和人プロセスは子供にデータを送信します。

さらにその後、和人プロセスと子プロセスはそれぞれ自分でスレッドを1つ作成します。スレッドについては「manを見てください」とお願いしまして、こうなります。

そのスレッドのエントリ関数はparent_thread_func()とchild_thread_func()です、これは強制的に一定間隔で子プロセスから親プロセスへデータを送りつけるための関数です(和人プロセスは無理やりやられます)

大まかにはこんな感じです、では、コードはこちらです、Unixドメインソケットってパイプみたいに使えるのですね。

コードです


実行しますとターミナルへの出力が混ざっちゃいますので注意して見てください..
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

#define BUFFSIZE 4096

static void parent(void);
static void child(void);

void parent_thread_func(void);
void child_thread_func(void);

/* UNIX DOMAIN SOCKET */
int sock[2];

int main(void)
{
  int result;
  pid_t pid, cid;

  result = socketpair(AF_UNIX, SOCK_STREAM, 0, sock);
  if(result < 0) {
    printf("ソケットぺあ失敗だぜ errno=%s(%d)", strerror(errno), errno);
    return EXIT_FAILURE;
  }

  cid = fork();
  switch(cid) {
  case 0:
    child();
    break;
  case -1:
    printf("フォーク失敗だぜ");
    exit(EXIT_FAILURE);
  default:
    parent();
    break;
  }

  return 0;
}

static void parent(void)
{
  char buff[BUFFSIZE] = "Kazuto";
  int index = 0, val, bytes = 0, result, tret;
  ssize_t n;
  socklen_t len;
  struct msghdr msg;
  struct iovec iov[2];
  struct cmsghdr cmsghdr;
  pthread_t tid;

  iov[0].iov_base = buff;
  iov[0].iov_len = sizeof(buff);
  iov[1].iov_base = &index;
  iov[1].iov_len = sizeof(index);

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = iov;
  msg.msg_iovlen = 2;

#ifdef __gnu_linux__
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
#else
  msg.msg_accrights = NULL;
  msg.msg_accrightslen = 0;
#endif

  len = sizeof(val);
  result = getsockopt(sock[1], SOL_SOCKET, SO_SNDBUF, &val, &len);
  if(result < 0) {
    printf("のっけからダメかよ... errno=%s(%d)", strerror(errno), errno);
    exit(EXIT_FAILURE);
  }
  printf("ソケットの送信バッファサイズはこんだけ = %d", val);

  close(sock[0]);

  tret = pthread_create(&tid, NULL, (void*)parent_thread_func, NULL);
  if(tret != 0) {
    printf("スレッドできねぇーし");
    exit(EXIT_FAILURE);
  }

  for(;;) {
    n = sendmsg(sock[1], &msg, 0);
    if(n >= 0) {
      printf("ソケットへ和人をブチ込んだバイト数 = %d", (int)n);
      index++;
      bytes += iov[0].iov_len + iov[1].iov_len;
      printf("ブチバイト数の計算あってればこんなもんだろ = %d", bytes);
    } else {
      printf("ああぁーやっちまった errno=%s(%d)", strerror(errno), errno);
    }
  }
}

void parent_thread_func(void)
{
  char buff[BUFFSIZE];
  int index;
  ssize_t n;
  struct msghdr msg;
  struct iovec iov[2];

  iov[0].iov_base = buff;
  iov[0].iov_len = sizeof(buff);
  iov[1].iov_base = &index;
  iov[1].iov_len = sizeof(index);

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = iov;
  msg.msg_iovlen = 2;

#ifdef __gnu_linux__
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
#else
  msg.msg_accrights = NULL;
  msg.msg_accrightslen = 0;
#endif

  for(;;) {
    n = recvmsg(sock[1], &msg, 0);
    if(n >= 0) {
      printf("(裏)和人きたバイト数 ret = %d", (int)n);
      printf("   iovec[0]=%s", buff);
      printf("   iovec[0].size=%d", (int)msg.msg_iov[0].iov_len);
      printf("   iovec[1]=%d", index);
      printf("   iovec[1].size=%d", (int)msg.msg_iov[1].iov_len);
      sleep(5);
    } else {
      printf("(裏)ここはレアだぜ errno=%s(%d)", strerror(errno), errno);
    }
  }
}

static void child(void)
{
  char buff[BUFFSIZE];
  int index = 0, val, bytes = 0, result, tret;;
  ssize_t n;
  socklen_t len;
  struct msghdr msg;
  struct iovec iov[2];
  pthread_t tid;

  iov[0].iov_base = buff;
  iov[0].iov_len = sizeof(buff);
  iov[1].iov_base = &index;
  iov[1].iov_len = sizeof(index);

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = iov;
  msg.msg_iovlen = 2;

#ifdef __gnu_linux__
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
#else
  msg.msg_accrights = NULL;
  msg.msg_accrightslen = 0;
#endif

  len = sizeof(val);
  result = getsockopt(sock[0], SOL_SOCKET, SO_RCVBUF, &val, &len);
  if(result < 0) {
    printf("socketぺあー errno=%s(%d)", strerror(errno), errno);
    exit(EXIT_FAILURE);
  }
  printf("ソケットの受信バッファサイズはこんだけ = %d", val);

  close(sock[1]);

  tret = pthread_create(&tid, NULL, (void*)child_thread_func, NULL);
  if(tret != 0) {
    printf("スレッドできねぇーし");
    exit(EXIT_FAILURE);
  }

  for(;;) {
    puts("ソケットから何か読んでみるから 何かキー押せ...なければ寝る...");
    getchar();
    n = recvmsg(sock[0], &msg, 0);
    if(n >= 0) {
      printf("和人きた...データあったw ret = %d", (int)n);
      printf("   iovec[0]=%s", buff);
      printf("   iovec[0].size=%d", (int)msg.msg_iov[0].iov_len);
      printf("   iovec[1]=%d", index);
      printf("   iovec[1].size=%d", (int)msg.msg_iov[1].iov_len);
    } else {
      printf("recvmsg() errno=%s(%d)", strerror(errno), errno);
    }
  }
}

void child_thread_func(void)
{
  char buff[BUFFSIZE] = "Konomae Oka-ga, kitattena";
  int index = 10000;
  ssize_t n;
  struct msghdr msg;
  struct iovec iov[2];
  struct cmsghdr cmsghdr;

  iov[0].iov_base = buff;
  iov[0].iov_len = sizeof(buff);
  iov[1].iov_base = &index;
  iov[1].iov_len = sizeof(index);

  msg.msg_name = NULL;
  msg.msg_namelen = 0;
  msg.msg_iov = iov;
  msg.msg_iovlen = 2;

#ifdef __gnu_linux__
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
#else
  msg.msg_accrights = NULL;
  msg.msg_accrightslen = 0;
#endif
  
  for(;;) {
    n = sendmsg(sock[0], &msg, 0);
    if(n >= 0) {
      printf("(裏)和人ぶち込みバイト数 = %d", (int)n);
      index++;
    } else {
      printf("(裏)めんどくせぇー errno=%s(%d)", strerror(errno), errno);
    }
  }
}

ビルド方法はこんな感じです

Linuxですとこうで
$ gcc -g -O0 socketpair.c -lpthread


Solarisですとこうです、cc使ってます
$ cc -g -O0 socketpair.c -lsocket -lpthread

msghdr構造体とiovec構造体で送受信ができます、送信したいデータはiovec構造体にデータとデータの長さをセットする(scatter/gather配列と言うらしいです)、サンプルでは2つのデータを設定して送信しています。

1つ目はテキストデータで、2つ目はint型の整数で、和人プロセス<=>子プロセス間は0からインクリメントされていき、スレッド<=>スレッド間は10000からインクリメントされるようになっています。

sendmsg()とrecvmsg()で送受信しますが、ブロッキングソケットなので、ソケットバッファがいっぱいになった時にsendmsg()はブロックし、recvmsg()は読み込みに行ったときにバッファにデータが無いとブロックして帰ってきません。

和人プロセス<=>子プロセス間の通信はsendmsg()が先に走るように設定してありますので送信バッファが一杯になってブロックした後に、ENTERキーを押すとrecvmsg()が走るようにしています。

ちなみにmsghdr構造体ですが、Linuxではこうで
---------------------------
 Linux
---------------------------
/* Structure describing messages sent by
   `sendmsg' and received by `recvmsg'.  */
struct msghdr
  {
    void *msg_name;  /* Address to send to/receive from.  */
    socklen_t msg_namelen; /* Length of address data.  */

    struct iovec *msg_iov; /* Vector of data to send/receive into.  */
    size_t msg_iovlen;  /* Number of elements in the vector.  */

    void *msg_control;  /* Ancillary data (eg BSD filedesc passing). */
    size_t msg_controllen; /* Ancillary data buffer length.
                               !! The type should be socklen_t but the
                               definition of the kernel is incompatible
                               with this.  */

    int msg_flags;  /* Flags on received message.  */
  };

/* Structure used for storage of ancillary data object information.  */
struct cmsghdr {
    size_t cmsg_len; /* Length of data in cmsg_data plus length
                               of cmsghdr structure.
                               !! The type should be socklen_t but the
                               definition of the kernel is incompatible  with this.  */
    int cmsg_level;  /* Originating protocol.  */
    int cmsg_type;  /* Protocol specific type.  */
#if (!defined __STRICT_ANSI__ && __GNUC__ >= 2) || __STDC_VERSION__ >= 199901L
    __extension__ unsigned char __cmsg_data __flexarr; /* Ancillary data.  */
#endif
};


Solarisではこうなっていました、ちょっと違いますね
--------------------
./sys/socket.h:327:
--------------------
/*
 * Message header for recvmsg and sendmsg calls.
 */
struct msghdr {
 void  *msg_name;  /* optional address */
 socklen_t msg_namelen;  /* size of address */
 struct iovec *msg_iov;  /* scatter/gather array */
 int  msg_iovlen;  /* # elements in msg_iov */

#if defined(__lint) || defined(_XPG4_2) || defined(_KERNEL)
 void  *msg_control;  /* ancillary data */
 socklen_t msg_controllen;  /* ancillary data buffer len */
 int  msg_flags;  /* flags on received message */
#else
 caddr_t  msg_accrights; /* access rights sent/received */
 int  msg_accrightslen;
#endif /* defined(_XPG4_2) || defined(_KERNEL) */
};

--------------------
sendmsg()
--------------------
ssize_t sendmsg(int s, const struct msghdr *msg, int flags);

--------------------
struct iovec
--------------------
#if !defined(__USE_LEGACY_PROTOTYPES__)
typedef struct iovec {
    void    *iov_base;
    size_t  iov_len;
} iovec_t;
#else
typedef struct iovec {
    caddr_t iov_base;
#if defined(_LP64)
    size_t  iov_len;
#else
    long    iov_len;
#endif
} iovec_t;
#endif  /* !defined(__USE_LEGACY_PROTOTYPES__) */

--------------------
./sys/socket.h:426:
--------------------
struct cmsghdr {
 socklen_t cmsg_len; /* data byte count, including hdr */
 int  cmsg_level; /* originating protocol */
 int  cmsg_type; /* protocol-specific type */
};

「defined(__lint) || defined(_XPG4_2) || defined(_KERNEL)」のあたりがいつ定義されるかは詳しく分かってません、すいません。

ソケットバッファのサイズも64bit版Linux、(そうもちろんGentooですが...)とSolaris Zoneでは違うのですね、Solaris Zoneは32bitでコンパイルしてしまいましたが...

UNIXドメインソケットのいろいろ

ソケットってイメージてきには


こういう感じですかね、データが混ざらないんですね、当たり前ですか...。

サンプルは面倒な方法でやっていますが、もっと簡単な方法でreadv()やwritev()などの関数もあるので調べてみるのも面白いとおもいます。
例えばMySQLの/tmp/mysql.sockなんかsockaddr_un構造体へファイルのパスを指定してbind()を呼んでいるソケットです、Unixドメインソケットにもいろいろありますね。


あまりというか、おもしろくないコードですが、意外とソケットの動きを確認できます、「こういう時にBroken pipeが出ちゃうかぁ...」とか、「バッファが一杯でブロックするのね」、とか、「ソケットの送受信バッファってOSが違うとだいぶ違うのね」、とか発見がおおいです。

できる事はこれだけじゃなくて、「ディスクリプタパッシング」ってファイルディスクリプタを別のプロセスに送っちゃったり、ユーザーのクレデンシャルを送受信できたりといろいろな利用方法があるのですね、案外忘れてることが多いです。(こういうデータは補助情報、msg_controlメンバに載せるらしくUnixドメインソケットを作成する時に追加オプションが必要らしいが...)まぁまぁいいとして、

なかなか勉強になるではないですか!

是非、皆さんにもUnixドメインソケットライフで楽しんでいただきたいと思います、

和人君が最近リア充らしくSocketライフを楽しんでるっていう話にチョイムカつき気味...な私ですが

(次はNonblockingソケットにしてやるって...)