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

Ckd gus·2026년 2월 2일

네트워크 기본 개념

IP 주소란?

IP 주소는 컴퓨터, 스마트폰, 서버 등을 식별하기 위해 부여되는 고유한 번호이다.
IP는 IPv4, IPv6로 나뉘며, IPv4 주소 부족 문제로 인해 IPv6와 함께 사용한다.


TCP / UDP

TCP

TCP는 인터넷 핵심 프로토콜 중 하나로 신뢰성 있는 연결 지향형 통신을 제공한다.

UDP

UDP는 TCP와 달리 비연결형 전송 프로토콜이다.
TCP가 안정성을 위해 사용하는 프로토콜이라면, UDP는 속도와 효율성을 우선한다.


3-Way Handshake

TCP 연결을 하기 위해 사용하는 절차이다.

클라이언트가 서버에 SYN 플래그를 보내면
서버에서는 SYN + ACK 플래그를 보낸다.
그 후 마지막으로 클라이언트에서 ACK를 서버로 전해주면 연결이 성립되어 데이터를 전송할 수 있다.

  • SYN: 연결 요청 플래그
  • ACK: 응답/승인 플래그


TCP 프로그래밍 기본 절차

  • 서버 생성: ServerSocket을 생성하고 특정 포트에서 클라이언트 연결 대기
  • 클라이언트 연결 요청: 클라이언트는 Socket을 사용하여 서버의 특정 포트로 연결 요청
  • 연결 수락 및 데이터 교환: 서버는 요청을 수락하고 Socket을 통해 데이터를 송수신
  • 연결 종료: 데이터 전송이 끝나면 클라이언트와 서버 모두 연결 종료

자바 네트워크 프로그래밍 기초

InetAddress

InetAddress는 자바에서 IP를 표현하고 관리하기 위해 사용되는 클래스이며, IPv4와 IPv6를 추상화하여 제공한다.

주소 조회 메서드

  • getByName(String host): 주어진 호스트 이름 또는 IP 주소에 대한 InetAddress 객체 반환
  • getAllByName(String host): 주어진 호스트 이름에 대한 모든 IP 주소를 배열로 반환
  • getLocalHost(): 로컬 컴퓨터의 IP 주소를 포함하는 InetAddress 객체 반환

주소 정보 검색 메서드

  • getHostName(): 호스트 이름 반환
  • getHostAddress(): IP 주소를 문자열 형태로 반환
  • getAddress(): IP 주소를 바이트 배열로 반환
  • isReachable(int timeout): 호스트가 주어진 시간 내에 도달 가능한지 검사

InetAddress 예제

import java.net.InetAddress;

public class InetAddressExample {
    public static void main(String[] args) throws Exception {
        InetAddress address = InetAddress.getByName("www.google.com");
        System.out.println("ip : " + address.getHostAddress());
        System.out.println("hostname : " + address.getHostName());

        boolean reachable = address.isReachable(3000);
        System.out.println("도달가능 : " + reachable);

        InetAddress[] allByName = InetAddress.getAllByName("www.google.com");
        for (InetAddress inetAddress : allByName) {
            System.out.println("ip : " + inetAddress.getHostAddress());
        }

        InetAddress localhost = InetAddress.getLocalHost();
        System.out.println("localhost : " + localhost.getHostAddress());
        System.out.println("hostname : " + localhost.getHostName());

        byte[] ip = localhost.getAddress();
        System.out.print("Byte array format: ");
        for (int i = 0; i < ip.length; i++) {
            System.out.print((ip[i] & 0xFF));
            if (i != ip.length - 1) {
                System.out.print(".");
            }
        }
        System.out.println();
    }
}

TCP ECHO 서버 구현

Echo 프로그래밍은 클라이언트가 서버에 메시지를 보내면, 서버가 동일한 메시지를 클라이언트에게 되돌려주는 간단한 네트워크 애플리케이션이다.


