画像をUDPで送る/受け取る


[注意] 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日追記)。。


UDPサーバ(受信側)




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  
% java UDPWinShareServer -p 8888  

受信に失敗した UDP パケットの部分の影響で、画像が欠けていることがわかります。 しばらく時間がたつと、だんだん画像が、クライアント側の表示と等しくなっていきます。


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  
% java UDPWinShareClient -h 192.168.66.255 -p 8888  
Pressed 45 79
menu 2
Released 45 79
Pressed 229 194
Released 614 405
Pressed 535 293
Released 697 483
Pressed 38 127
menu 4
Released 38 127
Pressed 217 126
Released 358 414
Pressed 74 46
menu 1
Released 75 47
...

上記のUDPWinShareClient.javaの実行例は、192.168.66.0/24 のネットワークにおける ブロードキャストアドレス(192.168.66.255)を指定しています。 各自の状況に合わせてブロードキャストアドレスを選んでください。 また、PC1台で試す場合はIPアドレスは localhost または 127.0.0.1 と指定して下さい。