4. Application Layer

chelseey·2025년 4월 16일

E-mail

이메일 시스템의 세 가지 주요 구성 요소

  • User Agents (사용자 에이전트)
    이메일을 작성하고, 읽고, 편집하는 프로그램

  • Mail Servers (메일 서버)
    사용자의 메일을 저장하고 전송하는 역할
    수신된 메일은 사용자 메일박스(노란색),
    보내는 메일은 outgoing queue(초록색)에 저장

  • Simple Mail Transfer Protocol: SMTP
    이메일을 서버 간에 전송하는 표준 프로토콜

사용자 에이전트 → 메일 서버 → SMTP
→ 상대방 메일 서버 → 상대방 사용자 에이전트

E-mail: mail servers

E-mail: the RFC 5321 (SMTP)

전송 계층과 포트

TCP 사용 , 포트 번호 25에서 동작

Direct Transfer

메일을 보내는 쪽 메일 서버가 클라이언트 역할을,
받는 쪽 메일 서버가 서버 역할을 수행
SMTP는 중간에 큐잉 서버 없이도 서버 간 직접 연결로 메시지를 전달

전송의 세 단계

  1. 핸드셰이킹(HELO/EHLO)
    연결 수립 후 서로 인사

  2. 메시지 전송

  3. 연결 종료(QUIT)

command/response interaction

ASCII 텍스트 기반의 명령
3자리 상태 코드 + 설명 문구로 응답
ex. 250 OK , 550 No such user

HTTP와 유사한 구조지만, 전송 대상은 메일 메시지

웹메일** :
웹메일은 사용자↔메일서버 간에 HTTP/HTTPS를 쓰고,
메일서버↔메일서버 간에는 SMTP를 사용

메시지 인코딩 제한

본문과 헤더는 7비트 ASCII 문자만 사용 가능

Scenario: Alice sends e-mail to Bob

작성(Compose)

Alice는 자신의 User Agent(UA, ex.Outlook, iOS Mail)를 열고
받는 사람(To:)에 bob@someschool.edu를 입력한 뒤 본문을 작성

로컬 전송(to Outgoing Queue)

Alice의 UA가 SMTP를 통해 자신(송신) 메일 서버에 연결
작성한 메시지를 서버의 Outgoing Message Queue에 저장

서버 간 연결 설정

송신 메일 서버가 Bob의 메일 서버를 찾기 위해 DNS의 MX 레코드를 조회
해당 호스트(IP)를 확보한 뒤 TCP 포트 25/587로 연결

SMTP 세션에서 메시지 전송

HELO/EHLOMAIL FROM:<alice@…>
RCPT TO:<bob@…>DATA본문 전송QUIT

각 단계마다 3xx~5xx 상태 코드로 성공·오류 확인

수신 서버 처리

Bob의 메일 서버는 도착한 메시지를 Bob의 Mailbox(Inbox) 저장소에 적재

읽기(Read)

Bob이 자신의 UA를 통해 로그인
Inbox에서 Alice의 메시지를 조회

Sample SMTP interaction

SMTP 프로토콜의 대화 과정

연결 시작

S: 220 hamburger.edu

수신자 측 메일 서버(hamburger.edu)가 연결을 허용하고 인사
SMTP 표준 응답 코드 220: 서비스 준비 완료

인사 (HELO)

C: HELO crepes.fr
S: 250 Hello crepes.fr, pleased to meet you

송신자(Alice 측 메일 서버)가 자신의 도메인(crepes.fr)을 밝히며 인사
서버가 250 OK 로 응답하며 인사 수락

발신자 정보

C: MAIL FROM: <alice@crepes.fr>
S: 250 alice@crepes.fr... Sender ok

보낸 사람(Alice)의 이메일 주소 명시
수신 서버가 발신자 이메일을 허용했음을 250 OK로 응답

수신자 정보

C: RCPT TO: <bob@hamburger.edu>
S: 250 bob@hamburger.edu... Recipient ok

