슬기로운 개발생활

[Java] NIO 기반 입출력 및 네트워킹 - UDP 채널 : 발신자 / 수신자 / 통신

by coco3o
반응형

UDP 채널

NIO에서 UDP 채널은 DatagramChannel이다.

DatagramChannel도 TCP 채널과 마찬가지로 블로킹과 넌블로킹 방식으로 사용할 수 있지만, 여기선 블로킹 방식만 다룬다.


발신자 만들기

발신자 프로그램을 구현해보면서 DatagramChannel을 사용하는 방법에 대해 알아보자.

 

DatagramChannel을 생성하려면 open() 메소드를 호출해야 한다. open()은 ProtocolFamily 인터페이스 타입 매개값을 가진다.

 

이 객체의 역할은 IPv4와 IPv6을 구분하는 것이다. 구현 객체는 StandardProtocolFamily 열거 상수를 사용한다.

 

다음은 IPv4를 사용하는 DatagramChannel을 생성하는 코드이다.

DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET);

DatagramChannel을 이용해서 데이터를 보내려면 send() 메소드를 사용한다.

 

send() 메소드의 첫 번째 매개값은 보낼 데이터를 가지고 있는 ByteBuffer이고,

 

두 번째 매개값은 수신자IP와 포트 정보를 가지고 있는 SocketAddress이다.

 

SocketAddress는 추상 클래스이므로 하위 클래스인 InetSocketAddress 객체를 생성하고 대입하면 된다.

 

send() 메소드의 리턴 값은 실제로 보낸 바이트 수이다.

 int byteCount = datagramChannel.send(byteBuffer, new InetSocketAddress("localhost", 5001));

더 이상 보낼 데이터가 없을 경우 close() 메소드 호출

datagramChannel.close();

 

* UdpSendExample.java 발신자

package javaStudy;
import java.net.InetSocketAddress;
import java.net.StandardProtocolFamily;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.Charset;
public class UdpSendExample {
public static void main(String[] args) throws Exception {
DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET);
System.out.println("[발신 시작]");
for (int i = 1; i < 3; i++) {
String data = "메시지 " + i;
Charset charset = Charset.forName("UTF-8");
ByteBuffer byteBuffer = charset.encode(data);
int byteCount = datagramChannel.send(byteBuffer, new InetSocketAddress("localhost", 5001));
System.out.println("[보낸 바이트 수] " + byteCount + " bytes");
}
System.out.println("[발신 종료]");
datagramChannel.close();
}
}

 

수신자 만들기

이번엔 DatagramCHannel로 수신자 프로그램을 구현하는 방법에 대해 알아본다.

 

DatagramChannel을 이용해서 데이터를 받으려면 bind() 메소드를 호출해서 포트와 바인딩을 해야 한다.

 

매개값은 SocketAddress 타입으로 InetSocketAddress 객체를 대입하면 된다.

DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET);
datagramChannel.bind(new InetSocketAddress(5001));

 

포트와 바인딩이 되었다면 다음과 같이 receive() 메소드로 데이터를 받을 수 있다.

 

receive() 메소드의 매개값은 받은 데이터를 저장할 ByteBuffer이고,

리턴 타입은 원격 클라이언트의 IP와 포트 정보를 가지고 있는 SocketAddress이다. 실제로는 InetSocketAddress가 리턴된다.

SocketAddress socketAddress = datagramChannel.receive(ByteBuffer dst);

 

데이터를 받기 전까지 receive() 메소드는 블로킹되고, 데이터를 받으면 리턴된다.

 

수신자는 항상 데이터를 받을 준비를 해야 하므로 작업 스레드를 생성해서 receive() 메소드를 반복적으로 호출해야 한다.

 

작업 스레드를 종료시키는 방법은 두 가지이다.

1. receive() 메소드가 블로킹되어 있는 상태에서 작업 스레드의 interrupt()를 호출시켜 ClosedByInterruptException 예외를 발생시킨다.

 

2. 아래와 같이 DatagramChannel의 close()를 호출시켜 AsynchronousCloseException 예외를 발생시키는 것이다.

그리고 예외처리 코드에서 작업 스레드를 종료시키면 된다.

datagramChannel.close();

 

* UdpReceiveExample.java 수신자

package javaStudy;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.StandardProtocolFamily;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.charset.Charset;
public class UdpReceiveExample extends Thread {
public static void main(String[] args) throws Exception {
DatagramChannel datagramChannel = DatagramChannel.open(StandardProtocolFamily.INET);
datagramChannel.bind(new InetSocketAddress(5001));
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("[수신 시작]");
try {
while (true) {
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
SocketAddress socketAddress = datagramChannel.receive(byteBuffer);
byteBuffer.flip();
Charset charset = Charset.forName("UTF-8");
String data = charset.decode(byteBuffer).toString();
System.out.println("[받은 내용: " + socketAddress.toString() + "] " + data);
}
} catch (Exception e) {
System.out.println("[수신 종료]");
}
}
};
thread.start();
Thread.sleep(10000);
datagramChannel.close();
}
}

 

Reference : '이것이 자바다' 19장

반응형
블로그의 프로필 사진

블로그의 정보

슬기로운 개발생활

coco3o

활동하기