Skip to content

Split View: [컴퓨터 네트워크] 07. 소켓 프로그래밍: TCP와 UDP

|

[컴퓨터 네트워크] 07. 소켓 프로그래밍: TCP와 UDP

본 포스팅은 James Kurose, Keith Ross의 Computer Networking: A Top-Down Approach (6th Edition) 교재를 기반으로 정리한 내용입니다.


1. 소켓 프로그래밍 개요

네트워크 애플리케이션은 두 프로세스(클라이언트, 서버) 간 통신으로 이루어진다. 이 통신의 접점이 바로 소켓(socket) 이다.

┌──────────────┐                    ┌──────────────┐
│  애플리케이션  │                    │  애플리케이션  │
│   프로세스    │                    │   프로세스    │
│      │       │                    │      │       │
│   소켓(문)   │                    │   소켓(문)   │
├──────┼───────┤    인터넷           ├──────┼───────┤
│   전송 계층   │ ←═════════════> │   전송 계층   │
└──────────────┘                    └──────────────┘

소켓은 애플리케이션 계층과 전송 계층 사이의 인터페이스(API) 이다.

1.1 두 가지 전송 서비스

서비스TCPUDP
연결연결 지향비연결
신뢰성신뢰적 (순서 보장, 재전송)비신뢰적 (손실 가능)
흐름 제어있음없음
혼잡 제어있음없음
오버헤드높음낮음

2. UDP 소켓 프로그래밍

2.1 UDP 통신의 특징

  • 연결 설정 없음: 데이터를 보내기 전에 핸드셰이킹이 불필요
  • 각 데이터그램에 목적지 IP와 포트 번호를 명시적으로 첨부
  • 패킷 순서 보장 없음, 패킷 손실 가능

2.2 UDP 클라이언트-서버 상호작용 흐름

    서버                          클라이언트
    ────                          ────────
  소켓 생성                       소켓 생성
  (serverSocket)                  (clientSocket)
       │                              │
  주소 바인딩                          │
  (IP, 포트)                          │
       │                              │
  수신 대기 ←──── 데이터그램 전송 ──── 메시지 전송
  (recvfrom)      (목적지 IP:포트)    (sendto)
       │                              │
  데이터 처리                     수신 대기
       │                         (recvfrom)
  응답 전송 ────── 데이터그램 ──────→ 응답 수신
  (sendto)                            │
       │                         소켓 닫기

2.3 UDP 서버 코드 (Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print("UDP 서버 준비 완료. 포트:", serverPort)

while True:
    # 클라이언트로부터 메시지 수신
    message, clientAddress = serverSocket.recvfrom(2048)
    print(f"수신: {message.decode()} from {clientAddress}")

    # 대문자로 변환하여 응답
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(), clientAddress)

코드 해설

socket(AF_INET, SOCK_DGRAM)
  AF_INET: IPv4
  SOCK_DGRAM: UDP 소켓

bind(('', serverPort))
  '': 모든 인터페이스에서 수신
  serverPort: 바인딩할 포트 번호

recvfrom(2048)
  최대 2048 바이트 수신
  반환: (데이터, 클라이언트 주소)

sendto(data, address)
  데이터를 지정된 주소로 전송

2.4 UDP 클라이언트 코드 (Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM)

message = input("소문자 문장을 입력하세요: ")
clientSocket.sendto(message.encode(), (serverName, serverPort))

# 서버 응답 수신
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print("서버 응답:", modifiedMessage.decode())

clientSocket.close()

실행 예시

# 서버 터미널
$ python UDPServer.py
UDP 서버 준비 완료. 포트: 12000
수신: hello world from ('127.0.0.1', 54321)

# 클라이언트 터미널
$ python UDPClient.py
소문자 문장을 입력하세요: hello world
서버 응답: HELLO WORLD

3. TCP 소켓 프로그래밍

3.1 TCP 통신의 특징

  • 연결 지향: 데이터 전송 전에 반드시 TCP 연결 수립
  • 환영(welcome) 소켓연결 소켓 구분
  • 바이트 스트림 기반 (메시지 경계 없음)
  • 신뢰적 전송: 순서 보장, 재전송

