MulticastSocket_Java

1984·2021년 12월 3일
0

Multicast

  • Unicast
    - 참여자가 둘, 보통 클라이언트와 서버
    • 각각 하나씩 두 개의 IP 주소로 송수신되는 통신
    • Point-to-point 통신 (1:1 통신)
  • Multicast
    - 여러 사람이 관심을 갖는 정보가 있을 경우
    • Unicast 방식으로 동일한 데이터를 여러 수신자에게 보내면, 여러 회 반복하여 Unicast 해야 하므로 매우 비효율적이다
      • MultiPlayer Game
        • Software Distribution
        • Video Conferencing
    • 프로그래머는 1회만 송신하고 네트워크가 알아서 배포하게 하는 방법은 없는가?
  • IP는 세 가지의 전송 방식 제공
    - Unicast
    • Broadcast
    • Multicast
  • IP의 Broadcast
    - Broadcast는 매우 제한적으로 사용되어야 함
    • 몇 개의 Broadcast 패킷으로도 전체 인터넷 점령 가능
    • 라우터는 로컬 네트워크나 서브넷에서만 Broadcast 지원
  • Multicast
    - Unicast 보다는 광범위하지만 Broadcast 통신보다는 좁은 범위의 중간 모델 통신 수단
    • 한 호스트에서 여러 개의 호스트로 데이터를 전송하는 것
      • 모든 호스트가 아니고 Multicast 그룹에 가입한 호스트에 한정
    • 전송할 데이터의 복사는 인터넷 Router에서 처리

Multicast 소켓이란

  • 인터넷의 멀티캐스팅 전송 방식
    - 서버는 데이터그램을 Multicast 주소로 전송
    • 라우터는 Multicast 그룹에 속한 호스트에 데이터가 바르게 전달되는가 상황을 체크
      • 모든 라우터가 Multicast 라우터인 것은 아니다
    • 네트워크의 멀티캐스팅 지원 여부를 확인하기 위해 TTL 이용
      • TTL (Time-To-Live) : 데이터그램이 지나갈 수 있는 라우터의 최대 개수
        • 최대 수의 라우터를 지나가면 데이터그램은 자동 폐기
        • TTL을 사용하여 패킷의 전파를 제한한다.

참고 :

Multicast 주소

  • 첫 4개 비트가 1110인 주소
    - 224.0.0.0 ~ 239.255.255.255

    • e0.0.0.0 ~ ef.ff.ff.ff
      • e0 (1110 0000 = 224)
        • ef (1110 1111 = 239)
    • 클래스D
    • Multicast Group을 나타내는 논리적 IP 주소로서 Multicast Group에 가입한 호스트들이 공유한다.
  • 다른 IP 주소처럼 Multicast 주소도 호스트 이름을 가질 수 있다.

Multicast 그룹

  • 하나의 Multicast 주소를 공유하는 인터넷 호스트들의 집합

  • 멀티캐스트를 수신하기 위해서는 멀티캐스트 그룹에 가입해야 한다. 그러나 그룹에 가입하지 않아도 데이터를 전송할 수는 있다.

  • Multicast 주소로 전송된 임의의 데이터는 그룹의 모든 멤버에게 배포 전달된다.
    - Multicast 그룹에 호스트가 가입하고 탈퇴하는 것은 자유롭다.

    • 그룹은 일시 또는 영구적
  • 현재 자바는 Multicast 그룹을 만들 수 있는 기능이 없지만, 그룹에 가입할 수 있는 클래스는 제공

  • IANA (Internet Assigned Number Authority) 에서 필요한 경우 영구 Multicast 주소 배부

  • MBONE -> 멀티캐스팅을 지원하는 라우터와 호스트로 이루어진 네트워크

예 : 영구히 지속되는 멀티캐스트 그룹의 주소

