[注意] IPヘッダ中の「ヘッダ長フィールド(4bit)」が「ヘッダの長さ/4」を表していて、 通常の値は5でありIPヘッダ長が20byteであることを表します(IPヘッダの最大長は60byte)。 IPヘッダ中の「全データ長フィールド」はIPデータグラムの全長をバイト数で表しており、 これと「ヘッダ長フィールド」から 「IPデータグラム中のデータの始まりの位置とデータの長さ」がわかります。 全データ長フィールドは 16bit 幅なのでIPデータグラムの最大サイズは 65535 バイトです。 65535バイトのIPデータグラムを転送することは可能ですが、 普通はIP層でフラグメント化されて送られます。
実は、ホストは 576 バイト以上のデータグラムを受信するように要求されてはいません。 TCPはユーザのデータをフラグメント化するのでこの制限の影響を受けません。 UDPでは多くのアプケーション(RIP, TFTP, BOOTP, DNS, SNMP など)が ユーザのデータを 512バイト以下の長さにすることでこの影響から免れています (NFSを実装する多くのシステムでは、8192byte以上のIPデータグラムを許していますが)。
ブロードキャスト・アドレスにUDPデータグラムを送信する場合は、 512byte以下のデータサイズになるようにする方が安全です。 そうしないと、システムによっては送信時にerror が発生します。
下に示すプログラム例では一度に送るUDPのデータ長は 4*2+3*128=392バイト以下に制限しています。
UDPはコネクションを確立しないので、ブロードキャストアドレスを宛先として送信できます。 そのため、同じネットワークに接続されている複数のクライアントに一斉に情報を送ることができます。
津田塾大学計算センターs205wsの教室のネットワークのネットワーク・アドレスは 172.17.0.0/23 なので、 ブロードキャスト・アドレスは 172.17.1.255 となります(2019年4月8日時点)。
自宅のブロードキャストアドレスは、自分のPCのIPアドレスをipconfig コマンド(Windows)や/sbin/ifconfig コマンド(Mac)などで調べて 自分で計算して下さい(2020年6月20日追記)。。
UDPWinShareRecv.java |
import java.net.*; import java.io.*; import java.util.*; import java.awt.*; import java.awt.geom.*; public class UDPWinShareRecv implements Runnable { public static final int maxPixels = 128; public static final int maxlen = 4 * 2 + 3 * maxPixels; // int x, int y, (byte r, byte g,byte b) * maxPixels UDPServer serv; ToyGraphics tg; public UDPWinShareRecv(UDPServer serv, ToyGraphics tg) { this.serv = serv; this.tg = tg; } public void run() { DatagramPacket pkt = new DatagramPacket(new byte[maxlen],maxlen); System.out.println("start"); try { for (;;) { synchronized (serv) { serv.receive(pkt); } byte[] data = Arrays.copyOfRange(pkt.getData(), pkt.getOffset(), pkt.getLength()); int x = mkInt(data[0],data[1],data[2],data[3]); int y = mkInt(data[4],data[5],data[6],data[7]); int n=(data.length - 4*2)/3; // number of pixels //System.err.println(x+" "+y+" "+n); synchronized (tg.g2d) { for (int i=0; i<n; i++) { int r = data[4*2+3*i] & 0xff; int g = data[4*2+3*i+1] & 0xff; int b = data[4*2+3*i+2] & 0xff; tg.setRGB(x+i,y,r,g,b); } tg.repaint(x,y,n,1); } pkt.setLength(maxlen); } } catch (IOException e) { System.err.println("io exception"); } System.out.println("stop"); serv.close(); } int mkInt(byte a,byte b,byte c,byte d) { int n = ((a << 24) & 0xff000000) + ((b << 16) & 0x00ff0000) + ((c << 8) & 0x0000ff00) + (d & 0x000000ff); return n; } } |
UDPWinShareServer.java |
import java.net.*; import java.io.*; import java.util.*; import java.awt.*; import java.awt.geom.*; public class UDPWinShareServer extends ToyGraphics { UDPServer serv; public UDPWinShareServer(int port) { super(1280, 720); serv = new UDPServer(port); } public void run() { synchronized (this) { g2d.setPaint(Color.black); g2d.fill(new Rectangle2D.Double(0, 0, width, height)); // 画面全体を黒で塗りつぶす repaint(0,0,width,height); } new Thread(new UDPWinShareRecv(serv,this)).run(); } public static void main(String[] args) { int port = 8888; for (int i=0; i<args.length; i++) { if (args[i].equals("-p")) port = Integer.parseInt(args[++i]); else { System.err.println("unknown option: "+args[i]); } } UDPWinShareServer app = new UDPWinShareServer(port); app.run(); } } |
UDPWinShareServer.javaの実行例 |
% javac UDPWinShareServer.java |
受信に失敗した UDP パケットの部分の影響で、画像が欠けていることがわかります。 しばらく時間がたつと、だんだん画像が、クライアント側の表示と等しくなっていきます。
UDPWinShareSend.java |
import java.net.*; import java.io.*; import java.util.*; import java.awt.*; import java.awt.geom.*; public class UDPWinShareSend implements Runnable { public static final int maxPixels = UDPWinShareRecv.maxPixels; public static final int maxlen = UDPWinShareRecv.maxlen; UDPClient cli; ToyGraphics tg; int interval = 3; // seconds public UDPWinShareSend(UDPClient cli, ToyGraphics tg) { this.cli = cli; this.tg = tg; } public void run() { byte[] buf = new byte[maxlen]; try { for (;;) { for (int y=0; y<tg.height; y++) { for (int x=0; x <tg.width; x+=maxPixels) { int len=maxPixels; if (x+len > tg.width) len = tg.width - x; int[] pixels = new int[len]; synchronized (tg.g2d) { for (int i=0; i<len; i++) { pixels[i] = tg.image.getRGB(x+i,y); } } setBuf(buf,x,y,pixels); cli.send(buf); } Thread.sleep(10); } Thread.sleep(interval * 1000); } } catch (IOException e) { System.err.println("io exception"); } catch (InterruptedException e) { // for Thread.sleep System.err.println("interrupted exception"); } System.out.println("stop"); cli.close(); } void setBuf(byte[] buf,int x,int y,int[] pixels) { buf[0] = (byte) ((x >>> 24) & 0xff); buf[1] = (byte) ((x >>> 16) & 0xff); buf[2] = (byte) ((x >>> 8) & 0xff); buf[3] = (byte) (x & 0xff); buf[4] = (byte) ((y >>> 24) & 0xff); buf[5] = (byte) ((y >>> 16) & 0xff); buf[6] = (byte) ((y >>> 8) & 0xff); buf[7] = (byte) (y & 0xff); for (int i=0; i<pixels.length; i++) { buf[8 + 3*i] = (byte) ((pixels[i] >> 16) & 0xff); // Red buf[8 + 3*i + 1] = (byte) ((pixels[i] >> 8) & 0xff); // Green buf[8 + 3*i + 2] = (byte) (pixels[i] & 0xff); //Blue } } } |
UDPWinShareClient.java |
import java.util.*; import java.awt.*; import java.awt.geom.*; import java.awt.event.*; public class UDPWinShareClient extends WinDraw { UDPWinShareSend cli; String hostname; int port; public UDPWinShareClient(String hostname, int port) { this(hostname,port,1280,720); } public UDPWinShareClient(String hostname, int port, int w,int h) { super(w,h); this.hostname = hostname; this.port = port; } public void run() { super.redraw(); cli = new UDPWinShareSend(new UDPClient(hostname,port,0), this); new Thread(cli).run(); } public static void main(String[] args) { String hostname = "localhost"; int port = 8888; for (int i=0; i<args.length; i++) { if (args[i].equals("-h")) hostname = args[++i]; else if (args[i].equals("-p")) port = Integer.parseInt(args[++i]); else { System.err.println("unknown option: "+args[i]); } } UDPWinShareClient app = new UDPWinShareClient(hostname,port); app.run(); } } |
UDPWinShareClient.javaの実行例 |
% javac UDPWinShareClient.java |
上記のUDPWinShareClient.javaの実行例は、192.168.66.0/24 のネットワークにおける ブロードキャストアドレス(192.168.66.255)を指定しています。 各自の状況に合わせてブロードキャストアドレスを選んでください。 また、PC1台で試す場合はIPアドレスは localhost または 127.0.0.1 と指定して下さい。