본문 바로가기

자바

[JAVA] 소켓 통신의 기본 개념과 구조

728x90

Java에서는 네트워크 통신이 가능하도록 java.net 패키지를 통해 TCP/IP 기반 소켓 통신 기능을 제공한다.

이번 글에서 TCP 기반의 소켓과 서버소켓을 사용한 1:1 구조를 알아보도록 하겠다.

소켓 통신이란?

소켓 통신에 대해 알아보기 전에 소켓에 대해 설명하자면 아래와 같다.

네트워크 상의 두 프로그램이 데이터를 주고받기 위해 만들어지는 통신의 연결점이다.

소켓은 네트워크 통신의 끝단에서 데이터를 송수신하는 역할을 하며, 일반적으로 Socket, ServerSocket두 객체가 주로 사용된다.

  • Socket: 클라이언트 측에서 서버에 연결할 때 사용
  • ServerSocket: 서버 측에서 클라이언트의 요청을 대기하고 수락할 때 사용

소켓통신은 이런 소켓을 통해 서버 - 클라이언트간 데이터를 주고받는 양방향 연결 통신을 말한다.

보통 지속적으로 연결을 유지하며 실시간으로 데이터를 주고받을 때 소켓통신을 사용한다.

소켓간 통신을 위해서 클라이언트, 서버를 구분하기 위해 IP주소와 통신에 사용되는 프로그램을 식별하기 위한 포트번호가 사용된다.

자바의 TCP 통신 구조 흐름

위 그림은 자바에서의 클라이언트 - 서버 간 TCP 기반 소켓 통신 흐름을 나타낸 것이다.

클라이언트가 서버에 연결을 요청하고, 서버가 요청을 수락하면 통신선로가 고정되어 모든 데이터는 고정된 통신선로를 통해 순차적으로 전달되게 된다.

이런 TCP서버의 역할은 클라이언트가 연결요청을 하면 연결을 수락하고, 연결된 클라이언트와 통신한다는 것이다.

Java에서는 이 역할을 대신 해주는 클래스인 소켓이라는 클래스가 있다.

자바의 UDP 통신 구조 흐름

TCP는 연결지향형 통신이었다면, UDP는 비연결자향형 통신 방식이다.

UDP는 DatagramSocketDatagramPacket을 사용하여 데이터를 주고받는다.

  • DatagramSocket: 송/수신 역할을 담당하는 소켓
  • DatagramPacket: 전송될 데이터 + 목적지 주소(IP, Port)를 포함한 패킷

UDP는 클라이언트가 데이터를 보내면 수신 가능한 주소로 도착하는 구조를 띄기 때문에 서버가 항상 열려 있어야 할 필요가 없다.

TCP는 연결을 승인받고 연결이 상시 되어 있다면 UDP는 필요할때마다 주소로 보내면 되기 때문!

예시 코드

이제 이론을 이해했으면 Java에서 어떻게 사용하는지 알아보겠다.

TCP 통신의 경우

  • 서버 코드
import java.io.*;
import java.net.*;

public class TCPServer {
    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8080);
        System.out.println("[서버] 연결 대기 중...");

        Socket clientSocket = serverSocket.accept();
        System.out.println("[서버] 연결됨: " + clientSocket.getInetAddress());

        BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

        String message = in.readLine(); // 클라이언트 메시지 읽기
        System.out.println("[서버] 받은 메시지: " + message);

        out.println("서버 응답: " + message);

        clientSocket.close();
        serverSocket.close();
    }
}
  • 클라이언트 코드
import java.io.*;
import java.net.*;

public class TCPClient {
    public static void main(String[] args) throws IOException {
        Socket socket = new Socket("localhost", 8080);
        System.out.println("[클라이언트] 서버에 연결됨.");

        PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        String myMessage = "안녕하세요 서버님!";
        System.out.println("[클라이언트] 전송: " + myMessage);
        out.println(myMessage);

        String response = in.readLine();
        System.out.println("[클라이언트] 서버 응답: " + response);

        socket.close();
    }
}

서버 코드 실행 -> 클라이언트 코드 실행

서버 콘솔창

[서버] 연결 대기 중...
[서버] 연결됨: /127.0.0.1
[서버] 받은 메시지: 안녕하세요 서버님!

클라이언트 콘솔창

[클라이언트] 서버에 연결됨.
[클라이언트] 전송: 안녕하세요 서버님!
[클라이언트] 서버 응답: 서버 응답: 안녕하세요 서버님!

TCP는 서버가 항상 열려 있어야 하는가?

ServerSocket serverSocket = new ServerSocket(8080);

위 코드를 보면 accept()를 통해 클라이언트에서 요청을 승인받을 때까지 블로킹된다는것을 알 수 있다.

즉, 서버가 열려 있지 않으면 클라이언트는 연결이 되지 않는다는 뜻이다.

때문에, TCP의 경우 반드시 서버가 먼저 열려(실행) 있어야 하고, 이후에 클라이언트를 실행해야 한다.

UDP 통신의 경우

  • 서버 코드
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPServer {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket(9000)) {
            System.out.println("[서버] 수신 대기 중...");

            byte[] buffer = new byte[1024];

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

                String response = "응답: " + message;
                byte[] sendData = response.getBytes();

                DatagramPacket responsePacket = new DatagramPacket(
                        sendData,
                        sendData.length,
                        packet.getAddress(),
                        packet.getPort()
                );

                socket.send(responsePacket);
                System.out.println("[서버] 응답 전송 완료");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 클라이언트 코드
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class UDPClient {
    public static void main(String[] args) {
        try (DatagramSocket socket = new DatagramSocket()) {
            Scanner sc = new Scanner(System.in);

            InetAddress serverAddress = InetAddress.getByName("localhost");
            int serverPort = 9000;

            while (true) {
                System.out.print("보낼 메시지 입력 (exit 입력 시 종료): ");
                String message = sc.nextLine();

                if (message.equalsIgnoreCase("exit")) break;

                byte[] sendData = message.getBytes();

                DatagramPacket sendPacket = new DatagramPacket(
                        sendData, sendData.length, serverAddress, serverPort);
                socket.send(sendPacket);
                System.out.println("[클라이언트] 메시지 전송 완료");

                byte[] buffer = new byte[1024];
                DatagramPacket responsePacket = new DatagramPacket(buffer, buffer.length);
                socket.receive(responsePacket);

                String response = new String(responsePacket.getData(), 0, responsePacket.getLength());
                System.out.println("[클라이언트] 받은 응답: " + response);
            }

            System.out.println("종료합니다.");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

서버 콘솔창

[서버] 수신 대기 중...
[서버] 받은 메시지: test
[서버] 응답 전송 완료

클라이언트 콘솔창

보낼 메시지 입력 (exit 입력 시 종료): test
[클라이언트] 메시지 전송 완료
[클라이언트] 받은 응답: 응답: test
보낼 메시지 입력 (exit 입력 시 종료): 

UDP의 경우 서버가 꺼져있더라도 클라이언트를 실행해도 에러가 발생하지 않는다.

하지만, 서버가 꺼져있기 때문에 클라이언트는 응답을 받지 못한다.

728x90