プログラミングの仕方の質問なんだけど、
擬似コードを以下に。
// フレームワーク側で、recvイベントが発生した際に呼ばれる関数。
// 受信したデータがbufにバッファリングされてる
void F(buffer &buf){
if(buf.size()==0) return;
// TLV形式で、Typeを取ってくる
switch(buf[0]){
case MAP_DATA:
if(buf.size()<5) return;
size_t len = *(size_t*)(&buf[1]);
// TLを読み込んだ時点で、読み込んだサイズ分、bufからpopする
buf.pop(5);
// (*ここで質問)データを処理する
// len分データが届いていなかったら、届いてる分だけ処理してreturnする
}
こういうコードの場合、len分データが届いてなかったらreturnして、またFが呼ばれるのを待つんだけど、
次にFが呼ばれた場合、TLの解析は飛ばして、(*ここで質問)から処理を再開させたい
んだけど、coroutineとかステートパターンみたいなことを駆使しなきゃダメなの?
それとももっと別の"データを処理するためのコーディング方法/パラダイム"みたいなものがあるの?
yaccみたいなパーサジェネレータを作るというのはどうだろう
自前でステート管理のコードを書くかわりに自動生成したコードに管理してもらう
あとは構文とアクションを書くだけ
ただしパーサジェネレータを作るのが面倒いかもしれない
>>931 俺はStateパターンというか、状態変数持たせてる。
recvの第3引数に受信バッファにあるデータサイズより小さいものを指定した場合
recvで変数に入りきらずあまった分のデータって2度目のrecvで受け取れないの??
TCPなら受け取れる
UDPなら受け取れない
ありがとう
>>931 自分も
>>934と同様に、状態変数で管理してたよ。
・Type待ち
--> 初期状態
・Length待ち
--> 1byte固定なら1個、2byte固定なら2個、可変長ならN個の状態
・Value処理中
--> 受信カウンタを併用(初期値は0, Lengthになれば終了)
実装はインスタンス生成時に受信バッファを渡し、後はgetで取得するイテレータになる。
>>933 (Valueが更にTLVとして定義される)階層的なTLV構造で、それが再帰的である、
または複雑な構造(構文)を持つなら、yaccを使うのは良いアイデアかもしれない。
その場合、前記のイテレータをスキャナ(字句解析)として位置付け、
Typeをトークン(字句)として返すよう実装することになる。
yacc以外だと、precssというコードジェネレータを使うのが良いかもしれない。
俺はこないだ「パケット長を知るには最低何バイト読む
必要があるか」というのと、その「読んだバイト列から
パケット長を得る callback」をパラメータにした受信
部を作った。
内部的には皆が言っている「状態」を持っていて、使う
側からのインタフェイスは recv や recvfrom に管理情
報の引数が一つ増える感じ。
fdopenするのがお手軽。Winはできたっけ?
941 :
デフォルトの名無しさん:2009/10/10(土) 01:55:16
今まで動いてたけど実はたまたまうまく動いてただけでした^^
ってこともまれにある。
ぱっとみ、acceptの代参引数をsizeof( struct sockaddr_in )で初期化してねーだろ
自己解決しました。
len = sizeof(client);
をコピペミスしてました。
>>942 ありがとうございます。
まさにその部分が原因でした。
コピペ元も初期化してなかったのですが、そっちでは何故かうまく動いてましたw
これで1時間くらい悩んでたのですが、書き込んだ直後に解決しました。
スレ汚しすみません。
945 :
938:2009/10/10(土) 02:10:51
レスサンクスコ。
yaccとか難しすぎて俺には無理です。
みんな状態持たせて大変なことやってるんすね
>>938の
>実装はインスタンス生成時に受信バッファを渡し、後はgetで取得するイテレータになる。
「イテレータになる」ってあたりがどういうコードになるのかイメージできません
buffer::iterator iter;
while(true){
iter = buf.begin(); //TLVの解析はbufferクラスの中に閉じ込める
if(iter==buf.end()) break;
// 処理。bufクラスのクライアントはiterから取得するだけ。
}
こんな感じ?
946 :
931:2009/10/10(土) 02:12:20
ごめん。↑は931です
947 :
938:2009/10/10(土) 05:31:28
>>945 >yaccとか難しすぎて俺には無理です。
yaccについては、
>>938で書いたのは単なる思いつきだから、スルーでオケー。
ただ、preccsは面白いと思う。一読を薦めるヨ。自分も実際には触った事ないけどネ。
>こんな感じ?
だいたい、そんな感じ(w
ただ、C++のテンプレートは使用せず、一般的なTLVReaderクラスを定義していた。
イテレータという言葉は(
>>938のカキコ内では)デザインパターン用語のつもりで書いた。
疑似コードで書くと、こんな感じ。(実際にはC言語で(GTKみたいな)疑似クラスで実装していた)
unsigned char *recv_buf;
TLVReader reader;
reader = TLVReader.new(recv_buf);
while (tlv = reader.get()) {
switch (tlv.type) {
case TYPE_XXX_PDU:
: // Type別の処理
}
}
階層的なTLV構造の場合には、Type別処理の中で、更に別のTLVReaderインスタンスを生成する。
xxx_reader = TLVReader.new(tlv.value)
while (xxx_tlv = xxx_reader.get()) {
switch (xxx_tlv.type) {
: // Sub Typeの処理
}
}
caseなんか使わずに
メンバ関数へのポインタをメンバに持って
状態が遷移したら、returnする前に次の処理をするメンバ関数を設定するんだ。
いや、可読性を無視するなら、ね。
>>940 I/Oイベントドリブンが前提って書いてあるだろ。
950 :
938:2009/10/10(土) 09:59:42
>>947の疑似コードだけど、既にメッセージ全体が受信バッファに読み込まれていること、
あるいは、メッセージが未完成の場合にはget操作内でプロセス(or スレッド)がブロックされる、
言い換えると同期I/Oを前提にしている、という前提がある。
だから、そもそもの質問
>>931(未完成時の再開方法)に対するレスとしては不適切だった。
質問の意図に沿って「非同期I/Oを前提(だよね?)」とした場合、最も外側のTLV構造の処理は、
ステートマシン(状態管理処理)と一体になる。疑似コードだと、こんな感じ。
(続く)
951 :
938:2009/10/10(土) 10:01:02
(
>>950の続き)
RecvAcceptorState state = STATE_WAIT_TYPE; // 初期状態はType待ち
void recv_acceptor() // recvイベント発生に呼ばれるコールバック関数
{
while (buf.size() > 0) {
byte = buf.pop(1)
switch (state) {
case STATE_WAIT_TYPE: // Type待ち状態
tlv.type = byte; // Type値が決定
state = STATE_WAIT_LEGTH; // 次の状態(Length待ち)へ遷移
break;
case STATE_WAIT_LENGTH: // Length待ち状態
tlv.length = byte; // Length値が決定(Lengthフィールドが1byte固定である事を仮定)
value_counter = tlv.length; // 受信カウンタをLength値で初期化
state = STATE_PROCESS_VALUE; // 次の状態(Value処理中)へ遷移
break;
case STATE_PROCESS_VALUE: // Value処理中状態
:
if (メッセージ組立て完了) {
: // 受信メッセージ処理のアクションを起動する
: // もし階層的なTLV構造であれば、
>>947のメッセージ解析処理を呼ぶ
state = STATE_WAIT_TYPE; // 初期状態へ遷移(必要であれば他の再初期化処理も)
}
:
break;
}
}
}
(更に続く)
952 :
938:2009/10/10(土) 10:01:59
(
>>951の続き、これで終わり)
>>931の場合、Lengthとして4byte固定を想定しているみたいだから、
上記の疑似コードに対して、Length待ち状態をSTATE_WAIT_LENGTH1..._LENGTH4に分割するか、
あるいはLength受信カウンタを追加する、といった改造を加える必要があるから、注意してね。
他にも、pop値は1byte固定になってるけど、Value処理中状態ではNbyteに高速化すべきだろうし。
>>933 yacc 使えばいいんじゃね?
作らなきゃいけないのは、レキシカルアナライザ部分だ。
yacc使ってイベント駆動のコード書くの?
TLVのデコーダくらいさくっと書けよ。
一度書いたらいろんなプロジェクトで使いまわしできるもんだし。
TLVデコーダを書く事に関する質問ではない。
>>955じゃないが、元のお題は、
イベント駆動TLVデコーダの話なんじゃないの?
958 :
938:2009/10/10(土) 23:16:02
>>953-957 # スレの流れが錯綜しそうなので、強引にマトメてみる。
最も外側のTLV構造のデコード処理は(
>>950-952に書いた)イベント駆動方式(PUSH型)で実現できる。
この部分は比較的に単純なステートマシンだから、わざわざyaccみたいなコードジェネレータを
持ち出す必要性は(個人的には)感じられない(
>>957)。
検討課題になるのは、メッセージを組立てた後に、階層的なTLV構造の内側をデコードする
(
>>938の)プロセス駆動(PULL型)処理だと思う。
TLV方式は拡張性があるから、必要に応じていくらでも要素を追加できる。
しかも、それに対応するコードは(
>>938で書いたような)単純なイテレータで実現できるから、
コピペでコーディングできる。
>>955の言葉を借りれば、プロジェクト間で使いまわしができる。
とはいっても単調なコーディング作業の連続だから、(
>>948の言う)関数ポインタを活用したり、
あるいはテーブル駆動なコーディング様式を検討したくなるのは、自然な発想だと思う。
そして、それを追求するとTLVデコード処理に特化した俺様言語(DSL)が欲しくなるのも自然な成り行き。
で、DSL(Domain Specific Language)を定義して、その言語処理系(コードジェネレータ)を
開発してみては?という提案が、
>>933になる。それに対して、新たにメタ言語を定義して
処理系を開発するのは面倒だから、既に存在しているyaccで(メタ言語を)代用できるんじゃね?
ってのが、
>>953の提案。ここでのTLVデコード処理はプロセス駆動方式(PULL型)だから
yaccとの相性は良いかもしれない(
>>954)。容易に実現可能である、とまで断定はできないけどね。
(続く)
959 :
938:2009/10/10(土) 23:20:31
(
>>958の続き)
最後に、
>>956の意図をエスパーしてみたら、通信プロトコル(の状態遷移)そのものをDSLあるいは
yaccで記述できないのか?に読めた。もしそうなら、UNIX板の「yacc & lex」スレ内にある
レス#112-120で議論されていたから、まずそちらを参照してみて。
# 最後に訂正。
>>958内にあるアンカ
>>938は誤り。
>>947が正しい。
>>959 イベント駆動だから、yaccで直接書こうとすると、
yyparseをコルーチン化する必要があるぞ。
しかも不完全パケットを受けた状態を
それぞれ非終端記号にする必要がある。
ある程度はまとめられるとしてもね。
>>931のF()は一旦意味のある単位にまとめるのに集中して、
その結果をparseする別のコルーチンに渡すやり方なら簡単だけど。
pull型/push型の違いはI/Oライブラリで吸収できる程度の違いなので本質的じゃないですね。
なんか
>>938 が色々語りたいようだが、内容無くね?うざくね?
解釈勝手すぎくね?全部主観じゃね?うざくね?
963 :
931:2009/10/11(日) 10:20:40
なんか話が高度過ぎてついてけないっす。
push/pullの概念がわかりません
>>960 コルーチンの方が楽っすかね?
とりあえず、
>>951みたいな形で
stateとcounterを持たせる方法で書き始めました
>>962 何もしなくて、批判文句垂れる奴がうざいとおもますぅ><
>>961 使うフレームワークが固定でそういうわけにもいかないんでしょう
965 :
938:2009/10/11(日) 11:45:06
>>960 yaccとイベント駆動の組み合わせで生まれる問題(push/pull不整合)については、
>>961が1行レスでマトメてくれたので、これ以上はツッこまない。ただ、2点だけ補足。
・イベント駆動問題の解決方法はコルーチンだけではない。UNIX板で紹介したWebアプリの場合、
(コルーチンを使わずに)別の方法を使ってI/Oライブラリ(yylex()関数)内で吸収させている。
・イベント駆動問題はI/Oライブラリで吸収できる程度の問題だけど、他にも(ライブラリでは
吸収できない)本質的な問題(制約)が存在する。具体的な内容は省略。
>>963 push/pullやコルーチンなどの話題は、yaccとイベント駆動を組み合わせた場合に限定できるから、
元の質問(
>>931)に関しては無視して進めればいいと思うよ。(これも個人的な主観かもしれんが)
>>964 フレームワーク(他にもデザインパターンなど)という言葉の幻想(固定観念)に囚われることなかれ
>>931 > // フレームワーク側で、recvイベントが発生した際に呼ばれる関数。
このフレームワークを変更可能であると想定していいのかね。
968 :
931:2009/10/11(日) 12:34:35
>>967 一応このフレームワークを使おうって流れ(チームで)なんで変更できなさそうです。
フレームワーク側は、
bonblockingソケットを使っていて、EAGAINを帰すまで読み込んだ後、コールバック呼ぶっぽいです