パケット交換の説明をアニメで

パケット交換方式では

[A→D]のボタンでAからDにパケットの送出が始まります。さらに[B→F]のボタンを押すとBからFにもパケットが送られます。

A,B,C から D,E,F へ任意の組み合わせでパケットを同時に送ることができます。通信経路が共有されて複数の通信が可能な仕組みがわかります。

回線交換方式では

こちらは図を切り替えて表示しているだけです。線の色をパケットに合わせていますので、同ページに置いておきます。

説明図

制作意図

回線交換方式とパケット交換方式の違いを説明するために作りました。自分で通信を変更できる方が面白いだろうということで工夫をしました。

送信方向はA,B.CからD,E,Fへの固定です。受信側は複数の相手から同時に受信が可能になっていますが、送信側は相手を一つ選べるだけです。これはプログラムの都合です。

パケット通信のプログラム(初期化)

canvasの場所を特定し、幅と高さを取得。これは表示の幅ではなく、描画領域としての幅です。

幅を8等分し、左PC-合流点-分岐点-右PC の位置を x1〜x4 に、高さを6等分し、PCの位置を y1,y2,y3 に入れておきます。

dx,dyは縦横の移動の刻みです。x3-x2 は共有部分を等分、dyもそれに習った式にしたのだと思います(過去の自分は他人)。

draw()を呼び出すのは、パケットの移動後にPCや配線を書き戻すことになるので、初期画面もそれに書かせることにしたものです。

function initcanv(){
  canvas1 = document.getElementById('area1');
  ctx = canvas1.getContext('2d');
  cw = canvas1.width;
  ch = canvas1.height;
  x1 = cw/8; 
  x2 = cw*3/8; 
  x3 = cw*5/8; 
  x4 = cw*7/8; 
  y1 = ch/6;
  y2 = ch*3/6;
  y3 = ch*5/6;
  dx = (x3-x2)/5;
  dy = (y2-y1)/5;
  imgpc = document.createElement('img'); //=new Image();
  imgpc.onload = function() {
    draw();
  };
  imgpc.src = = "images/pc.png";
  color = new Object();   // color={a:"d" ,b:"d",..}
  color['zz']='#ffffff';
  color['ad']='#004586';
  color['ae']='#ff420e';
  color['af']='#ffd320';
  color['bd']='#579d1c';
  color['be']='#7e0021';
  color['bf']='#83caff';
  color['cd']='#314004';
  color['ce']='#aecf00';
  color['cf']='#4b1f6f';
  ln = new Array( 'zz', 'zz', 'zz', 'zz','zz',
                  'zz', 'zz', 'zz', 'zz','zz',
                  'zz', 'zz', 'zz', 'zz','zz'  );  //ln=[a,v,..]
  fm = new Array( 'zz', 'zz', 'zz');
  fmi = 0;
  pkt = 10;
  mov = false;
}

imgpcはcanvas上に描くPCの絵。new Image() でも、createElement() でも同じに作成できます。プロパティとしてsrcを指定すると、ファイルを取得しますが、このまま続けると画像データを取得できないまま次に進んでしまうので、onload属性も設定し、取得後にdraw()を呼ぶようにします。以降の要素は画像データ取得前に実行されても問題がないものです。

colorはパケットの色のobject。javaでいうmapにあたる。AからDに送られるバケットはadで、そのカラーコードは#004586。zzはクリアのため。

lnは左から右までのパケットの並びの配列。横位置が同じところには常に1個のパケットしかありません。

fmはPCの現在の状態を記憶する配列です。fm[0]は、ad,ae,af,zzのどれかという具合になります。

fmiは配列fmから状態の読み取りをするときに0,1,2のうち、どれから始めるかを記憶します。

基準点とdx,dy

PCと配線の描画

canvasをクリアして、配線を書き、PCの画像の幅が0でなければ、画像の中心で位置合わせをし、それを描きます。A〜FをPCの画像のあるなしで位置を考慮して書きます。