클라이언트와 서버

  • Multicasting 데이터
    - 호스트가 Multicast 그룹에 데이터를 전송하기 위해서 데이터를 Multicast 데이터그램이나 UDP 데이터 그램에 저장
    - UDP 전송 방식은 TCP 전송 방식에 비해 3배 정도 빠름
  • Scope : 폐기되기 전까지 전파되는 거리
    - Node-local : 네트워크 인터페이스를 벗어나 전파되지 않음
    • Link-local : 라우터를 벗어나 다른 네트워크로 전파되지 않음
  • TTL (Time-To-Live) based scope
    - 멀티캐스팅과 UDP 소켓의 차이점
    • TTL은 IP 헤더에 1 바이트가 할당되어 있으며 값은 0~255 범위 중 하나
    • TTL에 정의된 수는 패킷이 폐기되지 않고 통과할 수 있는 라우터수
      • 라우터를 지날 때마다 1씩 감소
        • 원래 TTL은 라우팅 루프를 벗어날 수 있도록 고안된 것
        • default는 1 / 즉 현행 subnet을 벗어나 전파되지 않음 (Link-local)
        • 0은 현 호스트를 벗어나지 않음을 의미 (Node-local)

예 : TTL의 값에 따른 예상 전파 범위

라우터와 라우팅

  • 동일한 라우터에 연결된 네 개의 클라이언트에 동일한 데이터를 전송했을 경우를 비교

  • 멀티캐스팅에서 프로그래머의 역할
    - 네트워크에서의 전달은 라우터가 전담한다.

    • 프로그래머는 단순히 멀티캐스팅을 사용한다는 사전 작업만을 한다.
      • MulticastSocket을 만들고 소켓을 Multicast 그룹에 가입시킴
        • 전송하려는 DatagramPacket 에 멀티캐스팅 주소를 채워 넣는다.
        • 나머지는 라우터와 MulticastSocket이 전담
    • 멀티캐스팅에서는 라우터가 Multicast를 지원해야 함
      • Multicast 라우터는 인터넷 라우터나 워크스테이션을 IP 멀티캐스팅이 가능하도록 재설정하여 구성
    • Multicast 지원 여부 확인
      • % ping all-routes.mcast.net
        - 지원 안 될 경우 : Unknown host all-routes.mcast.net
        - 지원될 경우 : all-routes.mcast.net is alive

Multicast 소켓 사용하기

  • java.net.MulticastSocket
    - 데이터를 Multicast 하기 위해서 사용하는 클래스
    • MulticastSocket은 DatagramSocket을 상속한 것임
      • public class MulticastSocket extends DatagramSocket imolements Closeable, AutoCloseable
  • 멀티캐스트 소켓 만들기
    - 원격지로부터 Multicast를 수신하기 위해서는 데이터를 기다릴 local port를 제시하며 소켓을 생성해야 한다.
    • MulticastSocket ms = new MulticastSocket(2300);
  • 멀티캐스트 그룹에 가입하기
    - InetAddress group = InetAddress.getByName("224.2.2.2");
    • ms.joinGroup(group);
    • 이제 호스트와 서버 사이의 라우터와 로컬 호스트가 multicast 상황임을 인식한다.
  • Multicast UDP 데이터 수신하기
    - byte[] buffer = new byte[8192];
    • DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
    • socket.receive(dp);
  • 그룹 탈퇴 및 소켓 닫기
    - 더 이상 데이터 수신을 원하지 않으면 그룹 탈퇴
    - ms.leaveGroup(group);
    - ms.close();




  • 멀티캐스트 데이터 전송하기
    - UDP Unicast 데이터 전송과 유사하다.
    • 멀티캐스트 그룹에 가입할 필요 없다.
      • 가입하지 않아도 데이터 전송 가능
    • 패킷에 멀티캐스트 그룹의 주소를 넣고 보낸다.
  • 코드 :
byte[] data = "Here's some mulicast data\r\n".getBytes("UTF-8");
InetAddress group = InetAddress.getByName("experiment.mcast.net");
int port = 4000; // 데이터 수신을 기다리고 있는 UDP 포트

DatagramPacket dp = new DatagramPacket(data, data.length, group, port);
MulticastSocket ms = new MulticastSocket();
ms.send(packet);
  • 포트는 프로그래머가 선정하거나 Java 가 anonymous port를 선택하도록 한다.
    - MulticastSocket 은 DatagramSocket 이므로 DatagramSocket이 이미 점유한 포트를 사용할 수 없다. (SocketException)
  • 생성자 :
    - public MulticastSocket() throws SocketException
    • public MulticastSocket(int port) throws SocketException
    • public MulticastSocket(SocketAddress bindAddress) throws SocketException

