Application: 애플리케이션 계층의 프로토콜과 메시지 교환
애플리케이션 계층(Application Layer)은 사용자 프로그램이 네트워크를 통해 의미 있는 데이터를 교환하도록 규칙을 정의한다. HTTP, DNS, SMTP 같은 프로토콜은 메시지 형식과 요청·응답 절차를 정하고, 하위 계층의 전송 기능을 이용한다. 애플리케이션 성능은 왕복 시간, 연결 재사용, 메시지 크기, 서버 처리 시간의 영향을 받는다.
By 송민성
1. 개념
애플리케이션 계층(Application Layer)은 네트워크 애플리케이션이 데이터를 주고받는 가장 위쪽 계층이다. 운영체제의 소켓(Socket) 인터페이스를 통해 전송 계층(Transport Layer)을 사용하며, 데이터의 의미와 교환 규칙을 정의한다.
대표적인 애플리케이션 계층 프로토콜(Application Layer Protocol)은 다음과 같다.
| 프로토콜 | 역할 | 일반적인 하위 전송 방식 |
|---|---|---|
| HTTP | 웹 문서와 API 데이터 교환 | TCP, QUIC |
| DNS | 도메인 이름을 IP 주소로 변환 | UDP, TCP |
| SMTP | 이메일 전송 | TCP |
| SSH | 원격 접속과 명령 실행 | TCP |
| WebSocket | 하나의 연결에서 양방향 메시지 교환 | TCP |
애플리케이션 프로토콜은 다음 요소를 정의한다.
- 메시지 형식: 헤더, 본문, 인코딩
- 메시지 의미: 조회, 생성, 오류 같은 동작
- 교환 순서: 누가 먼저 보내고 어떤 응답을 기다리는지
- 오류 처리: 상태 코드, 재시도 가능 여부
- 상태 관리: 요청 간 상태를 유지하는 방식
OSI 7계층 모델에서는 애플리케이션 계층이 7계층에 해당한다. 인터넷 프로토콜 스위트(Internet Protocol Suite)에서는 OSI의 애플리케이션·표현·세션 계층 역할을 애플리케이션 계층 하나로 묶어 설명한다.
2. 왜 사용하는가
전송 계층은 바이트를 전달하지만 그 바이트의 의미는 알지 못한다. TCP는 데이터가 순서대로 도착하도록 처리할 뿐, 받은 데이터가 로그인 요청인지 이미지인지 판단하지 않는다.
애플리케이션 계층은 바이트에 구조와 의미를 부여한다.
POST /users HTTP/1.1
Content-Type: application/json
{"name":"민수"}이 메시지에는 다음 규칙이 포함된다.
POST는 서버 자원을 생성하거나 처리하는 메서드(Method)다./users는 요청 대상 자원(Resource)을 나타낸다.Content-Type은 본문의 표현 형식(Media Type)을 나타낸다.- JSON 본문은 애플리케이션이 처리할 실제 데이터다.
클라이언트와 서버가 같은 규칙을 따르면 서로 다른 언어와 운영체제로 작성되어도 통신할 수 있다.
3. 동작 원리
3.1 클라이언트와 서버
클라이언트(Client)는 통신을 시작하고, 서버(Server)는 특정 주소와 포트에서 요청을 기다린다.
클라이언트
│
│ 1. DNS 조회
▼
서버 IP 주소 확인
│
│ 2. 전송 계층 연결 또는 세션 준비
▼
서버
│
│ 3. 애플리케이션 요청
▼
요청 해석과 처리
│
│ 4. 애플리케이션 응답
▼
클라이언트웹 브라우저가 https://example.com/users에 접근하면 대략 다음 과정이 진행된다.
- DNS로
example.com의 IP 주소를 조회한다. - 서버와 TCP 연결 및 TLS 연결을 맺거나 QUIC 연결을 맺는다.
- HTTP 요청을 보낸다.
- 서버가 요청 경로, 헤더, 본문을 해석한다.
- 서버가 상태 코드, 헤더, 본문으로 구성된 응답을 보낸다.
- 클라이언트가 응답을 해석해 화면이나 프로그램 상태에 반영한다.
3.2 프로토콜과 API
프로토콜(Protocol)은 통신 규칙 전체를 뜻한다. 애플리케이션 프로그래밍 인터페이스(API)는 프로그램이 기능이나 데이터에 접근하는 접점을 뜻한다.
REST API는 보통 HTTP를 전송 규칙으로 사용한다.
HTTP 프로토콜
├── 메서드: GET, POST, PUT, PATCH, DELETE
├── 상태 코드: 200, 201, 400, 404, 500
├── 헤더: Content-Type, Authorization
└── 본문: JSON, HTML, 파일 데이터HTTP는 메시지를 전달한다. /users/42가 어떤 사용자를 뜻하고 어떤 권한이 필요한지는 애플리케이션이 정한다.
3.3 소켓과 포트
소켓(Socket)은 애플리케이션이 운영체제의 네트워크 기능을 사용하는 인터페이스다. 서버는 일반적으로 특정 포트(Port)에 소켓을 바인딩(Binding)하고 연결이나 데이터그램을 기다린다.
하나의 통신 흐름은 다음 값으로 구분할 수 있다.
전송 프로토콜, 출발지 IP, 출발지 포트, 목적지 IP, 목적지 포트서버가 같은 포트로 여러 클라이언트를 처리할 수 있는 이유는 각 연결의 출발지 주소와 포트가 다르기 때문이다.
포트 번호는 애플리케이션을 식별하는 관례를 제공하지만 프로토콜을 강제하지는 않는다. 예를 들어 80번 포트는 일반적으로 HTTP에 사용되지만 다른 포트에서도 HTTP 서버를 실행할 수 있다.
3.4 상태 유지 방식
HTTP 요청 자체는 독립적으로 처리할 수 있도록 설계된다. 로그인 상태처럼 여러 요청에 걸친 정보가 필요하면 별도 상태 관리가 필요하다.
대표적인 방법은 다음과 같다.
- 쿠키(Cookie)에 세션 식별자를 저장한다.
- 서버의 세션 저장소(Session Store)에 상태를 보관한다.
- 토큰(Token)에 인증 정보를 담아 요청마다 전송한다.
- 데이터베이스에 지속 상태를 저장한다.
연결 상태와 애플리케이션 상태는 다르다. TCP 연결을 유지해도 로그인 상태가 자동으로 생기지 않는다. 반대로 TCP 연결이 끊겨도 쿠키나 토큰이 남아 있으면 새 연결에서 로그인 상태를 복원할 수 있다.
4. 코드 예제
다음 코드는 Node.js 표준 라이브러리로 HTTP 서버를 만들고 JSON 요청과 응답을 처리한다.
import { createServer } from "node:http";
type User = {
id: number;
name: string;
};
const users: User[] = [
{ id: 1, name: "민수" },
{ id: 2, name: "지수" },
];
const server = createServer((request, response) => {
const url = new URL(request.url ?? "/", "http://localhost");
if (request.method === "GET" && url.pathname === "/users") {
const body = JSON.stringify(users);
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(body),
});
response.end(body);
return;
}
if (request.method === "GET" && url.pathname.startsWith("/users/")) {
const id = Number(url.pathname.split("/")[2]);
const user = users.find((item) => item.id === id);
if (!user) {
const body = JSON.stringify({ message: "사용자를 찾을 수 없습니다." });
response.writeHead(404, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(body),
});
response.end(body);
return;
}
const body = JSON.stringify(user);
response.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(body),
});
response.end(body);
return;
}
const body = JSON.stringify({ message: "지원하지 않는 경로입니다." });
response.writeHead(404, {
"Content-Type": "application/json; charset=utf-8",
"Content-Length": Buffer.byteLength(body),
});
response.end(body);
});
server.listen(3000, "127.0.0.1", () => {
console.log("서버 실행: http://127.0.0.1:3000");
});파일을 server.ts로 저장한 뒤 실행한다.
npx tsx server.ts다른 터미널에서 요청한다.
curl -i http://127.0.0.1:3000/users
curl -i http://127.0.0.1:3000/users/1
curl -i http://127.0.0.1:3000/users/99/users/1 요청은 개념적으로 다음과 같은 HTTP 메시지로 전달된다.
GET /users/1 HTTP/1.1
Host: 127.0.0.1:3000
Accept: */*응답은 상태 줄, 헤더, 빈 줄, 본문으로 구성된다.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 24
{"id":1,"name":"민수"}애플리케이션 코드는 HTTP 메시지를 직접 바이트 단위로 조립하지 않는다. Node.js의 HTTP 구현이 메시지 직렬화(Serialization), 파싱(Parsing), 연결 관리를 담당한다.
5. 시간 복잡도 또는 성능 특성
네트워크 애플리케이션 성능은 알고리즘 시간 복잡도만으로 설명하기 어렵다. 주로 지연 시간(Latency), 처리량(Throughput), 동시성(Concurrency)을 측정한다.
지연 시간
단순화한 요청 지연 시간은 다음 요소의 합으로 볼 수 있다.
전체 지연 시간
= DNS 조회 시간
+ 연결 설정 시간
+ 보안 연결 설정 시간
+ 요청 전송 시간
+ 서버 처리 시간
+ 응답 전송 시간왕복 시간(RTT, Round-Trip Time)은 클라이언트가 데이터를 보내고 응답이 돌아오기까지 걸리는 시간이다. 새 TCP 연결에는 일반적으로 연결 설정을 위한 1 RTT가 필요하다. TLS 버전과 연결 재개 여부에 따라 보안 연결 설정에 추가 왕복이 발생할 수 있다.
처리량
처리량은 단위 시간에 전송하거나 처리한 데이터 또는 요청의 양이다.
처리량 = 처리한 요청 수 / 경과 시간대용량 전송의 실제 처리량은 링크 대역폭(Bandwidth), 혼잡 제어(Congestion Control), 패킷 손실, 서버 처리 능력의 영향을 받는다.
연결 재사용
매 요청마다 새 연결을 만들면 연결 설정 비용이 반복된다. 지속 연결(Persistent Connection)을 사용하면 여러 요청이 같은 연결을 재사용할 수 있다.
HTTP/1.1은 지속 연결을 기본으로 사용한다. HTTP/2는 한 연결에서 여러 스트림을 동시에 처리하는 다중화(Multiplexing)를 지원한다. HTTP/3는 QUIC 위에서 동작하며 스트림별 손실 처리를 제공한다.
서버 코드의 복잡도
예제의 사용자 조회는 배열을 순차 탐색한다.
users.find((item) => item.id === id);사용자 수를 n이라 할 때 시간 복잡도는 O(n)이다. 식별자를 키로 사용하는 Map이나 데이터베이스 인덱스를 사용하면 조회 비용을 줄일 수 있다. 자바스크립트 Map의 조회는 평균 O(1)이며, 균형 트리 기반 데이터베이스 인덱스 탐색은 일반적으로 O(log n)이다.
6. 실무 사용 사례
웹 API
브라우저, 모바일 앱, 다른 서버가 HTTP API를 호출한다. 서버는 인증, 입력 검증, 비즈니스 로직, 데이터베이스 접근을 수행한 뒤 응답한다.
브라우저 → HTTPS → API 서버 → SQL → PostgreSQL서비스 간 통신
분산 시스템에서는 서비스가 HTTP, 원격 프로시저 호출(RPC), 메시지 큐(Message Queue)를 통해 통신한다.
- 즉시 결과가 필요하면 요청·응답 방식을 사용한다.
- 비동기 처리가 필요하면 메시지 브로커(Message Broker)를 사용한다.
- 강한 형식과 효율적인 바이너리 전송이 필요하면 gRPC 같은 RPC 방식을 사용한다.
실시간 통신
채팅, 공동 편집, 게임 상태 전달에는 서버가 먼저 메시지를 보내야 한다. WebSocket은 연결을 유지하며 클라이언트와 서버가 양방향으로 메시지를 보낼 수 있다.
이름 해석
애플리케이션은 DNS를 이용해 사람이 읽을 수 있는 도메인 이름을 IP 주소로 변환한다. 운영체제와 런타임의 DNS 확인자(Resolver)가 캐시를 사용하므로 매 요청마다 전체 DNS 조회가 발생하는 것은 아니다.
7. 주의할 점
타임아웃을 설정한다
네트워크 요청은 무기한 기다리면 안 된다. 서버 장애, 패킷 손실, 응답 지연으로 연결이 끝나지 않을 수 있다. 연결, 읽기, 전체 요청에 적절한 타임아웃(Timeout)을 둔다.
재시도는 조건부로 수행한다
모든 요청을 재시도하면 중복 처리가 발생할 수 있다. 결제 생성 같은 요청이 서버에서는 처리됐지만 응답만 유실되면 같은 요청이 두 번 실행될 수 있다.
안전한 재시도를 위해 멱등성(Idempotency)을 고려한다. 멱등 연산은 같은 요청을 여러 번 수행해도 최종 상태가 한 번 수행한 결과와 같다. 필요하면 멱등성 키(Idempotency Key)를 사용한다.
입력을 신뢰하지 않는다
헤더, 경로, 쿼리 문자열, 본문은 모두 외부 입력이다. 크기 제한, 형식 검증, 인증, 권한 검사를 적용한다. 오류 메시지에 내부 경로나 스택 추적을 그대로 노출하지 않는다.
전송 보안을 적용한다
HTTP 평문 통신은 중간에서 읽히거나 변조될 수 있다. 인터넷 구간에서는 TLS를 사용하는 HTTPS로 기밀성(Confidentiality), 무결성(Integrity), 서버 인증(Authentication)을 확보한다.
프로토콜 상태와 업무 상태를 구분한다
HTTP 200은 요청 전달과 처리가 성공했다는 뜻으로 사용할 수 있지만, 업무 결과까지 항상 성공했다는 뜻은 아니다. 상태 코드와 응답 본문의 오류 구조를 일관되게 설계해야 한다.
8. 핵심 정리
- 애플리케이션 계층(Application Layer)은 데이터의 형식, 의미, 교환 순서, 오류 처리 규칙을 정의한다.
- HTTP, DNS, SMTP, SSH, WebSocket은 서로 다른 목적의 애플리케이션 계층 프로토콜이다.
- 애플리케이션은 소켓(Socket)을 통해 TCP, UDP, QUIC 같은 하위 전송 기능을 사용한다.
- 연결 상태와 로그인 같은 애플리케이션 상태는 서로 다른 개념이다.
- 성능은 왕복 시간, 연결 설정, 연결 재사용, 메시지 크기, 서버 처리 시간에 의해 결정된다.
- 실무에서는 타임아웃, 멱등성, 입력 검증, TLS, 일관된 오류 처리를 함께 설계해야 한다.