받을 사람(Bob)의 이메일 주소 명시
수신 서버가 해당 계정이 존재함을 확인하고 250 OK 응답

메일 본문 전송 준비

C: DATA
S: 354 Enter mail, end with "." on a line by itself

클라이언트가 본문 전송을 시작할 것을 알림
서버가 354로 응답하며, 본문 입력을 기다림

메일 본문 입력

C: Do you like ketchup?
C: How about pickles?
C: .

본문 메시지 입력
단독 마침표(.)로 메일 입력 종료를 표시 (SMTP 규칙)

메일 전송 완료

S: 250 Message accepted for delivery

서버가 메일 수신 완료했음을 확인하고 250 OK 응답

연결 종료

C: QUIT
S: 221 hamburger.edu closing connection

클라이언트가 세션 종료 요청
서버가 221 로 응답하고 연결 종료

SMTP: closing observations

Pull vs Push

  • HTTP (Pull)
    클라이언트가 서버에 요청(request)을 보내야 데이터가 돌아옴.
  • SMTP (Push)
    송신 메일 서버가 수신 메일 서버에 능동적으로 연결을 열고(HELO→MAIL) 메시지를 밀어넣음.
    사용자가 보낸 순간, 서버 간 큐에 담겨 있던 메일들이 자동으로 배달됨.

ASCII 명령/응답 & 상태 코드

HTTP: GET /index.html → 200 OK
SMTP: MAIL FROM:<…> → 250 OK

두 프로토콜 모두 사람이 읽을 수 있는 ASCII 텍스트로 명령을 주고받고,
숫자 코드로 성공·오류를 표시

전달 대상 객체(Object) 처리 방식

  • HTTP
    각 파일(HTML, CSS, 이미지 등)이 각각의 응답(Response) 으로 전송
    ex. GET logo.png200 OK + logo.png 데이터

  • SMTP
    하나의 메일 메시지 안에 여러 파트(multipart) 를 포함 가능
    본문(text/html), 첨부파일(이미지·PDF·워드) 등을 boundary 로 구분해 단일 세션에서 연속 전송

Content-Type: multipart/mixed; boundary=XYZ  
--XYZ  
Content-Type: text/plain  
(본문)  
--XYZ  
Content-Type: application/pdf  
(첨부파일 바이트)  
--XYZ--  

Persistence

SMTP는 송신 메일 서버가 수신 메일 서버와 TCP 연결을 맺으면
지속 연결을 사용해서 여러 메일을 하나의 TCP 연결로 보냄

메시지 종료 인식: CRLF.CRLF

SMTP 데이터 전송 단계(DATA 명령 후)에,
단독으로 (.) 한 줄이 오면 그 지점이 본문의 끝임을 표시

→ \r\n.\r\n (CRLF.CRLF) 시퀀스를 서버가 감지하면
데이터 모드에서 빠져나와 다음 상태로 넘어감

Mail message format

SMTP (RFC 531)
: 메일을 전송하는 방법을 정의한 통신 프로토콜 (HTTP와 비슷한 역할)

RFC 822
메일의 문법(Syntax), 내용 자체의 구조를 정의
→ HTML이 웹페이지 내용을 정의하듯, 이메일 본문의 구조를 정의

메일 메시지 형식 : Header + Blank line + Body

To: bob@example.com
From: alice@example.com
Subject: Lunch

Hey Bob,
Want to grab lunch today?

Header (초록색 박스)

To: , From: , Subject: 등 메타정보
각 줄이 key: value 형태로 구성됨

Blank Line (중간 공백 한 줄)

헤더와 본문을 구분하는 역할
빈 줄이 없으면 메시지가 파싱되지 않음

Body (파란 박스)

사람이 읽는 실제 메시지 내용
ASCII 문자만 허용