3.2 TCP 클라이언트-서버 상호작용 흐름

    서버                          클라이언트
    ────                          ────────
  환영 소켓 생성                       │
  (serverSocket)                       │
       │                               │
  주소 바인딩                           │
  listen()                             │
       │                          소켓 생성
  accept() 대기 ←── TCP 연결 요청 ── connect()
       │          (3-way handshake)    │
  연결 소켓 생성                        │
  (connectionSocket)                   │
       │                               │
  recv() ←──────── 데이터 ──────── send()
       │                               │
  데이터 처리                           │
       │                               │
  send() ────────── 응답 ──────────→ recv()
       │                               │
  연결 소켓 닫기                    소켓 닫기
  (connectionSocket.close())           │
  다음 연결 대기...

핵심: 두 가지 소켓

서버 측 소켓:

  1. 환영 소켓 (Welcome/Listening Socket)
     - serverSocket: 클라이언트 연결 요청을 받는 문
     - accept() 호출 시 대기

  2. 연결 소켓 (Connection Socket)
     - connectionSocket: 특정 클라이언트와의 전용 소켓
     - accept()가 반환하는 새 소켓
     - 데이터 송수신에 사용

3.3 TCP 서버 코드 (Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)
print("TCP 서버 준비 완료. 포트:", serverPort)

while True:
    # 클라이언트 연결 수락 (블로킹)
    connectionSocket, clientAddr = serverSocket.accept()
    print(f"연결 수립: {clientAddr}")

    # 데이터 수신
    message = connectionSocket.recv(1024).decode()
    print(f"수신: {message}")

    # 대문자로 변환하여 응답
    capitalizedMessage = message.upper()
    connectionSocket.send(capitalizedMessage.encode())

    # 연결 소켓 닫기 (환영 소켓은 유지)
    connectionSocket.close()

코드 해설

socket(AF_INET, SOCK_STREAM)
  SOCK_STREAM: TCP 소켓

listen(1)
  최대 1개의 대기 연결 허용
  (백로그 큐 크기)

accept()
  연결 요청이 올 때까지 블로킹
  반환: (새 연결 소켓, 클라이언트 주소)

recv(1024)
  최대 1024 바이트 수신
  TCP는 바이트 스트림이므로 경계 없음

send(data)
  연결된 소켓으로 데이터 전송
  목적지 주소 명시 불필요 (이미 연결됨)

3.4 TCP 클라이언트 코드 (Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)

# TCP 연결 수립 (3-way handshake)
clientSocket.connect((serverName, serverPort))

message = input("소문자 문장을 입력하세요: ")
clientSocket.send(message.encode())

# 서버 응답 수신
modifiedMessage = clientSocket.recv(1024)
print("서버 응답:", modifiedMessage.decode())

clientSocket.close()

실행 예시

# 서버 터미널
$ python TCPServer.py
TCP 서버 준비 완료. 포트: 12000
연결 수립: ('127.0.0.1', 54322)
수신: hello tcp world

# 클라이언트 터미널
$ python TCPClient.py
소문자 문장을 입력하세요: hello tcp world
서버 응답: HELLO TCP WORLD

4. UDP vs TCP 소켓 프로그래밍 비교

4.1 코드 수준 차이

항목UDPTCP
소켓 타입SOCK_DGRAMSOCK_STREAM
연결 설정없음connect() / accept()
데이터 전송sendto(data, addr)send(data)
데이터 수신recvfrom(bufsize)recv(bufsize)
주소 지정매번 명시연결 시 한 번만
서버 소켓하나만 사용환영 + 연결 소켓

4.2 흐름 비교 다이어그램

UDP:                              TCP:
────                              ────
서버: socket()                    서버: socket()
서버: bind()                      서버: bind()
                                  서버: listen()
클라: socket()                    서버: accept() (블로킹)
클라: sendto() ──────────>
                                  클라: socket()
서버: recvfrom()                  클라: connect() ──────>
서버: sendto() ──────────>                   3-way handshake
                                  서버: (새 연결 소켓 반환)
클라: recvfrom()
클라: close()                     클라: send() ──────────>
                                  서버: recv()
                                  서버: send() ──────────>
                                  클라: recv()
                                  클라: close()
                                  서버: close() (연결 소켓)

5. 멀티스레드 TCP 서버

기본 TCP 서버는 한 번에 하나의 클라이언트만 처리한다. 여러 클라이언트를 동시에 처리하려면 멀티스레딩을 사용한다.

from socket import *
import threading