에코 서버

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleEchoServer {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
            System.out.println("에코 서버가 12345번 포트에서 시작되었습니다.");

            while (true) {
                try (Socket socket = serverSocket.accept();
                     BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                     PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

                    System.out.println("클라이언트 연결 :: " + socket.getRemoteSocketAddress());

                    String inputLine;
                    while ((inputLine = in.readLine()) != null) {
                        System.out.println("클라이언트로부터 받은 메세지 : " + inputLine);
                        out.println("Echo : " + inputLine);

                        if ("bye".equalsIgnoreCase(inputLine)) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        } catch (Exception e) {
            System.out.println("서버시작 실패 : " + e.getMessage());
        }
    }
}

에코 클라이언트

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class SimpleEchoClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 12345);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             Scanner keyboard = new Scanner(System.in)) {

            System.out.println("서버 접속 성공");
            System.out.println("bye를 입력하면 종료됩니다.");

            while (true) {
                String message = keyboard.nextLine();
                out.println(message);

                String response = in.readLine();

                if (response == null) {
                    System.out.println("서버와의 연결이 끊어졌습니다.");
                    break;
                }

                System.out.println(response);

                if ("bye".equalsIgnoreCase(message)) {
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

클라이언트를 여러 개 만들어서 서버에 요청을 보내면 첫 번째로 연결된 클라이언트 말고는 응답이 오지 않는 것을 확인할 수 있다.
채팅 프로그램을 만든다고 가정했을 때, 다른 사용자가 채팅을 입력할 때에도 사용자 또한 채팅을 보내고 받을 수 있어야 한다. 이 문제를 해결하기 위해 멀티스레드 서버를 구현해야 한다.


멀티스레드 에코 서버

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class MultiThreadEchoServer {
    private static final int PORT = 12345;

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(5);

        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            System.out.println("에코 서버 시작");

            while (true) {
                Socket socket = serverSocket.accept();
                executorService.submit(new ClientHandler(socket));
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            executorService.shutdown();
        }
    }

    static class ClientHandler implements Runnable {
        private final Socket socket;

        ClientHandler(Socket socket) {
            this.socket = socket;
        }

        @Override
        public void run() {
            try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                 PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {

                SocketAddress clientAddress = socket.getRemoteSocketAddress();
                System.out.println(clientAddress + " 사용자 접속함");

                String inputLine;
                while ((inputLine = in.readLine()) != null) {
                    System.out.println(clientAddress + "로부터 받은 메시지 : " + inputLine);
                    out.println("Echo : " + inputLine);

                    if ("bye".equalsIgnoreCase(inputLine)) {
                        break;
                    }
                }

                System.out.println(clientAddress + " 연결종료");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

채팅 서버와 클라이언트 구현

채팅 서버

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 void broadcast(String message, ClientHandler sender) {
        for (ClientHandler client : clients) {
            if (client != sender) {
                client.sendMessage(message);
            }
        }
    }

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

        @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 + "님 입장");
                broadcast(nickname + " 님 입장", this);

                String message;
                while ((message = in.readLine()) != null) {
                    System.out.println(nickname + " : " + message);

                    if ("bye".equalsIgnoreCase(message)) break;
                    broadcast(nickname + " : " + message, this);
                }
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                clients.remove(this);

                if (nickname != null) {
                    System.out.println(nickname + "님 퇴장");
                    broadcast(nickname + "님 퇴장", this);
                }

                try {
                    socket.close();
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            }
        }
    }
}

채팅 클라이언트

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class ChatClient {
    private static final String SERVER_HOST = "localhost";
    private static final int SERVER_PORT = 12345;

    public static void main(String[] args) {
        try (Socket socket = new Socket(SERVER_HOST, SERVER_PORT);
             BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
             PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
             Scanner keyboard = new Scanner(System.in)) {

            Thread readerThread = new Thread(() -> {
                try {
                    String serverMsg;
                    while ((serverMsg = in.readLine()) != null) {
                        System.out.println(serverMsg);
                    }
                } catch (Exception e) {
                    System.out.println(e.getMessage());
                }
            });
            readerThread.start();

            String userInput;
            while (!(userInput = keyboard.nextLine()).equalsIgnoreCase("bye")) {
                out.println(userInput);
            }

            out.println("bye");
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
    }
}

느낀점

오늘은 네트워크 프로그래밍에 대해 배웠는데 어려운 부분은 자바에서 구현을 해둬서 그런지 신기하고 재미있었다.
AWS를 이용해 채팅 서버를 잠깐 열어주신 것도 작성한 코드로 결과물을 바로 볼 수 있어서 더 신기했다.

내일은 UDP 통신에 관하여 배우고, 추석 연휴가 있는데 연휴 기간 동안 자바에 관한 것들을 복습하고 채팅 서버도 다시 만들어 봐야겠다.

profile
백엔드 공부중입니다.

0개의 댓글