본문 바로가기
👨‍🏫Study/JAVA

[JAVA] 18 - 4. UDP 네트워킹

by 코푸는 개발자 2022. 3. 31.
728x90

 UDP(User Datagram Protocol)란?

비연결 지향적 프로토콜이다. 비연결 지향적이란 데이터를 주고받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식이다. 연결 과정이 없기 때문에 TCP보다는 빠른 전송을 할 수 있지만 데이터 전달의 신뢰성은 떨어진다.

*UDP는 발신자가 데이터 패킷을 순차적으로 보내더라도 이 패킷들은 서로 다른 통신 선로를 통해 전달될 수 있다. 먼저 보낸 패킷이 느린 선로를 통해 전송될  경우 나중에 보낸 패킷보다 늦게 도착할 수 있다. 또한 일부 패킷은 잘못된 선로로 전송되어 잃어버릴 수도 있다.

 

 

자바에서는 UDP 프로그래밍을 위해 java.net.DatagramSocket(발신점과 수신점에 해당하는 클래스)과 java.net.DatagramPacket(주고받는 패킷 클래스) 클래스를 제공하고 있다.

[데이터 통신 과정]

발신자 > DatagramSocket > DatagramPacket > DatagramSocket > 수신자

 

발신자 구현

//DatagramSocket 객체 생성
DatagramSocket datagramSocket = new DatagramSocket();

보내고자 하는 데이터를 byte[ ] 배열로 생성하는데, 문자열인 경우 다음과 같이 UTF-8로 인코딩해서 byte[ ] 배열을 얻으면 된다.

byte[ ] byteArr = data.getBytes("UTF-8");

이제 데이터와 수신자 정보를 담고 있는 DatagramPacket을 생성해야 하는데, DatagramPacket 생성자의 첫 번째 매개값은 보낼 데이터의 byte[] 배열이고, 두 번째 매개값은 byte[] 배열에서 보내고자 하는 항목 수이다. 전체 항목을 보내려면 length 값으로 대입하면 된다. 세 번째 매개값은 수신자 IP와 포트 정보를 가지고 있는 SocketAddress이다. SocketAddress는 추상 클래스이므로 하위 클래스인 InetSocketAddress 객체를 생성해서 대입하면 된다. 다음은 로컬 PC 5001번을 수신자로 하는 DatagramPacket을 생성하는 코드이다.

byte[ ] byteArr = data.getBytes("UTF-8");
DatagramPacket packet = new DatagramPacket(
	byteArr, byteArr.length,
    new InetSocketAddress("localhost", 5001)
);

DatagramPacket을 생성했다면, 이것을 매개값으로 해서 DatagramSocket의 send() 메소드를 호출하면 수신자에게 데이터가 전달된다.

datagramSocket.send(packet);

더 이상 보낸 데이터가 없을 경우에는 DatagramSocket을 닫기 위해 close() 메소드를 호출한다.

datagramSocket.close();

 

다음은 발신자 프로그램의 전체 코드이다. for문을 두 번 반복하는데 각각 "메시지1", "메시지2" 문자열을 전송하도록 했다.

[UdpSendExample.java]

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;

public class UdpSendExample {
	public static void main(String[] args) throws Exception {
		DatagramSocket datagramSocket = new DatagramSocket();//Datagram Socket 생성 
		
		System.out.println("[발신 시작]");
		
		for(int i=1; i<3; i++) {
			String data = "메시지" + i;
			byte[] byteArr = data.getBytes("UTF-8");
            //Datagram Packet 생성
			DatagramPacket packet = new DatagramPacket(
				byteArr, byteArr.length, 
				new InetSocketAddress("localhost", 5001)
			);
			//DatagramPacket 전송
			datagramSocket.send(packet);
			System.out.println("[보낸 바이트 수]: " + byteArr.length + " bytes");
		}
		
		System.out.println("[발신 종료]");
		//DatagramSocket 닫기
		datagramSocket.close();
	}
}

 

수신자 구현

수신자로 사용할 DatagramSocket 객체는 다음과 같이 바인딩할 포트 번호를 매개값으로 지정하고 생성해야 한다.

DatagramSocket datagramSocket = new DatagramSocket(5001);

DatagramSocket 생성 후, receive() 메소드를 호출해서 패킷을 읽을 준비를 한다. receive() 메소드는 패킷을 받을 때까지 블로킹되고, 패킷이 도착하면 매개값으로 주어진 DatagramPacket에 패킷 내용을 저장한다.

datagramSocket.receive(datagramPacket);

 

패킷의 내용을 저장할 DatagramPacket 객체 생성, 첫 번째 매개값은 읽은 패킷 데이터를 저장할 바이트 배열이고, 두 번째 매개값은 읽을 수 있는 최대 바이트 수로 첫 번째 바이트 배열의 크기와 같거나 작아야 한다.

DatagramPacket datagramPacket = new DatagramPacket(new byte[100], 100);

 

receive() 메소드로 패킷을 읽었다면 DatagramPacket의 getData() 메소드로 데이터가 저장된 바이트 배열을 얻어낼 수 있다. 그리고  getLength()를 호출해서 읽은 바이트 수를 얻을 수 있다. (인코딩된 문자열 디코딩해서 문자열 얻기)

String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8");

 

만약 수신자가 패킷을 받고 나서 발신자에게 응답 패킷을 보내고 싶다면 발신자의 IP와 포트를 알아야 한다. DatagramPacket의 getSocketAddress() 를 호출하면 발신자의 SocketAddress 객체를 얻어 낼 수 있어, 발신자에게 응답 패킷을 보낼 때 send() 메소드에서 이용할 수 있다.

SocketAddress socketAddress = packet.getSocketAddress();

 

수신자는 항상 데이터를 받을 준비를 해야 하므로 작업 스레드를 생성해서 receive() 메소드를 반복적으로 호출해야 한다. 작업 스레드를 종료시키는 방법은 receive() 메소드가 블로킹되어 있는 상태에서 DataGramSocket의 close()를 호출하면 된다. 이 경우 receive() 메소드에서 socketException이 발생하게 되고, 예외 처리 코드에서 작업 스레드를 종료시키면 된다.

datagramSocket.close();

 

수신자

[UdpReceiveExample.java]

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UdpReceiveExample extends Thread {
	public static void main(String[] args) throws Exception {
		DatagramSocket datagramSocket = new DatagramSocket(5001);//5001번 포트에서 수신하는 DatagramSocket 생성
		
		Thread thread = new Thread() {
			@Override
			public void run() {
				System.out.println("[수신 시작]");
				try {
					while(true) {
                    	//DatagramPacket 수신
						DatagramPacket packet = new DatagramPacket(new byte[100], 100);
						datagramSocket.receive(packet);
						
						String data = new String(packet.getData(), 0, packet.getLength(), "UTF-8");
						System.out.println("[받은 내용: "  + packet.getSocketAddress() + "] " + data);
					}
				} catch (Exception e) {
					System.out.println("[수신 종료]");
				}
			}			
		};
		thread.start();
		
		Thread.sleep(10000);
		datagramSocket.close();
	}
}

실행 순서는 상관없지만, 수신자를 먼저 실행하고 발신자를 실행해야만 발신자가 보낸 데이터를 수신자가 모두 받을 수 있다. 발신자를 먼저 실행하면 수신자가 실행하기 전에 보낸 데이터는 받을 수 없다.

 

[실행결과]

728x90

댓글