** SMTP 명령 ↔︎ 메시지 헤더
MAIL FROM, RCPT TO → SMTP 통신용 명령
From:, To: → 메일 내용 안의 정보 (사람이 보게 되는 주소)

Mail access protocols

송신자 UA ──SMTP──▶ 송신자 서버 ──SMTP──▶ 수신자 서버 
──(액세스 프로토콜)──▶ 수신자 UA

SMTP

송신자 서버에서 수신자 서버로 메일 메시지(헤더+본문)를 밀어넣어
저장해 주는 프로토콜

UA → 송신자 메일 서버 (SMTP)
송신자 메일 서버 → 수신자 메일 서버 (SMTP)

IMAP (Internet Mail Access Protocol)

액세스(읽기·관리) 담당
RFC 3501 규격
메일을 서버에 그대로 두고 필요한 순간마다 동기화
폴더, 읽음/삭제 상태가 여러 기기에서 실시간으로 반영

HTTP (웹메일용)

브라우저 UI에 쓰는 범용 프로토콜
Gmail 등은 브라우저와 HTTPS로 통신
내부에서는 여전히 SMTP(발송)·IMAP/POP3(수신)를 호출
사용자는 설치 없이 웹페이지에서 메일을 작성,열람

Socket programming

소켓 (socket)
: 애플리케이션과 transport layer (TCP/UDP) 사이의 입출구(door) 역할

  • app developer가 제어하는 부분
    소켓 생성: socket()
    서버 주소 설정: bind()
    클라이언트 연결 요청: connect()
    서버 연결 수락: accept()
    데이터 송수신: send(), recv()

  • OS(운영체제)가 제어하는 부분
    transport (TCP) : 패킷 분할, 재전송, 흐름 제어, 혼잡 제어
    network (IP) : 라우팅, 주소 지정
    link/physical : 이더넷, Wi-Fi 처리, 드라이버 작동

Socket programming

소켓 유형 비교

ex. 대문자 변환 서버

클라이언트가 키보드에서 한 줄(line) 입력 → 서버로 전송
서버가 받은 문자열을 모두 대문자로 변환
서버가 변환된 문자열을 다시 클라이언트로 전송
클라이언트가 수신한 문자열을 화면에 출력

Socket programming with UDP

UDP 소켓 프로그래밍 특징

  • 무연결(Connectionless)
    TCP처럼 3‑way handshake 로 연결을 맺지 않음
    보내기 전이나 후에 별도의 연결 설정/해제 과정이 없음

  • 주소 지정(Addressing) 방식
    송신 측:
    매번 전송할 때마다 데이터그램에 목적지 IP 주소 + 포트 번호를 명시
    수신 측:
    recvfrom() 호출 시, 들어온 패킷의 출발지 IP + 포트가 함께 반환
    → 이 정보를 통해 누가 보낸 것인지 알 수 있음

recvfrom() : UDP 소켓에서 데이터그램을 수신할 때 호출

  • 데이터 단위: Datagram
    “메시지 하나 = UDP 패킷 하나”
    각 패킷은 최대 크기(IPv4 기준 약 65 535 Bytes)를 가짐
    경계(boundary)가 명확하여, 한 번 sendto()로 보낸 메시지를 한 번에 recvfrom()으로 읽음

→ UDP는 연결 없는, 신뢰성 없는 데이터그램 전송을 제공

Client/server socket interaction : UDP

Example app: UDP client

ex. Python으로 작성한 UDP 클라이언트 코드

socket 라이브러리 불러오기

from socket import *

서버 주소 지정

serverName = 'hostname'
serverPort = 12000

UDP 소켓 생성

clientSocket = socket( AF_INET, SOCK_DGRAM )

키보드로부터 메시지 입력받기

message = raw_input('Input lowercase sentence:')

사용자가 터미널에 입력한 문자열을 message 변수에 저장

서버로 메시지 전송

clientSocket.sendto( message.encode(), (serverName, serverPort) )

서버 응답 읽기