def handle_client(connectionSocket, addr):
    """각 클라이언트를 처리하는 스레드 함수"""
    print(f"스레드 시작: {addr}")
    try:
        message = connectionSocket.recv(1024).decode()
        response = message.upper()
        connectionSocket.send(response.encode())
    finally:
        connectionSocket.close()
        print(f"스레드 종료: {addr}")

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(5)
print("멀티스레드 TCP 서버 준비 완료")

while True:
    connectionSocket, addr = serverSocket.accept()
    # 새 스레드에서 클라이언트 처리
    thread = threading.Thread(
        target=handle_client,
        args=(connectionSocket, addr)
    )
    thread.start()
멀티스레드 서버 동작:

  클라이언트1 ──연결──> [환영 소켓] ──accept()──> [연결 소켓1] ──Thread 1
  클라이언트2 ──연결──> [환영 소켓] ──accept()──> [연결 소켓2] ──Thread 2
  클라이언트3 ──연결──> [환영 소켓] ──accept()──> [연결 소켓3] ──Thread 3

  각 클라이언트가 독립적인 스레드에서 처리됨

6. 정리

소켓 프로그래밍 핵심 요약:

  UDP 소켓:
  ├── 비연결, 데이터그램 단위
  ├── sendto/recvfrom (매번 주소 지정)
  ├── 빠르지만 비신뢰적
  └── 사용처: DNS, 스트리밍, 게임

  TCP 소켓:
  ├── 연결 지향, 바이트 스트림
  ├── connect/accept로 연결 수립
  ├── send/recv (주소 지정 불필요)
  ├── 신뢰적이지만 오버헤드 있음
  └── 사용처: 웹, 이메일, 파일 전송

7. 확인 문제

Q1. UDP 소켓에서 sendto()를 사용하는 이유는?

UDP는 비연결 프로토콜이므로 연결이 사전에 수립되지 않는다. 따라서 매 데이터그램을 보낼 때마다 목적지 IP 주소와 포트 번호를 명시해야 한다. sendto(data, (ip, port)) 형태로 목적지를 지정한다. 반면 TCP는 connect()로 이미 연결이 수립되어 있으므로 send(data)만으로 충분하다.

Q2. TCP 서버에서 환영 소켓과 연결 소켓의 차이는?
  • 환영 소켓(serverSocket): 클라이언트의 연결 요청을 받아들이는 소켓. listen()accept()에 사용. 서버가 살아있는 동안 계속 유지된다.
  • 연결 소켓(connectionSocket): accept()가 반환하는 새 소켓. 특정 클라이언트와의 데이터 송수신에 사용. 통신이 끝나면 닫는다.

이렇게 분리함으로써 서버가 여러 클라이언트와 동시에 통신할 수 있다.

Q3. TCP는 바이트 스트림이라는 것이 프로그래밍에 어떤 영향을 주는가?

TCP는 메시지 경계를 유지하지 않는다. send()로 100바이트를 보내더라도 recv()가 한 번에 100바이트를 받는다는 보장이 없다. 50바이트씩 두 번에 나눠 받을 수도 있다. 따라서 애플리케이션에서 메시지 경계를 직접 처리해야 한다 (예: 구분자 사용, 길이 헤더 추가 등).

[Computer Networking] 07. Socket Programming: TCP and UDP

This post is based on the textbook Computer Networking: A Top-Down Approach (6th Edition) by James Kurose and Keith Ross.


1. Socket Programming Overview

Network applications consist of communication between two processes (client and server). The point of contact for this communication is the socket.

+--------------+                    +--------------+
| Application  |                    | Application  |
|  Process     |                    |  Process     |
|      |       |                    |      |       |
|  Socket(door)|                    |  Socket(door)|
+------+-------+    Internet        +------+-------+
| Transport    | <===============> | Transport    |
+--------------+                    +--------------+

A socket is the interface (API) between the application layer and the transport layer.

1.1 Two Transport Services

ServiceTCPUDP
ConnectionConnection-orientedConnectionless
ReliabilityReliable (order guaranteed, retransmission)Unreliable (loss possible)
Flow controlYesNo
Congestion controlYesNo
OverheadHighLow

2. UDP Socket Programming

2.1 Characteristics of UDP Communication

  • No connection setup: No handshaking needed before sending data
  • Destination IP and port number must be explicitly attached to each datagram
  • No packet order guarantee, packet loss possible

