ClassDevelopment今回のエントリは、極一部の人にしか伝わらない上、かなりの長文となっています。具体的には、ns-2への不満を吐いてみましたという感じです。別にこの方針に限る必要はないですが、ある程度のコーディング指針を意識して持ってくれたらな、と思います。動いたら何でも良いじゃあんまりにもあんまりだ、とか思ったりします。いうネットワークシミュレータが存在します。私の研究分野ではデファクト・スタンダードとなりつつあるシミュレータです。ソースコードには、いろいろと思う所があるので、今回は、守ると保守などが楽になるのでは、と私が考えることについて少し述べてみます。ソースコードを見ていて、目に付くソースコードは以下の2つです。基となるクラス)に直接修正を加えるクラス内のメンバ関数を一旦全てコピーしてクラス名を変えた後、修正を加えるこれらの方法のどちらにも言える問題点として、作成したソースコードの保守が難しいという事が挙げられます。現在でもそれなりの頻度でバージョンアップが繰り返されています。オリジナルのソースコードにすら、そういったコピー&ペーストのコーディングが多々見受けられますので、開発グループはバージョンアップを行う度にそれら(コピーした)全てのソースコードをチェックしなければなりません。各研究者が独自で実装したソースコードに関しては、状況はさらに悪化します。そのように独自で実装されたソースコードに関しては、当たり前ですが、開発グループのサポート外です。加えて、そのソースコードを書いた人は既に卒業してもういない、という事もしばしば起こります。使い続けてしまいます。バージョンアップされる度に少なからずのバグフィックスも行われているので、古いバージョンを使い続けることは、そういった観点(シミュレーション結果にバグの影響が及ぶかもしれない)から見てもあまり良くありません。修正を加える場合においては、別の問題も発生します。それは、基底クラス(TcpAgent)の肥大化です。ものメンバ変数が存在しており、既にクラスの全体を把握する事はかなり困難な状況となっています。関しては、最小とはかけ離れた方向へ進んでいます。そこで、今回は上記のような状況を改善するために、保守性を向上させるコーディング方法に関して検討します。コードを記述するときの修正量をできるだけ最小に抑え、バージョンアップの際にも容易に新しいバージョンに移行できることを目指します。主要メンバ関数についてその概要を述べます。大きく分けて次の3つの(重要な)機能が存在します。timeout()これらの3つの主要メソッドについて、次節以降にその概要を述べます。おいて、最も重要なメンバ関数の一つです。輻輳ウィンドウサイズの更新が行われるため、頻繁にコピー&ペーストされて修正されるメソッドでもあります。メソッドの概要は以下のようになります。maxburst_);}ACKパケットを受信すると、そのパケットが正常なACKパケットかそうではないACKパケット(重複ACK)かを判断し、それぞれの場合において必要な処理を行います。RTTや輻輳ウィンドウサイズもこのrecv()メソッド(からコールされるメソッド)によって更新されます。その後、send_much()メソッドをコールし、現在の状態(輻輳ウィンドウサイズなど)に基づいてデータパケットの送信を行います。send_much()、output()send_much()およびoutput()メソッドは、通信相手に対してパケットを送信するメソッドです。これらのメソッドの概要は以下のようになります。}TCPは輻輳ウィンドウサイズの情報を基にして、現在、自分が送信することのできる数のパケットを一度に送信します。send_much()は、その数だけoutput()メソッドをコールする役割を持つメソッドです。output()メソッド内で、新しいパケットを作成し、必要な情報をTCPヘッダに付加した後、実際にパケットを送信します(send()メソッド)。timeout()TCPは、スムーズなデータ転送を実現するために、いくつかのタイマを持っています(代表的なものは再送タイマ)。timeout()メソッドは、これらのタイマがタイムアウトした時にコールされるメソッドです。以下に、概要を述べます。timeout_nonrtx(tno);}timeout()メソッドは、tnoによってタイマの種類を見分けます。5この中で、TCP_TIMER_RTXが再送タイマに当たります。TcpAgentでは、timeout()メソッド内では、再送タイマのタイムアウト時の処理のみを記述し、残りのタイマに関してはtimeout_nonrtx()メソッドに記述するというポリシーのようです。ヘルパメソッド前章において、メソッドの概要を記述した際に、いくつかの箇所を強調表示しました。具体的には、以下に示す8箇所です。recv_helper(pkt);recv_newack_helper(pkt);dupack_action();send_one();send_idle_helper();send_helper(maxburst);output_helper(p);timeout_nonrtx(tno);これらのメソッドは、ヘルパメソッドと呼ばれています(厳密には、いくつかのメソッドはヘルパメソッドではないが、ここでは同様に扱う)。TcpAgentには、拡張性を考えて、コード上の要所に上記のようなヘルパメソッドが埋め込まれています。TcpAgentにおいては、これらのほとんどのメソッドに関しては、何も記述がされていない状態になっています。そこで、独自のTCPを作成する際にはこれらのメソッドをオーバーライドし、その中に必要な記述を加えることにより、既存のTcpAgentへの依存度を最小限に抑えられます。そのため(上記にtimeout()メソッドを加えた)、9種類のメソッドを有効に利用することにより、バージョンアップの際にも容易に移行が可能となるソースコードを記述することが可能となります。以下に、それぞれのヘルパメソッドのコールされるタイミングや注意点について、簡単に記述します。パケット受信に関するヘルパ関数パケット受信に関するヘルパ関数は、recv_helper()、recv_newack_helper()、dupack_action()、send_one()の4種類です(厳密には、dupack_action()、send_one()はヘルパメソッドではない)。これらのメソッドのコールされるタイミングはほとんど同じです。受信されると、まず始めにrecv_helper()メソッドがコールされます。その後、受信したACKパケットのシーケンス番号とラストACK(もっとも直近に受信した新規ACKパケットのシーケンス番号)を比較し、新規ACKであれば、recv_newack_helper()がコールされます。重複ACKであった場合、1、2回の重複であればsend_one()がコールされ、3回の重複であればdupack_action()がコールされます。どのヘルパメソッドにおいても、ヘルパメソッドがコールされた時点では、TCP内部で保持されているパラメータは前回ACKパケットを受信したときのままです。recv_newack_helper()、dupack_action()、send_one()メソッドにおいては、TcpAgentにおいても必要な処理が記述されています。主な処理として、前者の2つのメソッドでは、輻輳ウィンドウサイズの更新(opencwnd()、slowdown())、最後のメソッドでは“1個だけ”新たなデータパケットを送信するというものがあります。TcpAgent::recv_newack_helper();)必要があります。スーパークラスで行われている処理を含めて全ての処理を記述する方法もありますが、移行を容易にするために、スーパークラスで行われていることはできるだけそのままスーパークラスに行わせる方が良いと考えられます。新しいTCPの提案の際に修正される多くは、輻輳ウィンドウの制御方式ですので、上記に挙げたヘルパメソッドを把握することは、移行を容易にする観点から見ても、他の問題点をできるだけ切り離す観点から見ても非常に重要と考えられます(opencwnd()、slowdown()メソッドに関しては、後述する)。パケット送信に関するヘルパメソッドパケット送信に関するヘルパメソッドは、send_much()メソッドからコールされるsend_idle_helper()、send_helper()メソッド、output()メソッドからコールされるoutput_helper()メソッドです。send_idle_helper()、send_helper()メソッドはそれぞれ、send_much()がコールされた直後、終了する直前にコールされます。それぞれのタイミングにおいて何らかの処理を加える必要がある場合には、該当メソッドをオーバーライドして変更します。send_helper()メソッドには、引数としてmaxburstが渡されます。通常、TCPは輻輳ウィンドウサイズの値を基に一度に送信するパケット数を決定しますが、TcpAgentではそれとは別に、一度に最大に送ることのできる数(メンバ変数maxburst_。デフォルト値は0)が指定されています。maxburstは、今回一度に送信したパケット数が記憶されているので、その値を基に次回、一度に送信するパケット数を決定する場合などは、send_helper()メソッドで更新を行うようです。output_helper()メソッドは、TCPヘッダに必要な情報を付加した後、実際にパケットを送信する直前にコールされます。そのため、このヘルパメソッドはTCPヘッダのオプション領域に何らかの情報を付加したい場合などに利用します。タイマのタイムアウトに関する(ヘルパ)メソッドパケットの送受信時とは異なり、タイマのタイムアウト時にはヘルパメソッドは定義されていません。そのため、タイマのタイムアウト時に何らかの処理を行う際には、timeout()、およびtimeout_nonrtx()メソッドを直接オーバーライドし、必要な処理を記述することになります。TcpAgent::timeout();)形を取るのが良いと考えられます。輻輳ウィンドウサイズ更新のためのメソッドこの章では、最後に、輻輳ウィンドウサイズを更新するためのメソッドopencwnd()、slowdown()について触れておきます。先にも述べたように新規TCPを提案する際、その多くは輻輳ウィンドウサイズの制御方式に終始します。そのため、そういったTCPを実装する際には、opencwnd()(輻輳ウィンドウサイズを増加させるためのメソッド)およびslowdown()(輻輳ウィンドウサイズを減少させるためのメソッド)をオーバーライドして修正する方法が最も良いと考えられます。これには一つ問題があります。以下に、TcpAgentにおける上記のメソッドの宣言を示します。how);これを見ると、輻輳ウィンドウサイズを更新するためのメソッドは仮想化されていないことが分かります。このため、これらのメソッドをオーバーライドして修正した場合、期待する動作が得られない可能性があります(現在、TcpAgent外部からコールされているのは、recv()およびtimeout()メソッドなので、その可能性は低いが)。仮想化されていないということは、TcpAgentの設計者がそれらのメソッドをオーバーライドされることを想定していないことを意味します。使うと、基底クラスのポインタから派生クラスの関数が呼び出せるようになります。指定してはいけない、ということになります。・・・(中略)・・・オーバーライドによって振舞いを変えられてはいけない場合は結構あります。(何でもかんでもvirtualにしてはいけない)実際、wnd_option_というパラメータ(このパラメータ値によって輻輳ウィンドウサイズの制御方法を変更する)を用意していることから考えても、その是非は別として、当初の設計では輻輳ウィンドウサイズの制御は、TcpAgentに集めることを想定していたと考えられます。その意味でも、これらのメソッドをオーバーライドして修正することは好ましくないと考えられます。したがって、輻輳ウィンドウサイズの制御方式を修正するには、以下の2つの方法が考えられます。recv_helper()など各種ヘルパメソッドに輻輳ウィンドウサイズの制御も組み込む新しいwnd_option_を定義して、TcpAgentに直接組み込む既存のソースコードへの依存度を最小にするという観点から見ると前者の方が良いと考えられますが、実際には時と場合によって柔軟に対応する必要がありそうです。最後に、クラステンプレートの利用について述べます。...)が存在しています。そのため、場合によっては継承するクラスだけ違うほとんど同じクラスが複数必要となる場合があります。SACKオプションがあります。新しいTCPを作成したとき、しばしばSACKオプションが有効な場合と無効な場合の2種類のTCPが必要となります。このとき、SACKオプションの有効なTCPは、Sack1TcpAgentという一つのクラスとして存在しています。そのため、SACKオプションが有効な場合と無効な場合で2種類のTCPクラスを新たに作成しなければなりません。これも、コピー&ペーストによる複製が増える原因の一つとなります。そこで、テンプレートクラスを利用します。新規TCPを作成する際には、最初に以下のような宣言にしておきます。{...};そして、以下のように必要な分だけtypedefしておくことで、不必要な複製を防ぐことができます。XXXSackTcpAgent;尚、注意する点として、テンプレートクラスを使用した場合、継承元(BaseTcp)のメンバ変数やオーバーライドしてないメソッドに関しては、this->xxxのようにしてアクセスします。このように作成したTCPクラス内で、bindした変数に関しては、typedefしたクラス(に関連付けられているtclクラス)の分だけ、ns-default.tclに記述する必要があります。TCPクラスのスケルトンこの節では最後に、これまでに述べたの指針を満たしたTCPクラスのスケルトンを示します。super::send_helper(maxburst);}.....おわりに今回は、TCPクラスのns-2コードを記述する際の指針について述べてきました。コピー&ペーストによるコーディングは、単純な見にくさの観点以外においても多くの問題点を含んでいます。そのため、できるだけそういったコーディングは控え、最小かつ完全の原則でクラス設計およびコーディングを行っていくことが重要であると考えられます。C++で記述されてあるのだから、C++が持つ機能(テンプレートなど)も有効に利用することで、さらにバージョン間の移行が容易なコードが書けるのでは、と思います。ソフトウェアを開発しています。0.5.5βこのブログのこれまでとこれから注目記事スターをつけました引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。