소켓 프로그래밍 — TCP/UDP 소켓의 라이프사이클
소켓은 네트워크 통신의 끝점(endpoint)입니다. 서버가 클라이언트 요청을 받고 응답하는 전체 과정이 소켓 API 호출로 이루어집니다.
TCP 소켓 라이프사이클
서버 클라이언트
socket() → 소켓 생성 socket() → 소켓 생성
bind() → IP:포트 바인딩
listen() → 연결 대기 상태
connect() → 3-way handshake
accept() → 연결 수락 (새 소켓 생성)
send()/recv() ←─ 데이터 교환 ─→ send()/recv()
close() → 연결 종료 close()
핵심 시스템 콜
// 서버
int server_fd = socket(AF_INET, SOCK_STREAM, 0); // TCP 소켓 생성
bind(server_fd, &addr, sizeof(addr)); // 주소 바인딩
listen(server_fd, BACKLOG); // 연결 대기
int client_fd = accept(server_fd, &client_addr, &len); // 연결 수락
recv(client_fd, buf, sizeof(buf), 0); // 데이터 수신
send(client_fd, response, len, 0); // 데이터 송신
close(client_fd); // 연결 종료
// 클라이언트
int sock = socket(AF_INET, SOCK_STREAM, 0);
connect(sock, &server_addr, sizeof(server_addr)); // 서버에 연결
send(sock, data, len, 0);
recv(sock, buf, sizeof(buf), 0);
close(sock);
listen()의 backlog
listen(fd, 128); // backlog = 128
SYN 큐 (반연결 큐): SYN 받고 SYN-ACK 보낸 상태의 연결들
Accept 큐 (완료 큐): 3-way handshake 완료된 연결들 (accept() 대기)
backlog가 가득 차면 새 연결 요청을 거부하거나 무시합니다.
UDP 소켓
서버 클라이언트
socket() → SOCK_DGRAM socket() → SOCK_DGRAM
bind() → IP:포트 바인딩
sendto() → 데이터 전송
recvfrom() → 데이터 수신
sendto() → 응답 전송
recvfrom() → 응답 수신
UDP는 connect/accept가 없습니다. 각 패킷이 독립적이며, 목적지 주소를 매번 지정합니다.
SO_REUSEADDR과 SO_REUSEPORT
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
// → TIME_WAIT 상태의 포트를 재사용 (서버 재시작 시 유용)
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
// → 여러 프로세스가 같은 포트를 listen (로드 밸런싱)
핵심 포인트
- accept()이 새 소켓을 반환하는 이유: listen 소켓은 계속 연결을 받아야 하므로, 각 클라이언트마다 별도 소켓 생성
- TIME_WAIT와 SO_REUSEADDR: 서버 재시작 시 "Address already in use" 에러 방지
- Non-blocking 소켓: fcntl(fd, F_SETFL, O_NONBLOCK)으로 설정, epoll과 함께 사용
정리
소켓 프로그래밍은 네트워크의 가장 기본적인 API입니다. TCP 서버의 socket-bind-listen-accept 흐름과, 각 시스템 콜의 역할을 이해하면 웹 서버, 게임 서버 등 모든 네트워크 프로그래밍의 기반을 갖출 수 있습니다.