익명 포트에 바인드되는 소켓을 생성

  • public MulticastSocket() thrwos SocketException
    - 서버에 적합한 생성자
    • DatagramSocket에서 상속된 getLocalPort() 을 통해 할당된 포트 번호를 얻을 수 있다.
    • 소켓 생성이 불가능하면 SocketException 발생
try {
	MulticastSocket ms = new MulticastSocket();
} catch (SocketException se) {
	System.err.println(se);
}

포트 지정 소켓 생성

  • MulticastSocket ms2 = new MulticastSocket(4000);

  • SocketAddress address = new InetSocketAddress("192.168.254.32", 4000);

  • MulticastSocket ms3 = new MulticastSocket(address);
    - 소켓 생성 불가능 시에 SocketException 발생
    - 루트 권한이 없이 1024 이하의 포트 사용을 시도하는 경우
    - 연결하려는 포트를 다른 MulticastSocket 또는 DatagramSocket이 이미 사용 중일 경우

    • ms2, ms3은 특정 포트에서 데이터 그램을 수신하는 소켓
    • 데이터 수신자는 group 주소의 생성자의 port 번호로 나타내진다.

아직 포트에 바인드되지 않은 소켓 생성

  • MulticastSocket ms = new MulticastSocket(null);
  • ms.setReuseAddress(false);
  • SocketAddress address = new InetSocketAddress(4000);
  • ms.bind(address);

수신용 MulticastSocket 객체를 생성할 때 주의

  • 예 :
    - MulticastSocket socket = new MulticastSocket(8888);
  • 이 코드는 다음과 대등하다.
    - MulticastSocket socket = new MulticastSocket(null);
    • socket.bind(new InetAddress(8888));
  • 그러나 다음과 같이 작성해서는 안 된다.
    - MulticastSocket socket = new MulticastSocket();
    • socket.bind(new InetAddress(port));
      • 기본 생성자는 익명의 포트에 바인드된다.
        • 이미 바인드 되었으므로 특정 포트에 다시 바인드 할 수 없다.

Multicast 그룹과 통신하기

  • 생성된 MulticastSocket 객체로 할 수 있는 4가지
    - Multicast 그룹에 가입하기
    • 그룹의 멤버에게 데이터 보내기
    • 그룹으로부터 데이터 받기
    • Multicast 그룹 탈퇴하기
  • 보내기 / 받기 는 MulticastSocket 의 super class인 DatagramSocket의 메소드인 send() / receive()로 가능

Multicast 그룹에 가입하기

  • 가입하기 :
    - MulticastSocket 으로부터 데이터를 받기 위해, Multicast 그룹에 가입
    - Multicast 그룹에 가입하면 Unicast 데이터그램을 수신하는 방법으로 수신
    • 그룹에 가입하기 위하여 InetAddress 를 joinGroup() 에 전달
    • Multicast 주소 (224.0.0.0 ~ 239.255.255.255) 가 아니면 IOException을 발생
  • 메소드 :
    - public void joinGroup(InetAddress mcastaddr) throws IOException
    • public void joinGroup(SocketAddress mcastaddr, NetworkInterface interface) throws IOException

데이터 수신

  • 일단 그룹에 가입하면 데이터 수신은 Unicast Datagram 의 경우와 동일
    - 데이터 수신자는 group 주소와 생성자의 port 번호로 나타냄
    • 그룹에 가입하면 호스트와 서버 사이에 있는 라우터와 로컬 호스트에 신호가 전달됨
  • 예 :
try{
	
    MulticastSocket ms = new MulticastSocket(4000);
    
    // 데이터를 읽어 올 multicast 주소
    InetAddress ia = InetAddress.getByName("224.2.2.2");
    
    // 그룹에 가입 -> 데이터 수신은 Unicast Datagram과 동일
    ms.joinGroup(ia);
    
    while(true) {
    	DatagramPacket dp = new DatagramPacket(data, data.length);
        ms.receive(dp);
        String s = new String(dp.getData(), "UTF-8");
        System.out.println(s);
        }
    }catch(IOException ex) {}