2.2 UDP Client-Server Interaction Flow

    Server                          Client
    ------                          ------
  Create socket                    Create socket
  (serverSocket)                   (clientSocket)
       |                              |
  Bind address                        |
  (IP, port)                          |
       |                              |
  Wait to receive <--- Datagram --- Send message
  (recvfrom)      (dest IP:port)    (sendto)
       |                              |
  Process data                   Wait to receive
       |                         (recvfrom)
  Send response ---- Datagram -----> Receive response
  (sendto)                            |
       |                         Close socket

2.3 UDP Server Code (Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))
print("UDP server ready. Port:", serverPort)

while True:
    # Receive message from client
    message, clientAddress = serverSocket.recvfrom(2048)
    print(f"Received: {message.decode()} from {clientAddress}")

    # Convert to uppercase and respond
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(), clientAddress)

Code Explanation

socket(AF_INET, SOCK_DGRAM)
  AF_INET: IPv4
  SOCK_DGRAM: UDP socket

bind(('', serverPort))
  '': Receive on all interfaces
  serverPort: Port number to bind

recvfrom(2048)
  Receive up to 2048 bytes
  Returns: (data, client address)

sendto(data, address)
  Send data to the specified address

2.4 UDP Client Code (Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_DGRAM)

message = input("Enter a lowercase sentence: ")
clientSocket.sendto(message.encode(), (serverName, serverPort))

# Receive server response
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print("Server response:", modifiedMessage.decode())

clientSocket.close()

Execution Example

# Server terminal
$ python UDPServer.py
UDP server ready. Port: 12000
Received: hello world from ('127.0.0.1', 54321)

# Client terminal
$ python UDPClient.py
Enter a lowercase sentence: hello world
Server response: HELLO WORLD

3. TCP Socket Programming

3.1 Characteristics of TCP Communication

  • Connection-oriented: TCP connection must be established before data transfer
  • Distinction between welcome (listening) socket and connection socket
  • Byte-stream based (no message boundaries)
  • Reliable delivery: order guaranteed, retransmission

3.2 TCP Client-Server Interaction Flow

    Server                          Client
    ------                          ------
  Create welcome socket                 |
  (serverSocket)                        |
       |                                |
  Bind address                          |
  listen()                              |
       |                          Create socket
  accept() waiting <-- TCP conn req -- connect()
       |          (3-way handshake)     |
  Create connection socket              |
  (connectionSocket)                    |
       |                                |
  recv() <----------- Data ---------- send()
       |                                |
  Process data                          |
       |                                |
  send() ----------- Response -------> recv()
       |                                |
  Close connection socket          Close socket
  (connectionSocket.close())            |
       |
  Wait for next connection...

Key Point: Two Types of Sockets

Server-side sockets:

  1. Welcome Socket (Listening Socket)
     - serverSocket: The door that accepts client connection requests
     - Used with accept() call

  2. Connection Socket
     - connectionSocket: A dedicated socket for a specific client
     - New socket returned by accept()
     - Used for data transmission

3.3 TCP Server Code (Python)

from socket import *

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)
print("TCP server ready. Port:", serverPort)

while True:
    # Accept client connection (blocking)
    connectionSocket, clientAddr = serverSocket.accept()
    print(f"Connection established: {clientAddr}")

    # Receive data
    message = connectionSocket.recv(1024).decode()
    print(f"Received: {message}")

    # Convert to uppercase and respond
    capitalizedMessage = message.upper()
    connectionSocket.send(capitalizedMessage.encode())

    # Close connection socket (welcome socket remains open)
    connectionSocket.close()

Code Explanation

socket(AF_INET, SOCK_STREAM)
  SOCK_STREAM: TCP socket

listen(1)
  Allow up to 1 queued connection
  (backlog queue size)

accept()
  Blocks until a connection request arrives
  Returns: (new connection socket, client address)

recv(1024)
  Receive up to 1024 bytes
  TCP is a byte stream, so no boundaries

send(data)
  Send data over the connected socket
  No need to specify destination address (already connected)

3.4 TCP Client Code (Python)

from socket import *

serverName = '127.0.0.1'
serverPort = 12000
clientSocket = socket(AF_INET, SOCK_STREAM)

