네트워크 기초

소켓 프로그래밍 기초

meellon 2026. 7. 6. 05:10

학습 목표

TCP echo 서버·클라이언트를 소켓 API로 구현할 수 있다.

서버 bindlistenaccept 와 클라이언트 connect 흐름을 설명할 수 있다.

listen 소켓연결 소켓(connected) 의 차이를 설명할 수 있다.

소켓 호출이 시스템 콜을 통해 커널 TCP 스택과 연결됨을 설명할 수 있다.

문제 상황

  • curl은 되는데 직접 TCP 서버를 짜면 Address already in use가 난다
    • bind·SO_REUSEADDR·프로세스 재시작 타이밍
  • 서버 코드에 accept가 있는데 클라이언트는 connect만 한다 — 역할이 다르다
  • recv0바이트를 반환하면 무한 루프에 빠진다
    • 상대가 연결을 닫음close/FIN 처리 필요
  • connect·sendall을 호출했는데 커널·와이어에서 실제로 무슨 일이 일어나는지 감이 없다
  • 애플리케이션 계층 개요에서 소켓 API를 봤다 — 이번엔 직접 호출한다

1. Echo 서버가 하는 일

Echo는 받은 바이트를 그대로 돌려보낸다. HTTP·프로토콜 파싱 없이 TCP 바이트 스트림만 다루므로 소켓 API 연습에 적합하다.

역할 동작
서버 포트에 고정·대기·연결 수락·데이터 반사
클라이언트 서버 주소로 연결·전송·응답 수신
전송 계층 TCP 신뢰·순서 — 앱은 send/recv만 호출

2. 서버 vs 클라이언트 API 순서

서버 (TCP) — echo_server.py

순서 호출 의미
1 socket() listen_sock — IPv4 TCP 소켓
2 setsockopt(SO_REUSEADDR) 재시작 시 포트 재사용
3 bind((HOST, PORT)) 127.0.0.1:9000 고정
4 listen(BACKLOG) 연결 대기 큐
5 accept() 클라이언트 연결 수락conn
6 conn.recv / conn.sendall conn으로 echo
7 not databreak 상대 FINwith conn 종료
8 외부 while True listen_sock 은 다음 accept 대기

클라이언트 (TCP) — echo_client.py

순서 호출 의미
1 socket() sock 생성
2 connect((HOST, PORT)) 3-way handshake
3 sendall(message.encode()) UTF-8 bytes 전송
4 recv(1024)print echo 응답 수신·출력
5 with 블록 종료 close() — FIN
  • listen 소켓accept만 한다. 데이터 recv 안 함
  • conn 소켓accept가 돌려준 연결별 fd — echo는 여기서 recv/send
  • 클라이언트는 bind 생략 가능 — OS가 임시 포트(ephemeral) 할당

3. 코드 — echo 서버

code/echo_server.py:

#!/usr/bin/env python3
import socket

HOST = "127.0.0.1"
PORT = 9000
BACKLOG = 5


def main() -> None:
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as listen_sock:
        listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listen_sock.bind((HOST, PORT))
        listen_sock.listen(BACKLOG)
        print(f"listening on {HOST}:{PORT}")

        while True:
            conn, addr = listen_sock.accept()
            with conn:
                print(f"connected from {addr}")
                while True:
                    data = conn.recv(1024)
                    if not data:
                        break
                    conn.sendall(data)
                print(f"closed {addr}")


if __name__ == "__main__":
    main()
설명
BACKLOG = 5 listen() 인자 — handshake 대기 큐 상한(개념)
SO_REUSEADDR 재시작 시 TIME_WAIT 포트 재사용
with conn 연결별 fd 자동 close
recv(1024) TCP 스트림 — 한 번에 다 안 올 수 있음
not data 빈 bytes = 상대 FIN — 루프 종료
closed {addr} conn 종료 로그 — listen_sock 은 유지

4. 코드 — echo 클라이언트

code/echo_client.py:

#!/usr/bin/env python3
import socket
import sys

HOST = "127.0.0.1"
PORT = 9000


def main() -> None:
    message = sys.argv[1] if len(sys.argv) > 1 else "hello echo"

    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((HOST, PORT))
        sock.sendall(message.encode())
        data = sock.recv(1024)
        print(data.decode())


if __name__ == "__main__":
    main()
# 터미널 1
python3 code/echo_server.py

# 터미널 2
python3 code/echo_client.py "ping from client"
# ping from client

5. 유저 모드와 커널

소켓 함수는 라이브러리에 있지만, 실제로는 시스템 콜커널에 진입한다.

echo_server.py (listen_sock + conn):

listen_sock                     Kernel
  socket()         ──syscall──>  create listen_sock fd
  setsockopt       ──syscall──>  SO_REUSEADDR
  bind(:9000)      ──syscall──>  local port assigned
  listen(BACKLOG)  ──syscall──>  TCP_LISTEN
  accept()         ──syscall──>  return conn fd · ESTABLISHED
conn (per client)
  recv(1024)       ──syscall──>  copy from socket rx buffer
  sendall(data)    ──syscall──>  enqueue socket tx buffer
  close (with)     ──syscall──>  FIN · release conn fd

echo_client.py (sock):

sock                            Kernel
  socket()         ──syscall──>  create sock fd
  connect(:9000)   ──syscall──>  SYN · 3-way handshake
  sendall(bytes)   ──syscall──>  enqueue socket tx buffer
  recv(1024)       ──syscall──>  copy from socket rx buffer
  close (with)     ──syscall──>  FIN · release sock fd
개념 설명
소켓 버퍼 커널이 유지 — recv는 버퍼에서 복사, send는 버퍼에 적재
블로킹 기본 recv/accept — 데이터 올 때까지 프로세스 대기 (OS 스케줄링)
파일 디스크립터 소켓도 fdselect/poll·이벤트 루프의 대상
멀티 프로세스 요청마다 fork/스레드 vs 비동기 이벤트 (고급)
  • OS 시리즈 시스템 콜·유저/커널 모드와 동일 경계
  • strace -e trace=network python3 code/echo_client.py — 실제 syscall 이름 확인 (Linux)

6. 자주 나는 오류

증상 원인 대응
Address already in use 포트 이미 bind 서버 종료·SO_REUSEADDR·다른 PORT
Connection refused listen 없음·방화벽·잘못된 PORT 서버 기동·ss -tlnp
recv hang 서버가 응답 안 함·블로킹 프로토콜·버그·타임아웃 settimeout
recvb"" 정상 종료 루프 break
한글 깨짐 bytes vs str encode()/decode()·UTF-8
# 리스닝 확인
ss -tlnp | grep 9000
# macOS
lsof -iTCP:9000 -sTCP:LISTEN

7. 정리

  • TCP echobind/listen/accept vs connect역할 분리
  • accept 소켓listen 소켓 — 연결마다 새 fd
  • recv 빈 bytes — peer close 신호
  • 소켓 = 시스템 콜 → 커널 TCP·버퍼
  • HTTP·TLS는 이 위에 프로토콜 레이어

다음에 다룰 것

  • 네트워크 트러블슈팅
  • 연결 실패 체크리스트, DNS·TLS·방화벽

해당 내용은 Computer Networking: A Top-Down Approach, 8/E (James Kurose, Keith Ross) 의 내용을 기반으로 합니다.

'네트워크 기초' 카테고리의 다른 글

Network Series를 마치며  (0) 2026.07.08
네트워크 트러블슈팅  (0) 2026.07.07
Wireshark로 HTTP/TCP 보기  (0) 2026.07.05
물리 계층과 매체  (0) 2026.07.04
이더넷과 MAC  (0) 2026.07.03