멀티캐스트 그룹에 가입하기

  • 하나의 MulticastSocket으로 여러 그룹에 가입 가능
    - 멤버십 정보는 멀티캐스트 라우터에 저장됨
    • 수신되는 데이터그램의 주소를 이용하여 어느 그룹인가 판단
  • 한 컴퓨터 내의 복수의 MulticastSocket 과 심지어 한 Java 프로그램 내의 복수의 MulticastSocket 이 동일한 멀티캐스트 그룹에 가입할 수 있다.
    - 로컬 호스트에 전달된 데이터그램은 모든 소켓이 수신하게 된다.
  • public void joinGroup(SocketAddress mcastaddr, NetworkInterface interface) throws IOException
    - 명시된 로컬 네트워크 인터페이스만 멀티캐스트 그룹에 가입하게 된다.
  • 예 : eth0 라는 네트워크 인터페이스만 가입할 경우
MulticastSocket ms = new MulticastSocket();
SocketAddress group = new InetSocketAddress("224.2.2.2", 40);
NetworkInterface ni = NetworkInterface.getByName("eth0");

if (ni != null)
	ms.joinGroup(group, ni);
else 
	ms.joinGroup(group);

그룹 탈퇴와 연결 종료하기

  • public void leaveGroup(InetAddress mcastaddr) throws IOException

  • public void leaveGroup(SocketAddress mcastaddr, NetworkInterface interface) throws IOException
    - Multicast 그룹으로부터 더 이상 데이터그램을 받지 않을 것임을 알림

    • 로컬 멀티캐스트 라우터에게 더 이상 데이터그램을 보내지 말라는 신호를 보냄
    • Multicast 주소가 아니면 (224.0.0.0 ~ 239.255.255.255) IOException 발생
    • 가입하지 않은 그룹을 탈퇴하려 해도 예외가 발생하지는 않음
  • MulticastSocket 클래스의 거의 모든 메소드는 IOException을 발생시키므로 보통 try 블록에서 호출

  • Java 7 에서는 DatagramSocket 클래스가 AutoCloseable 을 구현하고 있으므로 try-with-resource 사용 가능

예 : try-with-resource 사용

try(MulticastSocket ms = new MulticastSocket()) {

// 서버에 연결
}catch (IOException ex) {
	ex.prinStackTrace();
}

예 : try-with-resource 미사용

try{
	ms = new MulticastSocket() // 서버에 연결
} catch (IOException ex) {
	ex.prinStackTrace();
} finally {
	if (ms != null) {
    	try {
        	ms.close();
        } catch (IOException ex) {
        	// 무시
        }
    } // if
} // finally

Multicast 데이터 전송하기

  • public synchronized void send(DatagramPacket dp) throws IOException
    - DatagramSocket 에서 상속 받는 메소드
    • 데이터를 DatagramPacket의 객체 dp 에 채워 넣고, 이것을 전달
    • ttl 매개변수가 없으므로 ttl을 바꿀 필요가 없을 때 사용
      • ds.send(dp);
  • public synchronized void send(DatagramPacket dp, byte ttl) throws Exception
    - MulticastSocket 에서 새로이 정의한 메소드
    • 데이터를 DatagramPacket 객체 dp 에 채워 넣어 전달
    • TTL 은 패킷의 이동 공간 제한 목적
    • 예 :
      • DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);
        • MulticastSocket ms = new MulticastSocket();
        • ms.send(dp, 64);

Multicast 패킷 전송 순서

  • UDP Unicast 와 유사
    - UDP Datagram 을 Multicast address 로 보낸다.

    • 한 가지 차이점은 ttl 값을 지정해야 한다.
  • MulticastSocket() 생성자를 이용해 객체 생성

  • 데이터를 보낼 때는 Multicast 그룹에 가입할 필요가 없다.

  • DaagramPacket 객체를 구성한다.
    - <강조> 바이트 배열

    • 길이
    • 멀티캐스트 그룹 주소
    • 포트
  • MulticastSocket 의 send() 메소드 호출을 통해 데이터 송신

  • MulticastSocket 의 close() 메소드를 사용하여 소켓 종료

예 : 멀티캐스트 데이터 전송하기

try {
InetAddress group = InetAddress.getByName("Experiment.mcast.net");
byte[] data = "Here's some multicast data\r\n".getBytes();
int port = 4000;

DatagramPacket dp = new DatagramPacket(data, data.length, group, port);
MulticastSocket ms = new MulticastSocket();
ms.sned(dp);

} catch(IOException ex) {
	ex.prinStackTrace();
}

