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();
}
}
실행 순서는 상관없지만, 수신자를 먼저 실행하고 발신자를 실행해야만 발신자가 보낸 데이터를 수신자가 모두 받을 수 있다. 발신자를 먼저 실행하면 수신자가 실행하기 전에 보낸 데이터는 받을 수 없다.
[실행결과]
'👨🏫Study > JAVA' 카테고리의 다른 글
[JAVA] 19. NIO 기반 입출력 및 네트워킹(1) (0) | 2022.04.06 |
---|---|
[JAVA] 인텔리제이 오류 해결 (0) | 2022.04.01 |
[JAVA] 예외(EXCEPTION) 처리 (0) | 2022.03.31 |
[JAVA] 18 - 3. 채팅 서버, 클라이언트 구현 (0) | 2022.03.30 |
[JAVA] NullPointerException 처리방법 (0) | 2022.03.30 |
댓글