# Establish TCP connection (3-way handshake)
clientSocket.connect((serverName, serverPort))

message = input("Enter a lowercase sentence: ")
clientSocket.send(message.encode())

# Receive server response
modifiedMessage = clientSocket.recv(1024)
print("Server response:", modifiedMessage.decode())

clientSocket.close()

Execution Example

# Server terminal
$ python TCPServer.py
TCP server ready. Port: 12000
Connection established: ('127.0.0.1', 54322)
Received: hello tcp world

# Client terminal
$ python TCPClient.py
Enter a lowercase sentence: hello tcp world
Server response: HELLO TCP WORLD

4. UDP vs TCP Socket Programming Comparison

4.1 Code-Level Differences

ItemUDPTCP
Socket typeSOCK_DGRAMSOCK_STREAM
Connection setupNoneconnect() / accept()
Data sendsendto(data, addr)send(data)
Data receiverecvfrom(bufsize)recv(bufsize)
Address specificationEvery timeOnce at connection
Server socketsSingle socketWelcome + connection socket

4.2 Flow Comparison Diagram

UDP:                              TCP:
----                              ----
Server: socket()                  Server: socket()
Server: bind()                    Server: bind()
                                  Server: listen()
Client: socket()                  Server: accept() (blocking)
Client: sendto() ---------->
                                  Client: socket()
Server: recvfrom()                Client: connect() ------>
Server: sendto() ---------->                   3-way handshake
                                  Server: (new connection socket returned)
Client: recvfrom()
Client: close()                   Client: send() ---------->
                                  Server: recv()
                                  Server: send() ---------->
                                  Client: recv()
                                  Client: close()
                                  Server: close() (connection socket)

5. Multi-Threaded TCP Server

The basic TCP server handles only one client at a time. To handle multiple clients simultaneously, multi-threading is used.

from socket import *
import threading

def handle_client(connectionSocket, addr):
    """Thread function to handle each client"""
    print(f"Thread started: {addr}")
    try:
        message = connectionSocket.recv(1024).decode()
        response = message.upper()
        connectionSocket.send(response.encode())
    finally:
        connectionSocket.close()
        print(f"Thread ended: {addr}")

serverPort = 12000
serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(5)
print("Multi-threaded TCP server ready")

while True:
    connectionSocket, addr = serverSocket.accept()
    # Handle client in a new thread
    thread = threading.Thread(
        target=handle_client,
        args=(connectionSocket, addr)
    )
    thread.start()
Multi-threaded server operation:

  Client1 --connect--> [Welcome socket] --accept()--> [Conn socket 1] --Thread 1
  Client2 --connect--> [Welcome socket] --accept()--> [Conn socket 2] --Thread 2
  Client3 --connect--> [Welcome socket] --accept()--> [Conn socket 3] --Thread 3

  Each client is handled in an independent thread

6. Summary

Socket programming key summary:

  UDP Socket:
  +-- Connectionless, datagram-based
  +-- sendto/recvfrom (specify address every time)
  +-- Fast but unreliable
  +-- Use cases: DNS, streaming, games

  TCP Socket:
  +-- Connection-oriented, byte stream
  +-- connect/accept to establish connection
  +-- send/recv (no address specification needed)
  +-- Reliable but has overhead
  +-- Use cases: Web, email, file transfer

7. Review Questions

Q1. Why does a UDP socket use sendto()?

UDP is a connectionless protocol, so no connection is established in advance. Therefore, the destination IP address and port number must be specified with each datagram sent. It uses the form sendto(data, (ip, port)) to specify the destination. In contrast, TCP already has an established connection via connect(), so send(data) alone is sufficient.

Q2. What is the difference between the welcome socket and the connection socket in a TCP server?
  • Welcome socket (serverSocket): The socket that accepts client connection requests. Used with listen() and accept(). Remains open as long as the server is running.
  • Connection socket (connectionSocket): A new socket returned by accept(). Used for data transmission with a specific client. Closed when communication is complete.

This separation allows the server to communicate with multiple clients simultaneously.

Q3. How does TCP being a byte stream affect programming?

TCP does not maintain message boundaries. Even if send() sends 100 bytes, there is no guarantee that recv() will receive all 100 bytes at once. It might receive 50 bytes in two separate calls. Therefore, the application must handle message boundaries itself (e.g., using delimiters, adding a length header, etc.).