TTL

  • 기본적으로 멀티캐스트 소켓의 TTL은 1
    - 패킷이 로컬 네트워크를 벗어나지 못한다.
  • 0-255 범위의 TTL 부여 가능
  • 관련 메소드 :
    - public void setTimeToLive(int ttl) throws IOException
    - DatagramSocket 에서 상속한 send(DatagramPacket dp) 를 사용하여 전송할 때 패킷의 디폴트 TTL 값을 설정할 때 사용
    • public int getTimeToLive() throws IOException
      • DatagramSocket의 default TTL 값을 리턴
  • 전송되는 데이터그램마다 TTL을 설정하는 메소드
    - void send(DatagramPacket dp, int ttl) throws IOException

예 :

try{
	InetAddress group = InetAddress.getByName("experiment.mcast.net");
    bye[] data = "Here's some multicast data\r\n".getBytes();
    int port = 4000;
    
    DatagramPacket dp = new DatagramPacket(data, data.length, group, port);
    MulticastSocket ms = new MulticastSocket();
    ms.setTimeToLive(64);
    ms.send(dp);
} catch(IOException ex) {
	ex.prinStackTrace();
}

네트워크 인터페이스

  • public void setInterface(InetAddress interface) throws SocketException
    - Multi-homed Host 에서, Multicast을 위해서 사용될 네트워크 인터페이스 선택
    • InetAddress 가 로컬 기계 상의 네트워크 인터페이스가 아니면 SocketException 발생

예 : 멀티캐스팅 클라이언트

import java.io.*;
import java.net.*;
public class MulticastClient {
	MulticastSocket socket = null;
	DatagramPacket packet = null;
	InetAddress group = null;
	int port = 20001;
	String address = "237.100.100.1";
	byte[] b = new byte[100];
	public MulticastClient() {
		try {
			socket = new MulticastSocket(port);
			group = InetAddress.getByName(address);
			packet = new DatagramPacket(b, b.length);
			socket.joinGroup(group);
			for (int i = 0; i < 3; i++) {
				socket.receive(packet);
				String notice = new String(packet.getData());
				System.out.println(notice);
			}
			socket.leaveGroup(group);
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) throws IOException {
		new MulticastClient();
	}
}

예 : 멀티캐스팅 서버

import java.io.*; 
import java.net.*; 

public class MulticastServer extends Thread { 
  DatagramSocket socket = null; 
  DatagramPacket packet = null;
  InetAddress multicastGroup = null;
  int port = 20001; 
  String address = "237.100.100.1";
  boolean onAir = true; 

  public MulticastServer() throws IOException { 
    super("멀티캐스트 방송국"); 
    socket = new DatagramSocket(); 
  } 

  public void run() { 
    byte[] b = new byte[100]; 
    while (onAir) { 
      try { 
        b = "이 자료는 멀티캐스트 방송국에서 보내고 있습니다".getBytes(); // 메시지를 바이트 배열로 변환
        multicastGroup = InetAddress.getByName(address); 
        packet = new DatagramPacket(b, b.length, multicastGroup, port); 
        socket.send(packet);
        try { 
          sleep(500);
          System.out.println("방송 중입니다.");
        } catch (InterruptedException e) { } 
      } catch (IOException e) { 
          e.printStackTrace(); 
      }  
    } // end while 
    socket.close(); 
  } 

  public static void main(String[] args) throws java.io.IOException { 
    new MulticastServer().start(); 
  } 
}

간단한 두 예제

Multicast Sniffer

  • Sniffer
    - 네트워크 트래픽을 감시, 분석하여 병목현상 등과 같은 문제점을 발견해 내는 프로그램
    • 트래픽 데이터를 분석하여 트래픽의 동향을 효율적으로 이용하고 네트워크를 최적화 하는 기술
  • 기능 :
    - 명령행에서 멀티캐스트 그룹의 이름을 읽어 와서 이름에 대한 InetAddress 를 생성
    • MulticastSocket 을 생성
    • 멀티 캐스트 그룹에 가입
    • 가입에 성공하면 소켓에서 데이터그램을 수신하고 내용을 System.out 으로 출력
      • 특정 호스트로부터 Multicast 데이터가 수신되고 있는가 확인할 수 있다.
import java.io.*;
import java.net.*;

public class MulticastSniffer {