function draw() {
  ctx.fillStyle = "#fffff0";
  ctx.fillRect(0,0,cw,ch);
  ctx.fillStyle = "#000000";
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  ctx.lineTo(x2, y2);
  ctx.lineTo(x1, y2);
  ctx.moveTo(x1, y3);
  ctx.lineTo(x2, y2);
  ctx.lineTo(x3, y2);
  ctx.lineTo(x4, y1);
  ctx.moveTo(x4, y2);
  ctx.lineTo(x3, y2);
  ctx.lineTo(x4, y3);
  ctx.stroke();
  pcw = imgpc.width/2;
  pch = imgpc.height/2;
  if (imgpc.width != 0){
    ctx.drawImage(imgpc, x1-pcw, y1-pch);
    ctx.drawImage(imgpc, x1-pcw, y2-pch);
    ctx.drawImage(imgpc, x1-pcw, y3-pch);
    ctx.drawImage(imgpc, x4-pcw, y1-pch);
    ctx.drawImage(imgpc, x4-pcw, y2-pch);
    ctx.drawImage(imgpc, x4-pcw, y3-pch);
  }
  ctx.font = "bold 18px Sans";
  ctx.fillText("A", x1-pcw-18, y1+9);
  ctx.fillText("B", x1-pcw-18, y2+9);
  ctx.fillText("C", x1-pcw-18, y3+9);
  ctx.fillText("D", x4+pcw+4, y1+9);
  ctx.fillText("E", x4+pcw+4, y2+9);
  ctx.fillText("F", x4+pcw+4, y3+9);
}

送信先の変更

ボタンで、起動されるfunctionは onclick="setmov(0,'ad')" のようになっています。第一引数がAからCを表す数値。第二は送出元と送信先からなる二文字です。

function setmov(j,ft){
   fm[j]=ft;
   if (mov!=true) {
      mov=true;
      movmov();
   }
}

movがtrueでない、つまりまだパケットが動いていなければ、動かします。

実際にパケットを動かす

movmovは、3つのことをします。

まずパケットを一つずつ進めます。ln[i]をずらして、ln[0]に新しいパケットを加えます。合流前にも同じ横位置にはA,B,Cどれか一つのPCからでたものしか存在できません。これは手抜きのようですが、共通部分での送信速度の上限と考えれば合理的です。複数のPCが送信するときには交代に送信させなければなりません。

2つめは画面のクリアです。配線を書いてPCの図を描きます。パケットがなくなったあとの線やPCを描着直すことを考えれば、全部書き直したほうが速いということです。

3つ目はパケットの描画です。合流前と分流後はln[i]にa,dが入っていれば上へ、c,fが入っていれば下にずらします。

動作例

function movmov(){
  //パケットを一つ進める
  for (var i=ln.length-1;i>0;i--){
     ln[i] = ln[i-1];
  }
  if (fm[fmi]!="zz") {
      ln[0]=fm[fmi];
      fmi=(fmi+1)%3;
  }else if (fm[(fmi+1)%3]!="zz" ){
      ln[0]=fm[(fmi+1)%3];
      fmi=(fmi+2)%3;
  }else if (fm[(fmi+2)%3]!="zz" ){
      ln[0]=fm[(fmi+2)%3];
      //fmi=(fmi+3)%3 = fmi%3 = fmi;
  }else{
      ln[0]="zz";
  }
  //画面のクリア
  draw();
  //パケットの描画
  var allzz = true;
  for (var i=0;ln.length>i;i++){
     if (ln[i]=='zz') continue;
     allzz = false;
     ctx.fillStyle   = color[ln[i]];
     var y=y2;
     if (4>i) {
        if (ln[i].indexOf('a')>=0){
           y=y2-(4-i)*dy;
        }else if (ln[i].indexOf('c')>=0){
           y=y2+(4-i)*dy;
        }
     } else if (i>9) {
        if (ln[i].indexOf('d')>=0){
           y=y2-(i-9)*dy;
        }else if (ln[i].indexOf('f')>=0){ 
           y=y2+(i-9)*dy;
        }
     }
     var x=x1+(dx*(i+1));
     ctx.fillRect(x-pkt/2,y-pkt/2, pkt, pkt);
  }
  if (!allzz) {
     moving = setTimeout("movmov()",500);
     ctx.fillStyle='#000000';
  }else{
     mov=false;
     ctx.fillStyle='#ffffff';
  }
  //以下はデバッグ用でなくても良い
  //if文中の ctx.fillStyle='#000000'/'#ffffff';も
  ctx.font = "normal 11px courier";
  ctx.fillText(ln, 60, 196);
}

作業が終わったら、自分自身を0.5秒後にもう一度呼び出すようにセットします。

最後の3行はln[i]を書き出しています。lnと書いただけで全部書けるので便利です。動作確認のために作ったのですが、面白いので残してあります。抹消しても動作に影響しません。

