ネットワークプログラミング相談室 Port24

このエントリーをはてなブックマークに追加
931デフォルトの名無しさん
プログラミングの仕方の質問なんだけど、
擬似コードを以下に。
// フレームワーク側で、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とかステートパターンみたいなことを駆使しなきゃダメなの?
それとももっと別の"データを処理するためのコーディング方法/パラダイム"みたいなものがあるの?
932デフォルトの名無しさん:2009/10/09(金) 16:56:05
>>931
動けばいいの。動けば。
933デフォルトの名無しさん:2009/10/09(金) 17:28:24
yaccみたいなパーサジェネレータを作るというのはどうだろう
自前でステート管理のコードを書くかわりに自動生成したコードに管理してもらう
あとは構文とアクションを書くだけ
ただしパーサジェネレータを作るのが面倒いかもしれない
934デフォルトの名無しさん:2009/10/09(金) 17:29:09
>>931
俺はStateパターンというか、状態変数持たせてる。
935デフォルトの名無しさん:2009/10/09(金) 20:04:33
recvの第3引数に受信バッファにあるデータサイズより小さいものを指定した場合
recvで変数に入りきらずあまった分のデータって2度目のrecvで受け取れないの??
936デフォルトの名無しさん:2009/10/09(金) 20:09:00
TCPなら受け取れる
UDPなら受け取れない
937デフォルトの名無しさん:2009/10/09(金) 20:10:51
ありがとう
938デフォルトの名無しさん:2009/10/09(金) 22:41:28
>>931
自分も>>934と同様に、状態変数で管理してたよ。
・Type待ち
 --> 初期状態
・Length待ち
 --> 1byte固定なら1個、2byte固定なら2個、可変長ならN個の状態
・Value処理中
 --> 受信カウンタを併用(初期値は0, Lengthになれば終了)
実装はインスタンス生成時に受信バッファを渡し、後はgetで取得するイテレータになる。

>>933
(Valueが更にTLVとして定義される)階層的なTLV構造で、それが再帰的である、
または複雑な構造(構文)を持つなら、yaccを使うのは良いアイデアかもしれない。
その場合、前記のイテレータをスキャナ(字句解析)として位置付け、
Typeをトークン(字句)として返すよう実装することになる。

yacc以外だと、precssというコードジェネレータを使うのが良いかもしれない。
939デフォルトの名無しさん:2009/10/09(金) 23:27:21
俺はこないだ「パケット長を知るには最低何バイト読む
必要があるか」というのと、その「読んだバイト列から
パケット長を得る callback」をパラメータにした受信
部を作った。

内部的には皆が言っている「状態」を持っていて、使う
側からのインタフェイスは recv や recvfrom に管理情
報の引数が一つ増える感じ。
940デフォルトの名無しさん:2009/10/09(金) 23:56:07
fdopenするのがお手軽。Winはできたっけ?
941デフォルトの名無しさん:2009/10/10(土) 01:55:16
winsockで通信のコードを書いているのですが、
accept関数で10014のエラーが出てしまいます。
エラー番号の意味は、
呼び出しでポインタ引数を使用するときに、無効なポインタ アドレスを検出しました。
らしいのですが、acceptの引数については今まで普通に動いてきたソースのコピペなので
正直なにが悪いのかよく分かりません。どなたか、原因を教えていただけないでしょうか?

http://www.dotup.org/uploda/www.dotup.org241369.cpp.html
942デフォルトの名無しさん:2009/10/10(土) 02:02:14
今まで動いてたけど実はたまたまうまく動いてただけでした^^
ってこともまれにある。
ぱっとみ、acceptの代参引数をsizeof( struct sockaddr_in )で初期化してねーだろ
943デフォルトの名無しさん:2009/10/10(土) 02:04:20
自己解決しました。
len = sizeof(client);
をコピペミスしてました。
944デフォルトの名無しさん:2009/10/10(土) 02:07:42
>>942
ありがとうございます。
まさにその部分が原因でした。
コピペ元も初期化してなかったのですが、そっちでは何故かうまく動いてましたw
これで1時間くらい悩んでたのですが、書き込んだ直後に解決しました。
スレ汚しすみません。
945938: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から取得するだけ。
}
こんな感じ?
946931:2009/10/10(土) 02:12:20
ごめん。↑は931です
947938: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の処理
   }
  }
948デフォルトの名無しさん:2009/10/10(土) 06:05:50
caseなんか使わずに
メンバ関数へのポインタをメンバに持って
状態が遷移したら、returnする前に次の処理をするメンバ関数を設定するんだ。


