소켓 통신에서 TCP와 UDP를 선택하는 기준

소켓 통신의 엔드포인트 구조와 TCP·UDP 처리 흐름, 서버와 클라이언트 구현 절차, 운영 시 고려할 사항을 정리한다.

2026-08-14 · 최초 발행 2025-08-10

프로그램 간 통신이 닿는 지점

소켓은 서버와 클라이언트 프로그램이 특정 포트를 통해 양방향으로 통신하도록 만드는 소프트웨어 엔드포인트다. 운영체제가 제공하는 API로 다루며, 복잡한 네트워크 통신을 개발자가 직접 처리하지 않도록 추상화한다.

통신 종단점은 IP 주소와 포트 번호의 조합으로 식별된다. 데이터 스트림을 읽고 쓰는 관점에서는 파일 디스크립터와 유사하게 동작한다.

전달 보장이 필요할 때의 TCP 소켓

TCP는 연결을 먼저 설정한 뒤 데이터를 교환하는 방식이다. 전송 순서를 보장하고, 패킷이 손실되면 재전송 메커니즘이 처리한다. 흐름 제어와 혼잡 제어도 TCP가 제공하는 특성이다.

서버는 socket(), bind(), listen(), accept() 순서로 연결을 준비한다. 클라이언트는 socket()을 만든 뒤 connect()로 서버에 연결한다. 연결이 성립한 뒤에는 send()recv()로 데이터를 주고받고, 작업이 끝나면 close()로 소켓을 닫는다.

ClientServerClientServersocket()bind()listen()socket()accept()Connection EstablishedData Exchangeconnect()send()recv()send()recv()close()close()

서버에서 socket()은 프로토콜 패밀리, 소켓 타입, 프로토콜을 지정해 소켓을 생성한다.

int server_socket = socket(AF_INET, SOCK_STREAM, 0);

bind()는 생성된 소켓에 IP 주소와 포트 정보를 연결한다.

bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

listen()은 클라이언트의 연결 요청을 받을 수 있도록 소켓을 대기 상태로 바꾸고, 연결 대기 큐의 크기를 지정한다.

listen(server_socket, 5);

accept()는 연결 요청을 수락하면서 클라이언트 통신에 사용할 새 소켓을 만든다.

client_socket = accept(server_socket, (struct sockaddr*)&client_addr, &addr_size);

이후 새 소켓에서 send()recv()로 데이터를 교환한다.

send(client_socket, message, strlen(message), 0);

recv(client_socket, buffer, sizeof(buffer), 0);

통신이 끝나면 close(client_socket);으로 리소스를 해제한다.

클라이언트도 동일하게 소켓을 생성한다.

int client_socket = socket(AF_INET, SOCK_STREAM, 0);

서버의 IP 주소와 포트 번호를 대상으로 연결을 요청한다.

