2025-10-02 네트워크프로그래밍 2

Ckd gus·2026년 2월 2일

UDP

UDP는 연결이 없는 프로토콜로, 데이터 전송 시 연결 설정 과정이 없다.
데이터는 데이터그램(datagram) 이라는 작은 패킷 단위로 전송된다.

자바에서는 java.net 패키지의 DatagramSocket, DatagramPacket 클래스를 사용하여 UDP 통신을 구현한다.


UDP Echo 프로그래밍

어제 설명한 Echo 프로그래밍을 UDP로 구현해보자.

UDPEchoServer

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());
        }
    }
}

UDPEchoClient

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());
        }
    }
}

UDP Broadcast

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는 연결 과정 없이 “패킷 단위”로 바로 송수신한다는 점이 핵심이다.


TCP 채팅 프로그램 구현

어제 오늘 배웠던 것을 이용해 간단한 채팅 프로그램을 구현했다.
기본 기능에 더해 귓속말 기능(/w)도움말(/help) 을 추가했다.

ChatServer

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());
                }
            }
        }
    }
}

ChatClientSwing

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에게 모든 걸 맡기면 코드를 짜는 건 편해지겠지만 자신의 성장 또한 같이 멈출 것 같다는 생각이 들었다.

profile
백엔드 공부중입니다.

0개의 댓글