いや、可読性を無視するなら、ね。
949デフォルトの名無しさん:2009/10/10(土) 08:36:40
>>940
I/Oイベントドリブンが前提って書いてあるだろ。
950938:2009/10/10(土) 09:59:42
>>947の疑似コードだけど、既にメッセージ全体が受信バッファに読み込まれていること、
あるいは、メッセージが未完成の場合にはget操作内でプロセス(or スレッド)がブロックされる、
言い換えると同期I/Oを前提にしている、という前提がある。
だから、そもそもの質問>>931(未完成時の再開方法)に対するレスとしては不適切だった。

質問の意図に沿って「非同期I/Oを前提(だよね?)」とした場合、最も外側のTLV構造の処理は、
ステートマシン(状態管理処理)と一体になる。疑似コードだと、こんな感じ。

(続く)
951938: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;
  }
 }
}

(更に続く)
952938:2009/10/10(土) 10:01:59
(>>951の続き、これで終わり)

>>931の場合、Lengthとして4byte固定を想定しているみたいだから、
上記の疑似コードに対して、Length待ち状態をSTATE_WAIT_LENGTH1..._LENGTH4に分割するか、
あるいはLength受信カウンタを追加する、といった改造を加える必要があるから、注意してね。
他にも、pop値は1byte固定になってるけど、Value処理中状態ではNbyteに高速化すべきだろうし。
953デフォルトの名無しさん:2009/10/10(土) 12:50:43
>>933
yacc 使えばいいんじゃね?
作らなきゃいけないのは、レキシカルアナライザ部分だ。
954デフォルトの名無しさん:2009/10/10(土) 13:14:50
yacc使ってイベント駆動のコード書くの?
955デフォルトの名無しさん:2009/10/10(土) 14:25:34
TLVのデコーダくらいさくっと書けよ。
一度書いたらいろんなプロジェクトで使いまわしできるもんだし。
956デフォルトの名無しさん:2009/10/10(土) 14:29:25
TLVデコーダを書く事に関する質問ではない。
957デフォルトの名無しさん:2009/10/10(土) 16:27:51
>>955じゃないが、元のお題は、
イベント駆動TLVデコーダの話なんじゃないの?
958938: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)。容易に実現可能である、とまで断定はできないけどね。

(続く)
959938:2009/10/10(土) 23:20:31
(>>958の続き)

最後に、>>956の意図をエスパーしてみたら、通信プロトコル(の状態遷移)そのものをDSLあるいは
yaccで記述できないのか?に読めた。もしそうなら、UNIX板の「yacc & lex」スレ内にある
レス#112-120で議論されていたから、まずそちらを参照してみて。

# 最後に訂正。>>958内にあるアンカ>>938は誤り。>>947が正しい。
960デフォルトの名無しさん:2009/10/11(日) 00:45:27
>>959
イベント駆動だから、yaccで直接書こうとすると、
yyparseをコルーチン化する必要があるぞ。

しかも不完全パケットを受けた状態を
それぞれ非終端記号にする必要がある。
ある程度はまとめられるとしてもね。

>>931のF()は一旦意味のある単位にまとめるのに集中して、
その結果をparseする別のコルーチンに渡すやり方なら簡単だけど。
961デフォルトの名無しさん:2009/10/11(日) 08:29:23
pull型/push型の違いはI/Oライブラリで吸収できる程度の違いなので本質的じゃないですね。
962デフォルトの名無しさん:2009/10/11(日) 10:12:13
なんか >>938 が色々語りたいようだが、内容無くね?うざくね?
解釈勝手すぎくね?全部主観じゃね?うざくね?
963931:2009/10/11(日) 10:20:40
なんか話が高度過ぎてついてけないっす。
push/pullの概念がわかりません
>>960
コルーチンの方が楽っすかね?

とりあえず、>>951みたいな形で
stateとcounterを持たせる方法で書き始めました

>>962
何もしなくて、批判文句垂れる奴がうざいとおもますぅ><
964デフォルトの名無しさん:2009/10/11(日) 11:44:08
>>961
使うフレームワークが固定でそういうわけにもいかないんでしょう
965938: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)に関しては無視して進めればいいと思うよ。(これも個人的な主観かもしれんが)
966デフォルトの名無しさん:2009/10/11(日) 11:50:38
>>964
フレームワーク(他にもデザインパターンなど)という言葉の幻想(固定観念)に囚われることなかれ
967デフォルトの名無しさん:2009/10/11(日) 12:09:34
>>931
> // フレームワーク側で、recvイベントが発生した際に呼ばれる関数。

このフレームワークを変更可能であると想定していいのかね。
968931:2009/10/11(日) 12:34:35
>>967
一応このフレームワークを使おうって流れ(チームで)なんで変更できなさそうです。
フレームワーク側は、
bonblockingソケットを使っていて、EAGAINを帰すまで読み込んだ後、コールバック呼ぶっぽいです