modifiedMessage, serverAddress = clientSocket.recvfrom(2048)

돌아온 데이터(modifiedMessage)와
그걸 보낸 서버의 주소(serverAddress)를 함께 받음

응답 내용 출력 & 소켓 닫기

print( modifiedMessage.decode() )
clientSocket.close()

받은 바이트를 다시 문자열로 디코딩해 화면에 출력하고,
close() 로 소켓을 정리

Example app: UDP server

ex. 소문자를 대문자로 바꿔 돌려주는 Python UDP 서버

socket 라이브러리 불러오기

from socket import *

서버 포트 설정

serverPort = 12000

이 서버가 수신 대기할 로컬 포트 번호를 12000으로 지정

소켓 생성 & 바인드

serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print("The server is ready to receive")

무한 루프에서 요청 처리

while True:
    message, clientAddress = serverSocket.recvfrom(2048)
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(), clientAddress)

sendto(..., clientAddress) : recvfrom()으로부터 알게 된 clientAddress로 응답 전송

한 번 생성한 serverSocket을 루프 안에서 계속 재사용

Socket programming with TCP

클라이언트

  • 서버 프로세스가 이미 실행 중이어야 함

  • 클라이언트가 서버와 연결하는 방법

→ 클라이언트는 소켓을 열 때 곧바로 서버와 연결을 맺음

서버

  • 클라이언트가 접속 요청(connect)을 보내오면
    서버 TCP가 내부적으로 새로운 소켓을 하나 더 만들어
    그 소켓을 통해 해당 클라이언트와만 데이터 교환을 함

  • 다중 클라이언트 지원
    각 클라이언트마다 별도의 연결 소켓이 생기므로
    서버는 여러 클라이언트를 동시에 연결할 수 있음.

TCP 소켓은
(클라이언트 IP, 클라이언트 Port) → (서버 IP, 서버 Port)으로 구성

A 192.168.0.2:40001→10.0.0.1:80,
B 192.168.0.2:40002→10.0.0.1:80

클라이언트 측 Port가 매번 다르기 때문에, 클라이언트별로 구분

Client/server socket interaction: TCP

Example app : TCP client

라이브러리 불러오기

from socket import *

연결할 서버 주소 지정

serverName = 'servername'
serverPort = 12000

TCP 소켓 생성

clientSocket = socket(AF_INET, SOCK_STREAM)

SOCK_STREAM: TCP 소켓

서버와 연결 수립(3‑way 핸드셰이크)

clientSocket.connect((serverName, serverPort))

이 순간 클라이언트와 서버 간에 TCP 연결이 생김

사용자 입력 읽기

sentence = raw_input('Input lowercase sentence:')

터미널(콘솔)에서 소문자 문장을 한 줄 입력받아 sentence에 저장

서버로 데이터 전송

clientSocket.send(sentence.encode())

서버 응답 수신

modifiedSentence = clientSocket.recv(1024)

최대 1024바이트를 읽어들여 modifiedSentence에 저장

결과 출력

print('From Server:', modifiedSentence.decode())

소켓 닫기

clientSocket.close()

더 이상 통신이 필요 없으면 close()로 TCP 연결을 종료하고,
OS 자원을 해제

Example app : TCP server

라이브러리 불러오기

from socket import *

서버 포트 설정

serverPort = 12000

서버가 수신 대기할 로컬 포트 번호를 변수에 저장

리스닝 소켓 생성 및 바인딩

serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))

연결 대기 시작

serverSocket.listen(1)
print('The server is ready to receive')

클라이언트 처리 루프

while True:
    connectionSocket, addr = serverSocket.accept()
    sentence = connectionSocket.recv(1024).decode()
    capitalizedSentence = sentence.upper()
    connectionSocket.send(capitalizedSentence.encode())
    connectionSocket.close()

connectionSocket.close() 로 해당 클라이언트 통신만 종료

0개의 댓글