connect(client_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

연결된 뒤에는 send()recv()로 서버와 데이터를 교환하고, close(client_socket);으로 연결을 종료한다.

데이터그램 단위로 주고받는 UDP 소켓

UDP는 연결 설정 없이 데이터그램을 전송한다. 신뢰성보다 속도와 효율성을 우선하며, 패킷 손실·중복·순서 변경이 발생할 수 있다. 실시간 스트리밍, 게임, DNS 쿼리처럼 이러한 특성을 감수할 수 있는 통신에 맞는다.

UDP에서는 socket(), bind(), recvfrom(), sendto(), close()를 주로 사용한다.

ClientServerClientServersocket()bind()socket()(optional) bind()Datagram Exchangeclose()close()sendto()recvfrom()sendto()recvfrom()

서버는 SOCK_DGRAM 타입으로 UDP 소켓을 만든다.

int server_socket = socket(AF_INET, SOCK_DGRAM, 0);

주소 정보는 다음과 같이 바인딩한다.

bind(server_socket, (struct sockaddr*)&server_addr, sizeof(server_addr));

recvfrom()은 클라이언트의 데이터를 받는 동시에 클라이언트 주소 정보를 얻는다.

recvfrom(server_socket, buffer, sizeof(buffer), 0, (struct sockaddr*)&client_addr, &addr_len);

응답은 클라이언트 주소를 포함해 sendto()로 보낸다.

sendto(server_socket, message, strlen(message), 0, (struct sockaddr*)&client_addr, addr_len);

서버 소켓은 close(server_socket);으로 종료한다.

클라이언트는 다음과 같이 UDP 소켓을 생성한다.

int client_socket = socket(AF_INET, SOCK_DGRAM, 0);

서버 주소와 함께 데이터를 전송한다.

sendto(client_socket, message, strlen(message), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

서버 응답은 recvfrom()으로 받는다.

recvfrom(client_socket, buffer, sizeof(buffer), 0, NULL, NULL);

완료 후에는 close(client_socket);으로 소켓 리소스를 해제한다.

TCP 에코 서버로 보는 호출 연결

아래 예제는 TCP 서버가 클라이언트 연결을 수락하고, 받은 데이터를 그대로 돌려주는 에코 서버다.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 소켓 생성
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 소켓에 주소 바인딩
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // 연결 대기 상태로 전환
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port %d\n", PORT);

    // 클라이언트 연결 수락
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("Accept failed");
        exit(EXIT_FAILURE);
    }

    printf("Client connected\n");

    // 데이터 수신 및 에코
    int valread = read(new_socket, buffer, BUFFER_SIZE);
    printf("Received: %s\n", buffer);
    send(new_socket, buffer, strlen(buffer), 0);
    printf("Echo message sent\n");

    // 소켓 종료
    close(new_socket);
    close(server_fd);

    return 0;
}

소켓이 쓰이는 통신 경로

HTTP 통신은 기본적으로 TCP 소켓 통신을 기반으로 구현된다. 실시간 채팅 애플리케이션은 메시지를 즉시 전달하기 위한 양방향 통신에 소켓을 사용하며, IoT 장치 제어에서는 스마트홈 기기와 중앙 제어 시스템 사이의 통신 경로가 된다.

데이터베이스 연결 풀은 클라이언트 애플리케이션과 데이터베이스 서버의 연결을 다룬다. 온라인 게임은 서버와 클라이언트의 실시간 데이터 교환에 활용하고, 금융 거래 시스템은 안전하고 신뢰성 있는 금융 데이터 전송에 사용한다.

구현 이후에 남는 운영 조건

전송 데이터는 SSL/TLS를 통해 암호화할 필요가 있다. 다중 클라이언트를 처리하려면 멀티스레딩 또는 비동기 I/O를 고려해야 하며, 연결과 데이터 전송의 타임아웃을 설정해 자원 낭비를 막아야 한다.

대용량 데이터를 다룰 때는 적절한 버퍼 크기가 필요하다. 네트워크 불안정성을 전제로 한 강건한 오류 처리도 빠질 수 없다. 비차단(non-blocking) I/O, 폴링, 멀티플렉싱은 성능 최적화에 활용할 수 있는 기법이다.

소켓 API를 둘러싼 구현 방식

Node.js와 Python asyncio는 이벤트 기반 비동기 소켓 처리에 활용된다. 웹 애플리케이션의 실시간 데이터 교환에는 HTTP 기반 양방향 통신 프로토콜인 WebSocket이 쓰인다.

마이크로서비스 환경에서는 서비스 간 통신을 위해 경량화된 소켓 기반 프로토콜을 활용한다. Docker와 Kubernetes 환경에서는 컨테이너 네트워킹 조건도 통신 설계에 포함된다. 구조화된 데이터의 직렬화와 역직렬화에는 Protocol Buffers를 활용할 수 있다.

신뢰성과 연결 지향성이 필요한 통신에는 TCP를, 속도와 효율성이 우선인 통신에는 UDP를 선택한다. 이 선택과 함께 서버·클라이언트의 호출 순서, 오류 처리, 보안 조건을 맞춰야 네트워크 애플리케이션의 통신 경로가 안정적으로 동작한다.

소켓통신TCPUDP네트워크 프로그래밍클라이언트 서버