近年,インターネットが発達してテレビ会議やストリーミングビデオ・オンラインゲームなど、遅延に敏感で実時間性の求められるアプリケーションが一般化し、それらのトラフィック量が増加している。これらのアプリケーションは一般に、トランスポート層プロトコルとしてUDP を用いている。
しかしUDP はそのシンプルな設計により輻輳制御を行っていない。そこで輻輳制御機能をもつプロトコル、DCCP(Dategram Congestion Control Protocol)が提案された。 本研究では、NS2によるDCCPの性能評価をした。
ns2とは、カルフォルニア大学のバークレイ高で開発されたネットワークをシミュレートするツールであり、OTclとC++の2つのプログラム言語を用いている。ns2のシミュレーションの結果はテキストファイルとして得られるが、シミュレーション結果をアニメーションでも見ることができる。
UDPは送達確認などを行わない無手順方式でのデータ転送である。インターネットで使用されるプロトコルでOSI参照モデルのトランスポート層にあたり、ネットワーク層のプロトコルであるIPと、セッション層以上のプロトコルの橋渡しをする。各ホストは、ポート番号ごとに異なる転送を行うことができる。また、複数の相手に同時にデータを送信できる。 通信中のパケット損失やデータ誤りなどの検出やそのための手段はアプリケーションで行う必要がある。UDPの送信元のデータ長を16ビットで表し、最大パケットサイズは65,535となる。
TCP(Transmission Control Protocol)は、インターネットで利用される標準プロトコルで、OSI参照モデルのトランスポート層にあたる。 ネットワーク層のプロトコルであるIPと、セッション層以上のプロトコルの橋渡しをする。 また、TCPはEnd to Endで確実なデジタル転送を行うもので、各ホストは、ポート番号ごとに異なるTCPによる転送を行うことが出来る。
TCPとUDPの違いを以下の表に記す。
UDP | TCP | |||||||
---|---|---|---|---|---|---|---|---|
接続形態 | 1:1及び1:多どちらも可 | 1:1のみ | ||||||
アプリケーションの特定方法 | UDPポート番号 | TCPポート番号 | ||||||
送受信の単位 | パケット※1 | ストリーム※2 | ||||||
宛先までの到達保証 | なし | あり | ||||||
送信エラー時の動作 | パケット破棄 | 自動的に再送 | ||||||
事前のアプリケーション同士の接続動作(コネクションの確立) | 不要※3 | 必要※4 | ||||||
処理の重さ | 軽い | 重い |
(※1)パケット……送信側が送ったパケットが、 そのままの形で受信側に届く。 パケット長は 65,535 バイト以下でなければならない。
(※2)ストリーム……送信されるデータはの長さに制限はない。
(※3)コネクションレス型通信と呼ばれている。
(※4)コネクション型通信と呼ばれている。
TCPで接続をオープンにする際、能動的に接続するのか受動的に接続されるのを待つかを決定しなければならない。前者のことをクライアント・アプリケーション(クライアント) と呼び、後者のことをサーバ・アプリケーション(サーバ)と呼ぶ。ここで、クライアント→サーバへの転送を上り、その逆を下りとする。また、SYN, ACK ビットとはTCPパケット の14バイト目(下6ビット)にあるフラグの中の各ビットのことである。
次に、クライアントからサーバに上りコネクションの確立を試みの説明をする。 ACK+SYNをサーバからクライアントに送り、SYNに対するACKを立てたパケットを送る(上りコネクションの確立を承認)とともに、SYNビットを立てることにより、下りコネクションの確立を試みる。最後にクライアントからサーバにACKを送り、下りコネクションの確立を承認する。この事を3ウェイ・ハンドシェークと言う。 これにより、双方向の通信を可能とすることができる。
TCPではデータを定められた長さに分けて送り、肯定応答をするために ACK ビットを使用する。コネクションの確立のところでは、ACKはSYN(接続要求)に対する肯定応答として使用された。いったん接続が確立されると(3ウェイ・ハンドシェークで使われた 3パケットの次のパケット以降)、ACKは相手からの実パケットに対する肯定応答という意味になる。
TCPは、ACKパケットを単独で返すだけではなく、自分が送信するパケットのTCPヘッダに、「前回、そちらからのパケットを正常に受信しました」という意味のACKビットを立てるということである。
つまり、肯定応答とパケットの送信を1パケットで行うことができるということである。TCPではパケットを送信するたびに、送信側はシーケンス番号(TCPパケット・ヘッダの5バイト目)を増やしてゆき、ACK は相手側から受信した「シーケンス番号」番目のセグメントまでは連続して受信したことを相手側に知らせる。
ウィンドウとは、バッファ内でデータの読み書きを行ったり、格納してあるデータの管理を行ったりする領域のこと。ウィンドウの範囲内であれば、一時的にデータを保存しておくことができ、データの損失を防ぐことができる。
受信側では、ストリームのうち何セグメント目までを受信したかを認識してACK番号をセットしてACKを返し、データが途中で抜けた場合はデータが到着するたびに正常に連続して受信できたセグメント番号の次の値を返す。送信側ではパケットを送出するたびにタイマーを発生させ、一定時間にACKが返らないと、データの再送を行う(タイムアウト)。
また、(途中のルータによる再送などで)重複したパケットが到着する場合もあるが、この場合はパケット・ヘッダ のシーケンス番号がすべて同一になるので、受信側はシーケンス番号(+データ長)を見て、自分がすでにACKを返した分のデータは無視(破棄)する。
輻輳制御は通信路の容量に応じて送信側が送信量を調節することで、シリアル通信などで使われるフロー制御としては、XON/XOFFという文字を使って行うソフトウェア・フロー制御と、モデムの信号線をオンオフすることによって行うハードウェア・フロー制御というものがある。これは制御信号が信頼できる通信路を使えるという前提があるから行える処理でありインターネットでは使用できない。送信側では制御できないかわりに受信側で制御する。
TCPの輻輳制御にスロースタートアルゴリズムというものをがある。最初は1つでACKが届くうちは一度に送る数を増やしていく。この送信者が一度に送信することの出来るデータ量を輻輳ウインドウという。増え方は1,2,4と倍々だがある程度(しきい値)までいったら、輻輳回避モードに入り、1づつ増加していく。グラフにすると、図1のようになる。
最初は小さいセグメントを送り、ACK1つにつき、輻輳ウィンドウを1増やす。しかし、多く送ると輻輳の可能性が高くなるので、しきい値まで増えたら輻輳が起きそうだなと判断してゆっくりと増やしていく。ACKがタイムアウトにより1つでも返ってこなかったとき、輻輳ウィンドウを1にする。これをグラフにすると図2になる。
基本的には図2のように1まで戻す形になる。そうすることで連続して輻輳が発生しない。しかし、1まで戻せば輻輳が発生しないが多く送ることができるまでには時間がかかる。よって、輻輳が発生したときには送信数を半分にする。これをグラフにすると図3になる。
有線ネットワークではパケットロスの原因のほとんどは輻輳によるものである。重複ACKはセグメントを失った可能性が高いことを意味し、セグメントを 失ったと推測できるならそれは輻輳が原因と推測される。このことからより慎重を期するために3回同じACKがくるとき(トリプルACK)、輻輳とみなしてそのセグメントを再送すること。
DCCP(Datagram Congestion Control Protocol)は、信頼性のないデータグラムの双方向ユニキャスト接続を実行する、メッセージ指向のトランスポートプロトコルである。
モード2では、ウィンドウフロー制御によるTCPライクな輻輳制御を行う。データの到着に対してACKを返し、複数のデータパケットに対してACKをまとめて返す重複ACK(ACK Vector)を行う点においてはTCPと共通している。しかし、このACKは受信側が送信側にロスしたパケットの再送を促すためのものではなく、ACKのロスによってネットワークの輻輳を検知するために用いられる。このように、ACKの扱い方がTCPとは違っている。
データ送信側では、受け取ったACK情報側を元に、AIMDアルゴリズム(additive-increase, multiplicative-decrease)によって輻輳ウィンドウを制御する。輻輳を検知しなければ、あるしきい値まで輻輳ウィンドウをACKの数だけ増加させ、輻輳を検出すると半分にする。ACK自体にロスがあった場合、タイムアウトをし、輻輳ウィンドウサイズをゼロにする。この時、しきい値はゼロになる直前の2の値で割った値になる。
これによって送信レートは調節され、輻輳は回避される。この部分においては、TCPと同じである。 さらに、DCCPはACKストリームについても制御を行う。これは、送信側においてACK RATIO(R)と呼ばれる値を受信側に通知し、その値によってACKの返答割合を制御する。送信側は以下の変数kを用いて、k輻輳ウィンドウごとにACK RATIOを1減少する。また輻輳を検知するとk輻輳ウィンドウごとにRを倍にすることでACKの数を減らすことができる。
輻輳のないウィンドウにつきを1増やしていき、その増加分だけRを1減らすことができる。
それから、がk輻輳のないウィンドウのあとにと等しいようにkを見つける。これを式にしたのが(1)の式である。 これをkの式に直すと(2)の式となる。
ここでcwndは輻輳ウィンドウで、RはACK RATIOのことである。このように、エンドホスト間でネットワークが輻輳すると上り下りともに流す情報量を減らすことでトラフィクの輻輳を回避する。
モード3は、TFRC(TCP Friendly Rate Control)均質を基礎とする頻度制御による輻輳制御メカニズムによって輻輳制御を行う。TFRCでは、同じネットワーク環境下(パス・遅延・ロス率)においてTCPフローに対して同じ平均帯域を得られるようなレート制御を行う。
これにより、既存のTCPフローに大きな悪影響を与えることなく通信を行うことができ,ネットワークの系全体の利用率を向上することができる。また、データの転送レートの変動が少ないため、IP 電話やストリーミングなどのある一定のレートでデータを送り続けるようなアプリケーションに向く。その具体的なアルゴリズムを以下に記す。
送信側は送信レートという変数を持ち、受信側はパケットのロス情報をもったACK を送る。送信側はこの情報により送信レートを更新する。送信開始時は、RTTごとに送信レートを指数関数的に増加させ、いわゆるスロースタートフェイズを設ける。 その後、一度パケットがロスすると、TCP とは異なりスロースタートフェイズには二度と入らない。以後、受信側からのRTTごとにフィードバック情報を受け、送信レートが制御される。送信レートは、1RTT ごとに受信側が受け取ったレートの2 倍を超えないように制御する。このため,送信レートを急激に変動しないよう制御する。
TCPとの違いを以下に記す。
ns2はもともとUNIXで開発されたソフトウェアなのでWindowsではCygwinを必要とする。今回は4ノードのネットワークについて2パターンの実験を行った。namコマンドによるアニメーションの動作に加えns2のトレースファイルの出力の2点から観察を行った。
今回の実験で使用したプログラムは付録に記す。
n0からn2までは双方向リンクで転送速度は10Mbps遅延0msに設定。
以下、n1からn2、n2からn3まで同様。
実験では、n0からn3まではdccp/CCID2及びdccp/CCID3でn1からn3まではtcpのプロトコルで実験した。
4つのノードでシミュレートし、アニメーションやトレースファイルを確認する。
Cygwin Bash Shellを起動。startxコマンドでXserverを起動する。
ns dccp.tclとコマンドを打つことでシミュレートする。
ノードの配置図は以下のように設定した。
図4のように4つのノード0、1、2、3に対してノード0からノード2を介してノード3へファイル転送を行い、ノード1からノード2を介してノード3へファイル転送を行う。ただしノード0から1は直接通信できない。
まず、OTclにおいて基本的にset 変数名 変数に設定する値という構文になっている。なお、#以降はコメント文である。
set ns [new Simulator]
ここでシミュレータのオブジェクトを生成する。シミュレータは変数$nsで参照される。
set namfile [open out.nam w] $ns namtrace-all $namfile set tracefile1 [open out_dccp.tr w] $ns trace-all $tracefile1
この部分では、シミュレートの際に書き出すファイル名を任意のものに設定できる。
set n0 [$ns node] set n1 [$ns node] set n2 [$ns node] set n3 [$ns node] $ns duplex-link $n0 $n2 10Mb 0ms DropTail $ns duplex-link $n1 $n2 10Mb 0ms DropTail $ns duplex-link $n2 $n3 10Mb 0ms DropTail
この部分では、ノードn1〜n3までを設定し、転送速度、遅延、パケットの処理を設定する。 今回の実験では、全て転送速度10Mb、遅延0ms、Droptail方式である。 Droptail方式とは、droptailは、queueの種類であり、queueが一杯になった時、新しいパケットが来たらそのパケットを破棄する方式。
$ns attach-agent $n0 $dccp1 $ns attach-agent $n3 $dccp2
この部分では、n0からn3までdccpのCCID2/CCID3に設定している。
for {set i 0} {$i $lt; $val(nn) } {incr i} { $ns at 100.0 "$n$i reset"; } $ns at 100.0 "stop" $ns at 100 "puts \"NS EXITING...\" ; $ns_ halt" puts "Starting Simulation..." $ns run
この部分では、シミュレーションが終わる時刻を定義して、ノード内のネットワークコンポーネントを実際にリセットするための指示を与えている。
出力ファイルをグラフにしてみると以下の図のようになった。
図5においてDCCP/CCID2では、20秒付近で輻輳が検知され、その後は最後まで伝送速度限界まで使われていることによってTCPの伝送速度に影響を及ぼしている。
図6においてDCCP/CCDID3では、35秒付近で輻輳おきていることがわかる。
また、図5に比べてTCPと平等に使われているのでCCID2よりCCID3の方が性能が良い事がわかる。
DCCPはUDPにはない輻輳制御機能をサポートしているので低信頼性の通信に利用することができる。
シミュレーションプログラム Agent/TCP set window 64 set ns [new Simulator] set testTime 100.0 if {$argc $lt; 3} { puts stderr "ns dccp.tcl \[x (bottleneck in Mbit/s)\] \[ 2|3 (ccid)\] \[Sack|Newreno (TCP version)\]" exit 1 } set ccid [lindex $argv 1] set tcpver [lindex $argv 2] set namfile [open out.nam w] $ns namtrace-all $namfile set tracefile1 [open out_dccp.tr w] $ns trace-all $tracefile1 set n0 [$ns node] set n1 [$ns node] set n2 [$ns node] set n3 [$ns node] $ns duplex-link $no $n2 10Mb 0ms DropTail $ns duplex-link $n1 $n2 10Mb 0ms DropTail $ns duplex-link $n2 $n3 10Mb 0ms DropTail # DCCP if {$ccid == 3} { set dccp1 [new Agent/DCCP/TFRC] set dccp2 [new Agent/DCCP/TFRC] } else { set dccp1 [new Agent/DCCP/TCPlike] set dccp2 [new Agent/DCCP/TCPlike] } $dccp1 set fid_ 2 $dccp2 set fid_ 2 $dccp1 set packetSize_ 552 $dccp2 set packetSize_ 552 $ns attach-agent $n0 $dccp1 $ns attach-agent $n3 $dccp2 set cbr2 [new Application/CBR] $cbr2 attach-agent $dccp1 $ns connect $dccp1 $dccp2 # TCP set tcp1 [new Agent/TCP/FullTcp/Sack] set cbr1 [new Application/CBR] $cbr1 attach-agent $tcp1 set sink [new Agent/TCP/FullTcp/Sack] $ns attach-agent $n1 $tcp1 $ns attach-agent $n3 $sink $tcp1 set fid_ 1 $sink set fid_ 1 $tcp1 set packetSize_ 552 $ns connect $tcp1 $sink $sink listen proc finish {} { global ns namfile tracefile1 ccid tcpver set file "./script-dccp.sh" $ns flush-trace close $tracefile1 exec $file $ccid $tcpver exit 0 } proc init {} { global dccp1 dccp2 $dccp1 reset $dccp2 reset } $ns at 0.1 "init" $ns at 0.2 "$dccp2 listen" $ns at 0.3 "$cbr1 start" $ns at 20.0 "$cbr2 start" $ns at [expr $testTime - 20] "$cbr2 stop" $ns at $testTime "$cbr1 stop" $ns at [expr $testTime + 1.0] "finish" $ns run |