UDP는 연결이 없는 프로토콜로, 데이터 전송 시 연결 설정 과정이 없다.
데이터는 데이터그램(datagram) 이라는 작은 패킷 단위로 전송된다.
자바에서는 java.net 패키지의 DatagramSocket, DatagramPacket 클래스를 사용하여 UDP 통신을 구현한다.
어제 설명한 Echo 프로그래밍을 UDP로 구현해보자.
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPEchoServer {
private static final int PORT = 9876; // 포트번호는 아무거나
private static final int BUFFER_SIZE = 1024; // 버퍼 사이즈
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket(PORT)) { // 우체국(소켓)
System.out.println("UDPEchoServer start");
byte[] buffer = new byte[BUFFER_SIZE]; // 박스(버퍼)
while (true) {
// 클라이언트가 보낸 메시지를 받아옴
DatagramPacket packet = new DatagramPacket(buffer, buffer.length); // 그릇(패킷)
socket.receive(packet);
// 받아온 데이터 범위 지정
String message = new String(packet.getData(), 0, packet.getLength());
System.out.println("받은메세지 : " + message);
// Echo 응답 만들기
String responseMessage = "Echo : " + message;
byte[] responseBuffer = responseMessage.getBytes();
// 데이터를 전송하기 위해서는 데이터, 데이터 크기, 클라이언트 IP, 클라이언트 port가 필요
DatagramPacket sendPacket = new DatagramPacket(
responseBuffer,
responseBuffer.length,
packet.getAddress(),
packet.getPort()
);
socket.send(sendPacket);
System.out.println("전송 : " + responseMessage);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
public class UDPEchoClient {
private static final String SERVER_HOST = "localhost";
private static final int SERVER_PORT = 9876;
private static final int BUFFER_SIZE = 1024;
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket();
Scanner keyboard = new Scanner(System.in)) {
InetAddress serverAddress = InetAddress.getByName(SERVER_HOST);
byte[] buffer = new byte[BUFFER_SIZE];
System.out.println("서버에게 보낼 메세지를 입력하세요.");
System.out.println("quit를 입력하면 종료됩니다.");
while (true) {
String message = keyboard.nextLine();
if ("quit".equalsIgnoreCase(message)) {
break;
}
// 서버에게 보낼 패킷 생성
byte[] sendData = message.getBytes();
DatagramPacket sendPacket = new DatagramPacket(
sendData,
sendData.length,
serverAddress,
SERVER_PORT
);
socket.send(sendPacket);
// 서버 응답 받기
DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);
socket.receive(receivePacket);
String responseMessage =
new String(receivePacket.getData(), 0, receivePacket.getLength());
System.out.println("서버응답 : " + responseMessage);
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
public class UDPBroadcast {
public static void main(String[] args) {
try (DatagramSocket socket = new DatagramSocket()) {
socket.setBroadcast(true);
String message = "브로드캐스트 메시지입니다!";
byte[] buffer = message.getBytes();
InetAddress broadcastAddress = InetAddress.getByName("255.255.255.255");
DatagramPacket packet = new DatagramPacket(
buffer,
buffer.length,
broadcastAddress,
9876
);
socket.send(packet);
System.out.println("브로드캐스트 메시지 전송 완료");
} catch (Exception e) {
e.printStackTrace();
}
}
}
어제 배웠던 TCP와 비교하면 전체적인 형태는 비슷하지만, UDP는 연결 과정 없이 “패킷 단위”로 바로 송수신한다는 점이 핵심이다.
어제 오늘 배웠던 것을 이용해 간단한 채팅 프로그램을 구현했다.
기본 기능에 더해 귓속말 기능(/w) 과 도움말(/help) 을 추가했다.
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class ChatServer {
private static final int PORT = 12345;
private static final Set<ClientHandler> clients = ConcurrentHashMap.newKeySet();
// 귓속말을 위한 닉네임-핸들러 맵
private static final ConcurrentHashMap<String, ClientHandler> clientMap = new ConcurrentHashMap<>();
private static void broadcast(String message, ClientHandler sender) {
for (ClientHandler client : clients) {
if (client != sender) {
client.sendMessage(sender.nickname + " : " + message);
}
}
}
private static boolean whisper(String senderName, String receiverName, String message) {
ClientHandler receiver = clientMap.get(receiverName);
if (receiver != null) {
receiver.out.println("[귓속말 from : " + senderName + "] " + message);
return true;
}
return false;
}
public static void main(String[] args) {
System.out.println("채팅 서버 시작!!!");
try (ServerSocket serverSocket = new ServerSocket(PORT)) {
while (true) {
Socket socket = serverSocket.accept();
ClientHandler clientHandler = new ClientHandler(socket);
clients.add(clientHandler);
new Thread(clientHandler).start();
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
static class ClientHandler implements Runnable {
private final Socket socket;
private PrintWriter out;
private String nickname;
ClientHandler(Socket socket) {
this.socket = socket;
}
public void sendMessage(String message) {
if (out != null) {
out.println(message);
}
}
public void sendHelpMessage() {
out.println(
"========== 채팅사용법 ==========\n" +
"1. 일반 입력 후 엔터\n" +
"2. 귓속말 사용법 : /w 유저 내용\n" +
"3. 퇴장시 bye 입력 후 엔터\n" +
"4. 도움말 : /help 입력\n"
);
}
private void handleWhisper(String message) {
try {
String[] words = message.substring(3).split(" ", 2);
if (words.length < 2) {
out.println("[시스템] 사용법: /w 닉네임 메시지");
return;
}
String receiverName = words[0];
String content = words[1];
if (receiverName.equals(nickname)) {
out.println("[시스템] 자기 자신에게는 귓속말을 보낼 수 없습니다.");
return;
}
boolean success = whisper(nickname, receiverName, content);
if (success) {
out.println("[귓속말 to " + receiverName + "] " + content);
} else {
out.println("[시스템] " + receiverName + "님을 찾을 수 없습니다.");
}
} catch (Exception e) {
out.println("[시스템] 귓속말 전송 실패. 사용법: /w 닉네임 메시지");
}
}
@Override
public void run() {
try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
out = new PrintWriter(socket.getOutputStream(), true);
out.println("닉네임을 입력하세요.");
nickname = in.readLine();
if (nickname == null) return;
System.out.println(nickname + "님 입장");
clientMap.put(nickname, this);
broadcast(nickname + " 님 입장", this);
sendHelpMessage();
String message;
while ((message = in.readLine()) != null) {
if ("bye".equalsIgnoreCase(message)) {
break;
}
if (message.equalsIgnoreCase("/help")) {
sendHelpMessage();
continue;
}
if (message.startsWith("/w ")) {
handleWhisper(message);
continue;
}
broadcast(message, this);
}
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
clients.remove(this);
if (nickname != null) {
clientMap.remove(nickname);
broadcast(nickname + "님 퇴장", this);
System.out.println(nickname + "님 퇴장");
}
try {
socket.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
}
import javax.swing.*;
import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
public class ChatClientSwing extends JFrame {
private JTextArea textArea;
private JTextField inputField;
private JButton sendButton;
private PrintWriter out;
public ChatClientSwing(String host, int port) {
setTitle("채팅 클라이언트");
setSize(500, 400);
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLocationRelativeTo(null);
textArea = new JTextArea();
textArea.setEditable(false);
JScrollPane scrollPane = new JScrollPane(textArea);
inputField = new JTextField();
sendButton = new JButton("전송");
JPanel bottomPanel = new JPanel(new BorderLayout());
bottomPanel.add(inputField, BorderLayout.CENTER);
bottomPanel.add(sendButton, BorderLayout.EAST);
add(scrollPane, BorderLayout.CENTER);
add(bottomPanel, BorderLayout.SOUTH);
sendButton.addActionListener(e -> sendMessage());
inputField.addActionListener(e -> sendMessage());
new Thread(() -> connect(host, port)).start();
}
private void connect(String host, int port) {
try {
Socket socket = new Socket(host, port);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)
);
out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true
);
new Thread(() -> {
try {
String line;
while ((line = in.readLine()) != null) {
appendMessage(line);
}
} catch (IOException e) {
appendMessage("서버 연결이 종료되었습니다.");
}
}).start();
} catch (IOException e) {
appendMessage("서버 연결 실패: " + e.getMessage());
}
}
private void sendMessage() {
String msg = inputField.getText().trim();
if (!msg.isEmpty() && out != null) {
out.println(msg);
inputField.setText("");
}
}
private void appendMessage(String msg) {
SwingUtilities.invokeLater(() -> {
textArea.append(msg + "\n");
textArea.setCaretPosition(textArea.getDocument().getLength());
});
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
ChatClientSwing client = new ChatClientSwing("localhost", 12345);
client.setVisible(true);
});
}
}
오늘은 어제와 연결되는 내용을 보다 보니 기억도 잘 나고 재미있게 수업을 들었던 것 같다. 특히 마지막에 간단하게 채팅 프로그램을 구현하는 게 정말 재밌었다.
귓속말을 구현하는 게 좀 어려웠지만, 강사님께서 힌트를 주셔서 구글링과 AI에게 물어보며 다행히 완성시킬 수 있었다.
개인적으로 나는 AI에게 모든 걸 맡기고 “코드를 짜줘”라고 하는 것보다는, 내가 아는 선에서 계속 질문을 하며 완전히 이해하고 넘어가는 게 맞다고 생각한다.
강사님이 오늘 AI 관련으로 여러 이야기를 하셨는데, 문득 AI에게 모든 걸 맡기면 코드를 짜는 건 편해지겠지만 자신의 성장 또한 같이 멈출 것 같다는 생각이 들었다.