【Java】複数端末間の通信を簡単に実現できるマルチキャスト通信

マルチキャストを利用すると複数端末間の通信が簡単に実現できます。

また通信効率も良いので、リアルタイム性を求められるゲームではなかなか使えると思います。



マルチキャスト通信

マルチキャスト通信は複数端末間でのUDP通信ができます。特定のマルチキャストアドレス(224.0.0.0 ~ 239.255.255.255)の中から指定し、同じマルチキャストアドレスに参加している端末すべてにデータを送信できます。

MulticastSocketクラス

マルチキャストを使用する場合はMulticastSocketクラスを利用します。

下記のような感じで指定すると簡単に使用できます。

MulticastSocket sock = new MulticastSocket(ポート番号);
InetAddress group = InetAddress.getByName(マルチキャスト用グループアドレス);
sock.joinGroup(group);//マルチキャストのグループに参加
sock.setLoopbackMode(false);//trueにするとローカルループバックを無効にする。(自分が送信する内容を受信しないようにする)

マルチキャストのサンプル

下記で紹介するMulticast通信のサンプルはユーザーの入力を待ち受け入力した値を送信、受信します。

同PCで試す場合は、同じクラスを2回起動してください。別PC間でも同じクラスで起動してもらうと送受信できると思います。

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.util.Arrays;
import java.util.Scanner;
import java.util.Random;

public class Multicast{
	
	
	MulticastSocket sock;
	InetAddress group;
	
	public static void main(String[] args) {
		new Multicast();
	}
	
