Java Category/Java

[Java] UDP 네트워킹

ReBugs 2023. 8. 13.

이 게시글은 이것이 자바다(저자 : 신용권, 임경균)의 책과 동영상 강의를 참고하여 개인적으로 정리하는 글임을 알립니다. 


IP 주소로 프로그램들이 통신할 때는 약속된 데이터 전송 규약이 있다.

이것을 전송용 프로토콜이라고 부른다.

인터넷에서 전송용 프로토콜은 아래의 두 가지로 나뉜다.

  • TCP(Transmission Control Protocol) : 우선 연결 후 데이터 전송
  • UDP(User Datagram Protocol) : 우선 데이터 전송 후 연결

 

UDP 네트워킹

UDP는 발신자가 일방적으로 수신자에게 데이터를 보내는 방식이다.

TCP처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 전송 속도가 상대적으로 빠르다.

 

UDP는 고정 회선이 아니라 여러 회선을 통해서 데이터가 전송되기 때문에 특정 회선의 속도에 따라 데이터가 순서대로 전달되지 않거나 잘못된 회선으로 데이터 손실이 발생할 수 있다.

따라서 데이터 전달의 신뢰성보다 속도가 중요하다면 UDP를 사용해야 한다.(스트리밍 서비스 등)

 

자바는 UDP 네트워킹을 위해 java.net 패키지에서 DatagramSocket과 DatagramPacket 클래스를 제공하고 있다.

DatagramSocket은 발신점과 수신점에 해당하고 DatagramPacket은 주고받는 데이터에 해당한다.

출처 : 이것이 자바다 유튜브 동영상 강의

 


 

UDP 서버

DatagramSocket 객체 생성

UDP 서버를 위한 DatagramSocket 객체를 생성할 때에는 아래와 같이 바인딩할 Port 번호를 생성자 매개값으로 제공해야 한다.

DatagramSocket datagramSocket = new DatagramSocket(50001);

데이터 수신

서버는 클라이언트가 보낸 DatagramPacket을 항상 받을 준비를 해야 한다.

이 역할을 하는 메소드가 receive() 메소드이다.

receive() 메소드는 데이터를 수신할 때까지 블로킹(대기 상태)되고, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장한다.

DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

첫 번째 매개값은 수신된 데이터를 저장할 배열이고, 두 번째 매개값은 수신할 수 있는 최대 바이트 수이다.

당연히 두 번째 매개값은 배열의 최대 인덱스보다 같거나, 작아야 한다.


수신된 데이터 얻기

receive() 메소드가 실행된 후 수신된 데이터와 바이트 수를 얻는 방법은 아래와 같다.

byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

 

읽은 데이터가 문자열이라면 아래와 같이 String 생성자를 이용해 문자열을 얻을 수 있다.

String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");

데이터 송신

반대로 서버가 클라이언트에게 처리 내용을 보내려면 클라이언트 IP 주소와 Port 번호가 필요한데, 이것은 receive()로 받은 DatagramPacket에서 얻을 수 있다.

getSocketAddress() 메소드를 호출하면 정보가 담긴 SocketAddress 객체를 얻을 수 있다.

SocketAddress socketAddress = receivePacket.getSocketAddress();

 

이렇게 얻은 SocketAddress 객체는 아래와 같이 클라이언트로 보낼 DatagramPacket을 생성할 때 네 번째 매개값으로 사용된다.

DatagramPacket 생성자의 첫 번째 매개값은 바이트 배열이고 두 번째는 시작 인덱스, 세 번째는 보낼 바이트 수이다.

String data = "처리 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);

 

DatagramPacket을 클라이언트로 보낼 때는 send() 메소드를 이용한다.

datagramSocket.send(sendPacket);

서버 종료하기

UDP 서버를 종료하고 싶을 경우에는 아래와 같이 close() 메소드를 호출하면 된다.

datagramSocket.close();

 


사용 예제

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.Scanner;

public class NewsServer {
	private static DatagramSocket datagramSocket = null;
	
	public static void main(String[] args) throws Exception {
		System.out.println("--------------------------------------------------------------------");
		System.out.println("서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요.");
		System.out.println("--------------------------------------------------------------------");		
		
		//UDP 서버 시작
		startServer();
		
		//키보드 입력
		Scanner scanner = new Scanner(System.in);
		while(true) {
			String key = scanner.nextLine();
			if(key.toLowerCase().equals("q")) {
				break;
			}
		}
		scanner.close();
		
        //TCP 서버 종료
        stopServer();		
	}	
		
