1. 네트워크 통신에서 IP 주소의 역할

네트워크 통신에서 서버와 통신하려면 IP 주소가 필요하다. 도메인(호스트 이름)만으로는 통신할 수 없으며, 도메인을 실제 IP 주소로 변환해야 한다.

자바에서는 InetAddress 클래스를 사용하여 도메인 이름을 IP 주소로 변환할 수 있다.

import java.net.*;

public class HostToIP {
    public static void main(String[] args) {
        try {
            InetAddress address = InetAddress.getByName("www.google.com");
            System.out.println("IP Address: " + address.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
    }
}

2. 서버와 클라이언트의 데이터 송수신

InputStream과 OutputStream

소켓 통신에서 데이터를 주고받을 때 InputStreamOutputStream을 사용한다.

  • InputStream: 서버 -> 클라이언트로 데이터를 받을 때 사용
  • OutputStream: 클라이언트 -> 서버로 데이터를 보낼 때 사용

하지만, 기본 스트림을 그대로 사용하면 byte 단위로 데이터를 변환해야 하는 불편함이 있다. 이를 해결하기 위해 보조 스트림을 사용한다.

DataInputStream과 DataOutputStream

DataInputStreamDataOutputStream을 사용하면 자바 기본 타입(int, float, String 등) 을 편리하게 송수신할 수 있다.

import java.io.*;
import java.net.*;

public class Client {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 12345);
             DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
             DataInputStream dis = new DataInputStream(socket.getInputStream())) {
            
            dos.writeUTF("Hello Server"); // 문자열 전송
            System.out.println("Server response: " + dis.readUTF()); // 응답 수신
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. 서버 소켓과 클라이언트 소켓

서버 소켓의 역할

서버는 특정 포트를 열어두어야 클라이언트가 접속할 수 있다.

  • ServerSocket: 클라이언트의 요청을 받아들여 연결만 담당하는 특별한 소켓
  • Socket: 서버와 클라이언트가 데이터를 주고받을 수 있는 실제 통신 소켓

TCP 3-Way Handshake 과정

  1. 클라이언트가 서버의 포트(예: 12345)로 연결 요청 (SYN)
  2. 서버가 요청을 받아 승인 (SYN-ACK)
  3. 클라이언트가 승인 확인 (ACK)
  4. 연결 완료 → Backlog Queue에 TCP 연결 정보 저장

서버의 동작 과정

  1. ServerSocket을 생성하고 특정 포트(예: 12345)에서 대기
  2. accept()를 호출하여 클라이언트 요청을 대기
  3. 클라이언트가 연결하면 새로운 Socket 객체를 생성하여 통신
import java.io.*;
import java.net.*;

public class Server {
    public static void main(String[] args) {
        try (ServerSocket serverSocket = new ServerSocket(12345)) {
            System.out.println("서버 대기 중...");
            
            while (true) {
                Socket socket = serverSocket.accept(); // 클라이언트 연결 대기 (블로킹)
                System.out.println("클라이언트 연결 완료!");
                
                new Thread(new ClientHandler(socket)).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

class ClientHandler implements Runnable {
    private Socket socket;

    public ClientHandler(Socket socket) {
        this.socket = socket;
    }
    
    @Override
    public void run() {
        try (DataInputStream dis = new DataInputStream(socket.getInputStream());
             DataOutputStream dos = new DataOutputStream(socket.getOutputStream())) {
            
            String message = dis.readUTF(); // 클라이언트 메시지 수신
            System.out.println("Received: " + message);
            dos.writeUTF("Hello Client!"); // 응답 전송
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 블로킹과 멀티스레드 처리

accept()의 블로킹 문제

  • accept() 메서드는 클라이언트 연결 요청이 없으면 무한 대기 상태(블로킹)가 된다.
  • readUTF() 같은 입력 메서드도 데이터를 받을 때까지 대기 상태가 된다.
  • 따라서, 서버는 멀티스레드 방식으로 각각의 클라이언트 요청을 별도의 스레드에서 처리해야 한다.

멀티스레드 방식의 서버

각 클라이언트가 연결될 때마다 새로운 Thread를 생성하여 별도로 처리하면, 여러 클라이언트를 동시에 처리할 수 있다.

  • 메인 스레드: ServerSocket을 관리하고 새로운 연결이 올 때마다 Session 스레드를 생성
  • Session 스레드: 특정 클라이언트와 1:1로 데이터를 주고받는 역할

5. 정리

서버 소켓과 클라이언트 소켓

  • ServerSocket연결만 담당하는 특별한 소켓
  • 실제 데이터 송수신은 Socket 객체를 통해 이루어짐

TCP 연결 과정

  • 클라이언트가 connect() 요청하면 TCP 3-Way HandshakeBacklog Queue에 저장
  • accept() 호출 시 Socket 객체 생성 후 클라이언트와 통신 가능

스트림을 이용한 데이터 송수신

  • DataInputStream / DataOutputStream을 활용하면 편리한 데이터 송수신 가능

멀티스레드 처리 필요

  • accept()readUTF() 같은 블로킹 메서드는 별도 스레드에서 실행해야 함

이제 자바 소켓 프로그래밍에 대한 기본 개념을 정리했으니, 실전 프로젝트에서 활용해 보자! 🚀

profile
배움을 추구하는 개발자

0개의 댓글