  public static void main(String[] args) {
  
    InetAddress group = null;
    int port = 0;
  
    // read the address from the command line
    try {
      group = InetAddress.getByName(args[0]);
      port = Integer.parseInt(args[1]);
    } catch (ArrayIndexOutOfBoundsException | NumberFormatException
        | UnknownHostException ex) {
      System.err.println(
          "Usage: java MulticastSniffer multicast_address port");
      System.exit(1);
    }
  
    MulticastSocket ms = null;
    try {
      ms = new MulticastSocket(port);
      ms.joinGroup(group);
      
      
      while (true) {
    byte[] buffer = new byte[8192];
        DatagramPacket dp = new DatagramPacket(buffer, buffer.length);
        ms.receive(dp);
        String s = new String(dp.getData(), "UTF-8");
        System.out.println(s);
      }
    } catch (IOException ex) {
      System.err.println(ex);
    } finally {
      if (ms != null) {
        try {
          ms.leaveGroup(group);
          ms.close();
        } catch (IOException ex) {} 
      }
    } 
  }
}

실행 : D:>java MulticastSniffer 239.255.255.250 1900

출력 예 :

M-SEARCH * HTTP/1.1
HOST: 239.255.255.250:1900
MAN: "ssdp:discover"
MX: 1
ST: urn:dial-multiscreen-org:service:dial:1
USER-AGENT: Google Chrome/54.0.2840.99 Windows

ectionManager:1
NTS: ssdp:alive
SERVER: Linux/2.x.x, UPnP/1.0, pvConnect UPnP SDK/1.0
USN: uuid:7076436f-6e65-1063-8074-001d737f3cef::urn:schemas-upnp-org:service:ConnectionManager:1

ediaReceiverRegistrar:1

.0
OPT:"http://schemas.upnp.org/upnp/1/0/"; ns=01
01-NLS:8cf259374f018f5b416893eea90e5a01

a90e5a01

eea90e5a01

MulticastSender

  • 명령행
    - 멀티캐스트 그룹 주소
    • 수신자의 포트 번호
    • TTL
  • 멀티캐스트 그룹 주소에 대한 InetAddress 를 생성
  • 멀티캐스트 그룹에 가입
  • MulticastSocket 객체 생성
  • 문자열 회차 + 날짜 시간 + "멀티캐스트 데이터입니다." 를 바이트 배열로 바꾸어 DatagramPacket 생성
  • 10회 send
import java.io.*;
import java.net.*;  
import java.util.Date;

public class MulticastSender {         

   public static void main(String[] args) { 

      InetAddress ia = null;
      int port = 0;
      byte ttl = (byte) 1;
      // command line에서 주소 읽어 들임
      try {
         ia = InetAddress.getByName(args[0]);
         port = Integer.parseInt(args[1]);
         if (args.length > 2) ttl = (byte) Integer.parseInt(args[2]);
      } catch (NumberFormatException | IndexOutOfBoundsException
        | UnknownHostException ex)  {
         System.err.println(ex);
         System.err.println(
                 "Usage: java MulticastSender multicast_address port ttl");
         System.exit(1);
      }


      try (MulticastSocket ms = new MulticastSocket()) {
         ms.setTimeToLive(ttl);
         ms.joinGroup(ia);
         for (int i = 0; i < 10; i++) {
            byte[] data = (i + " " + new Date() + " 멀티캐스트 데이터입니다\r\n").getBytes("UTF-8");
            DatagramPacket dp = new DatagramPacket(data, data.length, ia, port);
            ms.send(dp);
         }
         ms.leaveGroup(ia);
      } catch (SocketException ex) {
            System.err.println(ex);
      } catch (IOException ex) {
            System.err.println(ex);
      }  
   }
}   

실행 :

MulticastSniffer 를 다음과 같이 실행시킨 상태에서

  • D:>java MulticastSniffer 237.100.100.1 1900
    MulticastSender 를 다음과 같이 실행
  • D:>java MulticastSender 237.100.100.1 1900

0개의 댓글