PCごとの停止は onclick="setmov(0,'zz')" とするだけです。パケットの送出が止まっても運ばれるパケットがあれば描画は止めるわけには行きません。止めないということでもいいのですが、ln[i]が全部zzになったらsetTimeout()をしないことにします。allzzという変数で調べています。

ln[0]の決め方

%3は3で割った時の余りです。fm[0],fm[1],fm[2]のうち'zz'でない値をln[0]に入れていくのですが、毎回0,1,2と確認していくとfm[0]が'zz'でないときは常にfm[0]が選ばれます。そこで、次回は見つかった番号の次から探し始めることにします。探し始める番号がfmiです。

fmiが0のとき、1のとき、2のときを表にしました。これを%3を使って一度に表す式でプログラムしました。

  if (fm[fmi]!="zz") {
      ln[0]=fm[fmi];
      fmi=(fmi+1)%3;
  }else if (fm[(fmi+1)%3]!="zz" ){
      ln[0]=fm[(fmi+1)%3];
      fmi=(fmi+2)%3;
  }else if (fm[(fmi+2)%3]!="zz" ){
      ln[0]=fm[(fmi+2)%3];
      //fmi=(fmi+3)%3 = fmi%3 = fmi;
  }else{
      ln[0]="zz";
  }

'zz'でないものがなければfmiは変更しません。

fmiが0の時
0,1,2の順で探す
fmiが1の時
1,2,0の順で探す
fmiが2の時
2,0,1の順で探す
fmiの時fmi,(fmi+1)%3,
(fmi+2)%3の順で探す
fm[i]がzzで
ない最初のi
次のfmi fm[i]がzzで
ない最初のi
次のfmi fm[i]がzzで
ない最初のi
次のfmi fm[i]がzzで
ない最初のi
次のfmi
011220 fmi(fmi+1)%3
122001 (fmi+1)%3(fmi+2)%3
200112 (fmi+2)%3fmi

一時停止と再開

パケットの描画のまま、一時停止するボタンも作りました。clearTimeout(moving)で次にmovmov()を起動する予約をキャンセルすることで止まります。mov=falseにしておけば、一時停止ボタンの再押下や他の通信開始ボタンで再開できます。

elseの部分はAのPCの状態を現在の状態(fm[0])にするというギミックで動作を開始しています。

function stopmov(){
   if (mov==true) {
     clearTimeout(moving);
     mov=false;
   }else{
     setmov(0,fm[0]);
   }
}

HTML

<div><canvas id="area1" width="400" height="200"></canvas></div>
<p>
<button type="button" onclick="setmov(0,'ad')">A→D</button>
<button type="button" onclick="setmov(0,'ae')">A→E</button>
<button type="button" onclick="setmov(0,'af')">A→F</button>
<button type="button" onclick="setmov(0,'zz')">A停止</button></p><p>
<button type="button" onclick="setmov(1,'bd')">B→D</button>
<button type="button" onclick="setmov(1,'be')">B→E</button>
<button type="button" onclick="setmov(1,'bf')">B→F</button>
<button type="button" onclick="setmov(1,'zz')">B停止</button></p><p>
<button type="button" onclick="setmov(2,'cd')">C→D</button>
<button type="button" onclick="setmov(2,'ce')">C→E</button>
<button type="button" onclick="setmov(2,'cf')">C→F</button>
<button type="button" onclick="setmov(2,'zz')">C停止</button></p>
<p>
<button type="button" onclick="stopmov()">一時停止/再開</button>
</p>

css

canvas {
  border:solid 1px #999999; 
  max-width: 100%;
}

回線交換方式のプログラムとHTML

予めできている画像を切り替えるだけです。

function showimg(nm){
     document.getElementById("area2").src = "images/exchsw01"+nm+".png";
}

引数がファイル名の一部になります。

<div>
<img src="images/exchsw01ad.png" id="area2">
</div>
<p>
<button type="button" onclick="showimg('ad')">A→D</button>
<button type="button" onclick="showimg('ae')">A→E</button>
<button type="button" onclick="showimg('af')">A→F</button></p><p>
<button type="button" onclick="showimg('bd')">B→D</button>
<button type="button" onclick="showimg('be')">B→E</button>
<button type="button" onclick="showimg('bf')">B→F</button></p><p>
<button type="button" onclick="showimg('cd')">C→D</button>
<button type="button" onclick="showimg('ce')">C→E</button>
<button type="button" onclick="showimg('cf')">C→F</button>
</p>