네트워크
네트워크 (java / OSI 7계층)
1. 네트워크의 일반적인 정의
네트워크(Network)는 net이라는 그물과 work 일이라는 단어가 합쳐서 만들어진 단어로, IT 분야에서는 "두 개 이상의 노드(장치)가 링크(연결 매체)를 통해 서로 데이터를 공유하고 통신할 수 있도록 연결된 체계"라고 할 수 있습니다.
컴퓨터 네트워크 관점에서는 단순히 케이블을 연결한 물리적 상태만을 뜻하지 않으며, 송수신 장치가 서로 신호를 이해하고 데이터를 오류 없이 안전하게 주고받기 위해 합의된 규칙(프로토콜)과 소프트웨어 인프라까지를 모두 포함하는 광범위한 개념으로 확장할 수 잇습니다.
2. OSI 7계층과 Java 애플리케이션의 관계
1) OSI 7계층
OSI 7계층은 컴퓨터 시스템 간의 원활한 통신을 위해 국제표준화기구(ISO)에서 제정한 네트워크 표준 모델입니다. 복잡한 네트워크 통신 단계를 7개로 쪼개어 표준화한 이유는 "역할 분담과 유지보수의 용이성" 때문입니다. 특정 계층의 문제가 발생하면 다른 계층의 하드웨어나 소프트웨어를 건드리지 않고 해당 계층만 수정하여 문제를 해결할 수 있습니다.
2) Java(Application) 단과 연관이 깊은 계층
Java 기반 백엔드 애플리케이션을 개발할 때 우리는 주로 상위 계층인 L7(애플리케이션 계층), L6(표현 계층), 그리고 L4(전송 계층)와 깊은 연관을 맺습니다. 하위 계층(L1~L3)은 주로 운영체제(OS Kernel)와 네트워크 장비(라우터, 스위치 등)가 알아서 처리하므로, Java 코드 레벨에서는 추상화된 소켓 인터페이스를 통해 간접적으로 제어하게 됩니다.
- L7 (애플리케이션 계층) HTTP, gRPC, WebSocket 등 사용자가 직접 사용하는 영역입니다. Java에서는 Spring WebClient, RestTemplate, Controller 등을 구성하여 API 통신을 수행합니다.
- L6 (표현 계층) 데이터 포맷팅 및 암호화 영역입니다. Java 객체를 네트워크로 전송하기 위해 JSON, XML 또는 바이너리 포맷으로 직렬화(Serialization) 및 역직렬화하는 과정이 이에 해당합니다.
- L4 (전송 계층)
데이터 전송 신뢰성을 보장하는 영역(TCP/UDP)입니다. Java의 소켓 프로그래밍(
Socket,ServerSocket)이 이 계층에 직접 대응하며, 포트(Port) 번호를 할당하여 프로세스 간 통신 통로를 확보합니다.
3. 계층별 Java 구현 예시 코드
각 연관 계층이 Java 코드에서 실제로 어떻게 사용되는지 예시와 함께 설명해보겠습니다.
L4 (전송 계층) - TCP 소켓 통신 예시
OS 커널의 TCP/IP 프로토콜 스택과 직접 상호작용하기 위해 Java가 제공하는 로우 레벨 API를 사용한 예시입니다. 포트 번호를 지정하여 수신 대기 상태를 만들고 클라이언트 연결을 허용합니다.
package transport;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpServerSocketExample {
public static void main(String[] args) {
int port = 8080; // L4 계층의 포트 번호 지정
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("L4 Server가 포트 " + port + "에서 클라이언트 연결을 대기 중입니다.");
while (true) {
// 클라이언트와의 3-Way Handshake가 백그라운드(OS)에서 완료되면 연결 소켓을 반환받음
try (Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
System.out.println("클라이언트가 연결되었습니다: " + clientSocket.getRemoteSocketAddress());
// 스트림을 통한 데이터 읽기
String clientMessage = in.readLine();
System.out.println("수신된 메시지: " + clientMessage);
// 클라이언트에 응답 전송
out.println("서버에서 보내는 응답: " + clientMessage);
} catch (Exception e) {
System.err.println("클라이언트 처리 중 오류 발생: " + e.getMessage());
}
}
} catch (Exception e) {
System.err.println("서버 소켓 오픈 실패: " + e.getMessage());
}
}
}L6 (표현 계층) - 데이터 직렬화 및 변환 예시
Java 내부에서 다루는 객체(메모리 상의 인스턴스)를 네트워크 통신에 적합한 표준 규격(JSON)으로 변환(직렬화)하는 표현 계층의 역할 예시입니다. 실무에서는 json 형태가 제일 많이 사용되어Jackson 라이브러리의 사용방법을 예시로 들었습니다.
package presentation;
import com.fasterxml.jackson.databind.ObjectMapper;
public class SerializationExample {
// 전송할 도메인 객체 정의
public static class UserDto {
private String name;
private int age;
public UserDto() {} // 기본 생성자 필수
public UserDto(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
}
public static void main(String[] args) {
ObjectMapper objectMapper = new ObjectMapper();
try {
UserDto user = new UserDto("홍길동", 30);
// 1. Serialization (Java 객체 -> JSON 문자열) : 네트워크 송신을 위한 준비
String jsonString = objectMapper.writeValueAsString(user);
System.out.println("L6 직렬화 결과 (네트워크로 전송할 데이터 포맷):");
System.out.println(jsonString);
// 2. Deserialization (JSON 문자열 -> Java 객체) : 수신한 데이터 해석
UserDto deserializedUser = objectMapper.readValue(jsonString, UserDto.class);
System.out.println("\nL6 역직렬화 결과 (애플리케이션이 이해할 수 있는 객체 포맷):");
System.out.println("이름: " + deserializedUser.getName() + ", 나이: " + deserializedUser.getAge());
} catch (Exception e) {
System.err.println("직렬화/역직렬화 실패: " + e.getMessage());
}
}
}L7 (애플리케이션 계층) - HTTP 통신 예시
L4 소켓 제어와 L6 데이터 포맷 지정을 상위 수준에서 모두 추상화하여, 개발자가 순수하게 비즈니스 프로토콜(HTTP) 및 자원에 집중할 수 있게 해주는 계층입니다. Java 11부터 내장된 HttpClient를 사용하여 REST API를 호출하는 예시입니다.
package application;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.duration.Duration;
public class HttpClientExample {
public static void main(String[] args) {
// HTTP 통신을 수행할 클라이언트 인스턴스 생성
HttpClient httpClient = HttpClient.newBuilder()
.connectTimeout(Duration.ofSeconds(5))
.build();
// HTTP GET 요청 명세 작성 (URI, Method 등 애플리케이션 규약 정의)
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("[https://jsonplaceholder.typicode.com/posts/1](https://jsonplaceholder.typicode.com/posts/1)"))
.header("Accept", "application/json")
.GET()
.build();
try {
System.out.println("L7 HTTP GET 요청을 전송합니다.");
// 내부적으로 L4 소켓이 생성되고, L6 수준의 헤더/바디 포맷팅을 거쳐 비동기/동기 통신이 수행됨
HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println("응답 상태 코드: " + response.statusCode());
System.out.println("응답 바디 데이터:");
System.out.println(response.body());
} catch (Exception e) {
System.err.println("HTTP 통신 오류 발생: " + e.getMessage());
}
}
}4. 백엔드 개발자 관점에서 바라보는 네트워크 기능 활용
백엔드 애플리케이션을 지탱하는 웹 서버(톰캣, 네티 등)와 데이터베이스, 마이크로서비스 간의 통신은 모두 네트워크 위에서 작동합니다. 실무에서 신뢰성 있고 성능이 뛰어난 아키텍처를 설계하기 위해 백엔드 개발자는 다음과 같은 관점을 견지해야 합니다.
1) 추상화 장벽 허물기: 고수준 API 이면의 저수준 동작 이해
실무에서는 스프링 프레임워크가 제공하는 선언적 기능(예: @GetMapping, OpenFeign 등)을 주로 사용하기 때문에 자칫 네트워크 동작을 과하게 단순화하여 생각하기 쉽습니다. 하지만 대규모 트래픽 환경이나 장애 상황을 마주했을 때, 그 밑바닥에 흐르는 저수준 지식의 유무가 개발자의 역량을 가릅니다.
- TCP 3-Way Handshake 비용
- 새로운 연결을 맺을 때 발생하는 지연 시간을 방지하기 위해 애플리케이션 단에서 Connection Pool(커넥션 풀)의 개수를 조절하고 HTTP Keep-Alive 설정을 튜닝하는 일은 L4 계층의 원리를 명확히 알아야 최적화할 수 있습니다.
- 네트워크 병목 분석 시스템 장애 시 Netstat 명령어 등을 활용하여 포트 고갈 현상(TIME_WAIT 소켓 누적)이나 패킷 손실률을 진단하는 등 시스템 인프라 계층까지 아우르는 문제 해결 능력이 필수적입니다.
2) 서비스 요구사항에 따른 전송 방식 선택
모든 통신에 HTTP(REST API)가 만능은 아닙니다.
- 빠른 응답 속도와 저용량 데이터 송수신이 핵심인 실시간 양방향 서비스(채팅, 멀티플레이 게임, 실시간 주식 차트 등) L4 TCP 소켓 기반의 커스텀 프로토콜이나 웹소켓(WebSocket)
- 대규모 분산 서버(MSA) 간의 고속 통신 L6 변환 성능이 우수한 바이너리 직렬화 프로토콜인 gRPC(HTTP/2 기반) 또는 메시지 큐(Kafka, RabbitMQ)를 활용하여 시스템 간 결합도를 낮추고 처리량을 늘려야 합니다.
3) 비동기 및 논블로킹 I/O 모델의 이해
- 전통적인 1-Thread-Per-Connection (Blocking I/O) 클라이언트 수에 비례하여 스레드가 증가하므로 과도한 메모리 사용과 컨텍스트 스위칭 비용이 발생합니다.
- Non-Blocking I/O (NIO / Netty) 적은 스레드로 다수의 커넥션을 효율적으로 처리하는 반응형(Reactive) 프로그래밍 기법을 이해하고 도입할 수 있어야 합니다. Spring WebFlux나 API 게이트웨이 등의 아키텍처를 설계할 때 핵심 배경 지식으로 작용합니다.
- 실무에서는 WebFlux의 코드 개념을 MVC 패턴에도 활용해서 CompletableFuture 나 @Async를 활용해서 구현하기도 합니다.
댓글
GitHub 계정으로 의견이나 질문을 남길 수 있습니다.