	public static void startServer() {
		//작업 스레드 정의
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {
					//DatagramSocket 생성 및 Port 바인딩
					datagramSocket = new DatagramSocket(50001);
					System.out.println( "[서버] 시작됨");
					
					while(true) {
						//클라이언트가 구독하고 싶은 뉴스 주제 얻기
						DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
						datagramSocket.receive(receivePacket);
						String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
						
						//클라이언트의 IP와 Port 얻기
						SocketAddress socketAddress = receivePacket.getSocketAddress();
						
						//10개의 뉴스를 클라이언트로 전송
						for(int i=1; i<=10; i++) {
							String data = newsKind + ": 뉴스" + i;
							byte[] bytes = data.getBytes("UTF-8");
							DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
							datagramSocket.send(sendPacket);
						}
					}
				} catch (Exception e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}			
		};
		//스레드 시작
		thread.start();
	}
		
	public static void stopServer() {
		//DatagramSocket을 닫고 Port 언바인딩
		datagramSocket.close();
		System.out.println( "[서버] 종료됨 ");
	}
}

 


 

UDP 클라이언트

서버에 연결하기

UDP 클라이언트는 서버에 요청 내용을 보내고 그 결과를 받는 역할을 한다.

클라이언트를 위한 DatagramSocket 객체는 기본 생성자로 한다.

Port 번호는 운영체제가 자동으로 부여하기 때문에 따로 지정할 필요가 없다.

DatagramSocket datagramSocket = new DatagramSocket();

데이터 송신

요청 내용을 보내기 위한 DatagramPacket을 생성하는 방법은 아래와 같다.

String data = "정치";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, new InetSocketAddress("localhost", 50001));

첫 번째 매개값은 바이트 배열이고, 두 번째 매개값은 바이트 배열에서 보내고자 하는 바이트 수이다.

세 번째 매개값은 서버의 IP와 Port 정보를 가지고 있는 InetSocketAddress 객체이다.

 

생성된 DatagramPacket을 매개값으로 해서 send() 메소드를 호출하면 서버로 DatagramPacket이 전송된다.

datagramSocket.send(sendPacket);

데이터 수신

서버에서 처리 결과가 언제 올지 모르므로 항상 받을 준비를 하기 위해 receive() 메소드를 호출한다.

receive() 메소드는 데이터를 수신할 때까지 블로킹(대기 상태)되고, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장한다.

DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

첫 번째 매개값은 수신된 데이터를 저장할 배열이고, 두 번째 매개값은 수신할 수 있는 최대 바이트 수이다.

당연히 두 번째 매개값은 배열의 최대 인덱스보다 같거나, 작아야 한다.

 


수신된 데이터 얻기

receive() 메소드가 실행된 후 수신된 데이터와 바이트 수를 얻는 방법은 아래와 같다.

byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

 

읽은 데이터가 문자열이라면 아래와 같이 String 생성자를 이용해 문자열을 얻을 수 있다.

String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");

서버 종료하기

클라이언트를 종료하고 싶을 경우에는 아래와 같이 close() 메소드를 호출하면 된다.

datagramSocket.close();

 


사용 예제

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

public class NewsClient {
	public static void main(String[] args) {
		try {
			//DatagramSocket 생성
			DatagramSocket datagramSocket = new DatagramSocket();
			
			//구독하고 싶은 뉴스 주제 보내기
			String data = "정치";
			byte[] bytes = data.getBytes("UTF-8");
			DatagramPacket sendPacket = new DatagramPacket(
				bytes, bytes.length, 	new InetSocketAddress("localhost", 50001)
			);
			datagramSocket.send(sendPacket);

			while(true) {
				//뉴스 받기
				DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
				datagramSocket.receive(receivePacket);
				
				//문자열로 변환
				String news = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
				System.out.println(news);
				
				//10번째 뉴스를 받았을 경우 while 문 종료
				if(news.contains("뉴스10")) {
					break;
				}
			}
			
			//DatagramSocket 닫기
			datagramSocket.close();
		} catch(Exception e) {
		}
	}
}

 

 

 

댓글