	public Multicast() {
		try {
			//使用するポート番号
			sock = new MulticastSocket(10011);
			
			//マルチキャスト用グループアドレス
			group = InetAddress.getByName("224.0.0.100");
			
			//マルチキャストのグループに参加
			sock.joinGroup(group);
			
			//trueにするとローカルループバックを無効にする。(自分が送信する内容を受信しないようにする)
			sock.setLoopbackMode(false);
			
			
			new Thread(this::sendLogic).start();//送信スレッドの起動
			new Thread(this::recvLogic).start();//受信スレッドの起動
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendLogic() {
		try {
			String msg = null;
			Scanner sc = new Scanner(System.in);
			while(!"END".equalsIgnoreCase(msg = sc.next())) {
				byte[] data = msg.getBytes("UTF-8");
				
				//グループアドレスに送信するパケットを構築
				DatagramPacket p = new DatagramPacket(data, data.length,group, 10011);
				System.out.println("送信:"+msg);
				
        			
				//送信
				sock.send(p);
			}
			sc.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void recvLogic() {
		try {
			byte[] data = new byte[1024];
			while(true) {
				DatagramPacket p = new DatagramPacket(data, data.length);
				
				//グループメンバーが送信したデータを受信
				sock.receive(p);
				
				//受信データの表示
				System.out.println("受信:"+new String(Arrays.copyOf(data, p.getLength()),"UTF-8"));
			}
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
PC1実行結果

asdf
送信:asdf
受信:asdf

PC2実行結果

受信:asdf

PC1で適当な値を入力後送信した結果となります。

DatagramChannelの利用したマルチキャスト

DatagramChannelでマルチキャスト通信をすることもできます。

こちらを利用するとByteBufferでの処理、セレクタの使用も可能になります。

使用する場合、setOption(StandardSocketOptions.IP_MULTICAST_IF, ネットワークインターフェース);

のメソッドを呼び出しておきましょう。

ByteBufferの利用

DatagramChannelの利用

下記はDatagramChannelを利用したマルチキャストのサンプルです。

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Scanner;

public class Multicast{
	
	
	DatagramChannel sock;
	InetAddress group;
	
	public static void main(String[] args) {
		new Multicast();
	}
	
	public Multicast() {
		try {
			//有効ネットワークインターフェースを取得
			Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
			NetworkInterface ni = null;
			if ( nis.hasMoreElements() ) {
				ni = nis.nextElement();
			} else {
				throw new IOException("ネットワークインターフェースの取得に失敗しました");
			}
			
			//マルチキャストデータグラムチャネルを作成
			sock = DatagramChannel.open(StandardProtocolFamily.INET)
				.setOption(StandardSocketOptions.SO_REUSEADDR, true)
				.bind(new InetSocketAddress(10013))
				.setOption(StandardSocketOptions.IP_MULTICAST_IF, ni);
			
			//グループアドレスの指定
			group = InetAddress.getByName("225.4.5.6");
			
			//グループへの参加
			MembershipKey key = sock.join(group, ni);
			
			new Thread(this::sendLogic).start();//送信スレッドの起動
			new Thread(this::recvLogic).start();//受信スレッドの起動
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void sendLogic() {
		try {
			String msg = null;
			Scanner sc = new Scanner(System.in);
			while(!"END".equalsIgnoreCase(msg = sc.next())) {
				ByteBuffer bb = StandardCharsets.UTF_8.encode(msg);
				
				System.out.println("送信:"+msg);
				
				//グループアドレスに送信するパケットを構築
				sock.send(bb, new InetSocketAddress(group, 10013));
			}
			sc.close();
		} catch (IOException e) {
			try{sock.close();}catch(Exception e2){}
			e.printStackTrace();
		}
	}
	
	public void recvLogic() {
		try {
			ByteBuffer bb = ByteBuffer.allocate(1024);
			while(true) {
				
				//グループメンバーが送信したデータを受信
				sock.receive(bb);
				
				//データデコード準備
				bb.flip();
				
				//受信データの表示
				System.out.println("受信:"+StandardCharsets.UTF_8.decode(bb).toString());
			}
			
		} catch (IOException e) {
			try{sock.close();}catch(Exception e2){}
			e.printStackTrace();
		}
	}
}
PC1実行結果

asdf
送信:asdf
受信:asdf

PC2実行結果

受信:asdf

PC1で適当な値を入力後送信した結果となります。

やってることはMulticastクラスを使ったサンプルと同じです。

別PC間でのマルチキャストができない場合

環境によってはマルチキャストが別PC間でうまくいかない場合があります。私も環境によってできる時と、できない時に遭遇しました。PCの環境やお使いのネットワークの環境によって変わると思います。

私の環境ではネットワークインターフェースを適切なものに設定すると動きました。

その際に使った、問答無用ですべてのネットワークインターフェースで通信を行うサンプルを一応紹介しておきますので通信できない人は試してみてください。

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.StandardProtocolFamily;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.MembershipKey;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.ArrayList;

public class Multicast{
	
	
	DatagramChannel sock;
	InetAddress group;
	static ArrayList<Multicast> arr = new ArrayList<>();
	
	public static void main(String[] args) {
		
		try {
			
			Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
			while(nis.hasMoreElements() ) {
				NetworkInterface ni = nis.nextElement();
				arr.add(new Multicast(ni));
			}
			send();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public Multicast(NetworkInterface ni) {
		try {
			
			//マルチキャストデータグラムチャネルを作成
			sock = DatagramChannel.open(StandardProtocolFamily.INET)
				.setOption(StandardSocketOptions.SO_REUSEADDR, true)
				.bind(new InetSocketAddress(10013))
				.setOption(StandardSocketOptions.IP_MULTICAST_IF, ni);
			
			//グループアドレスの指定
			group = InetAddress.getByName("225.4.5.6");
			
			//グループへの参加
			MembershipKey key = sock.join(group, ni);
			
			new Thread(this::recvLogic).start();//受信スレッドの起動
			
		} catch (Exception e) {
		}
	}
	
	public void sendLogic(String msg){
		try {
			if ( sock == null ) return;
			ByteBuffer bb = StandardCharsets.UTF_8.encode(msg);
			sock.send(bb, new InetSocketAddress(group, 10013));
		} catch (Exception e) {
			try{sock.close();}catch(Exception e2){}
			e.printStackTrace();
		}
	}
	
	public static void send() {
		try {
			String msg = null;
			Scanner sc = new Scanner(System.in);
			while(!"END".equalsIgnoreCase(msg = sc.next())) {
				for ( Multicast c : arr ){
					c.sendLogic(msg);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	public void recvLogic() {
		try {
			ByteBuffer bb = ByteBuffer.allocate(1024);
			while(true) {
				
				//初期化
				bb.clear();
				
				//グループメンバーが送信したデータを受信
				sock.receive(bb);
				
				//データデコード準備
				bb.flip();
				
				//受信データの表示
				System.out.println("受信:"+StandardCharsets.UTF_8.decode(bb).toString());
			}
			
		} catch (IOException e) {
			try{sock.close();}catch(Exception e2){}
			e.printStackTrace();
		}
	}
}
送信側実行結果

vghjcgfh
受信:vghjcgfh
受信:vghjcgfh

受信側実行結果

受信:vghjcgfh
受信:vghjcgfh

もしこの方法でのみ通信ができた場合はネットワークインターフェースを適切なものに変更する処理を入れてやるのが良いでしょう。

ネットワークインターフェースの取得はここらへんでやってます。

Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
while(nis.hasMoreElements() ) {
	NetworkInterface ni = nis.nextElement();
	arr.add(new Multicast(ni));
}

Java

Posted by nompor