[WebProxy] echo 서버 구현 실습 및 코드 해석하기

2025. 5. 2. 15:58·크래프톤 정글/Code 정글(C언어)

echo 서버 구현 실습하기

소켓 프로그래밍의 기본 개념을 이해할 수 있었다면, 다음 목표는 echo 서버를 직접 구현해보는 것인데요. echo 서버는 서버-클라이언트 간 연결이 맺어진 후, 클라이언트의 입력을 받아 그대로 다시 클라이언트에게 전송해주는 구조입니다. 언뜻 보기엔 간단해 보이지만, 실제로는 연결과 통신 과정에서 복잡한 처리 로직이 필요하죠.

다만 기존 webproxy_lab에서는 echo 서버에 대한 별도 실습을 제공하지 않기 때문에, 직접 echo 서버 실습을 진행한 후 이를 실습용 폴더로 구성하여 더 편리하게 실습해 볼 수 있도록 공유해드리려고 합니다.

 

 

파일 다운로드 및 구성 파일 소개

우선 아래의 zip 파일을 다운로드해주세요.

echo.zip
0.01MB

 

그 다음, Docker 환경에서 동일하게 실행할 수 있도록 webproxy-lab 디렉터리 안에 압축을 해제하면 됩니다.

 

구성 파일 소개

  • echo_server.c : 서버 코드 템플릿 (main, open_listenfd, echo 직접 구현)
  • echo_client.c : 클라이언트 코드 템플릿 (main, open_clientfd 직접 구현)
  • csapp.c / csapp.h : 실습용 함수 모음 (기존의 open_clientfd, open_listenfd 등은 주석처리됨)
  • Makefile : make로 빌드 가능 (echo_server, echo_client 생성)

 

실습 방법

  1. CSAPP 11.4를 참고해 echo_server.c와 echo_client.c의 빈 함수들을 모두 구현한다.
  2. echo 디렉터리에서 make 명령어를 실행해 서버, 클라이언트 각각의 실행파일을 만든다.
  3. 터미널을 2개 연다. (서버용 / 클라이언트용)
  4. 터미널1에서 서버를 실행한다
    • ./echo_server 12345
  5. 터미널2에서 클라이언트를 실행한다
    • ./echo_client localhost 12345
  6. 이후 터미널2에서 메시지를 입력해서 동일한 문자가 출력되는지 확인한다.
    • 윈도우 기준, 종료 시 Ctrl + D (클라이언트) / Ctrl + C (서버)

 

구현 코드 해석하기

open_clientfd 함수

주어진 호스트 이름과 포트 번호를 이용해 서버에 TCP 연결을 시도하고, 연결에 성공한 소켓 디스크립터를 반환합니다.

int open_clientfd(char *hostname, char *port) {
    int clientfd;                        // 클라이언트 측 소켓 파일 디스크립터 (연결 성공 시 반환됨)
    struct addrinfo hints, *listp, *p;   // 주소 정보를 위한 구조체들

    // 'hints' 구조체를 0으로 초기화 (초기값 설정)
    memset(&hints, 0, sizeof(struct addrinfo));

    // 연결 지향형 TCP 스트림을 원한다는 것을 명시
    hints.ai_socktype = SOCK_STREAM;

    // 포트 번호가 숫자임을 명시 (예: "80"은 숫자이므로 DNS 조회 필요 없음)
    hints.ai_flags = AI_NUMERICSERV;

    // 현재 시스템의 네트워크 환경에 맞는 주소만 조회
    hints.ai_flags |= AI_ADDRCONFIG;

    // hostname과 port에 해당하는 주소 정보를 가져옴
    // listp는 여러 개의 주소 정보를 가리키는 연결 리스트의 시작점이 됨
    Getaddrinfo(hostname, port, &hints, &listp);

    // 주소 리스트를 돌면서 접속 시도
    for (p = listp; p != NULL; p = p->ai_next) {

        // 현재 주소 정보에 맞는 소켓 생성 시도
        if ((clientfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
            continue;   // 소켓 생성 실패 → 다음 주소로 넘어감
        }

        // 소켓을 통해 서버에 연결 시도
        if (connect(clientfd, p->ai_addr, p->ai_addrlen) != -1) {
            break;      // 연결 성공 → 루프 탈출
        }

        // 연결 실패 → 소켓을 닫고 다음 주소로 이동
        Close(clientfd);
    }

    // 주소 정보 해제 (메모리 반환)
    Freeaddrinfo(listp);

    // 연결이 실패한 경우(p == NULL)
    if (p == NULL) {
        return -1;  // 모든 연결 실패
    }

    // 연결 성공한 경우: 해당 소켓 파일 디스크립터 반환
    return clientfd;
}

 

open_listenfd 함수

주어진 포트 번호로 클라이언트 연결을 수신할 수 있는 TCP 서버 소켓을 생성 및 바인딩한 뒤, 대기 상태로 설정된 소켓 디스크립터를 반환합니다.

int open_listenfd(char *port) {
    int listenfd, optval = 1;    // 서버 소켓 디스크립터, 소켓 옵션 설정용 변수
    struct addrinfo hints, *listp, *p;  // 주소 정보 힌트와 결과 리스트 포인터들

    // hints 초기화: 어떤 종류의 주소 정보를 원하는지 설정
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;     // TCP(연결 지향형 스트림)
    hints.ai_flags = AI_NUMERICSERV;     // 포트가 숫자 문자열이라는 의미 ("80" 등)
    hints.ai_flags |= AI_ADDRCONFIG;     // 현재 시스템의 IPv4/IPv6 환경을 고려
    // NULL을 넘겨서 "모든 IP 주소"에 바인딩 (서버의 모든 네트워크 인터페이스에서 접속 허용)
    Getaddrinfo(NULL, port, &hints, &listp);

    // 주소 리스트를 하나씩 돌면서 소켓 생성 + 바인딩 시도
    for (p = listp; p != NULL; p = p->ai_next) {

        // 소켓 생성
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
            continue;  // 실패 시 다음 주소 시도
        }

        // 포트 재사용 설정 (서버 재시작 시 "Address already in use" 방지)
        Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&optval, sizeof(int));

        // 바인딩 시도 (서버 소켓에 IP/포트 연결)
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0) {
            break; // 바인딩 성공
        }

        // 바인딩 실패 → 소켓 닫고 다음 주소 시도
        Close(listenfd);
    }

    // 주소 리스트 메모리 해제
    Freeaddrinfo(listp);

    // 모든 바인딩 시도 실패
    if (p == NULL) {
        return -1;
    }

    // 클라이언트의 연결 요청을 대기 상태로 설정
    if (listen(listenfd, LISTENQ) < 0) {
        // 실패 시 소켓 닫고 -1 반환
        Close(listenfd);
        return -1;
    }

    // 성공 시, 클라이언트 연결을 받을 수 있는 서버 소켓 디스크립터 반환
    return listenfd;
}

 

main 함수(echo_client)

사용자가 입력한 문자열을 서버에 전송하고, 그 응답을 받아 출력하는 TCP 클라이언트 프로그램의 기본 구조를 가지고 있습니다.

int main(int argc, char **argv) {
    int clientfd;                         // 서버와 연결된 소켓 파일 디스크립터
    char *host, *port, buf[MAXLINE];      // 서버 주소, 포트 번호, 메시지 저장 버퍼
    rio_t rio;                            // Robust I/O를 위한 버퍼 구조체 (CSAPP 제공)

    // 인자 개수 확인: 실행 시 반드시 <host> <port> 두 인자를 받아야 함
    if (argc != 3) {
        fprintf(stderr, "usage: %s <host> <port>\n", argv[0]);
        exit(0);  // 인자 부족 시 프로그램 종료
    }

    // 명령행 인자로부터 서버 주소와 포트 번호 가져오기
    host = argv[1];     // 예: "localhost" 또는 "127.0.0.1"
    port = argv[2];     // 예: "8080"

    // 클라이언트 소켓을 열고 서버에 연결
    clientfd = open_clientfd(host, port);

    // Robust I/O(안정적인 입출력)를 위한 rio 버퍼 초기화
    Rio_readinitb(&rio, clientfd);

    // 사용자로부터 입력을 받아 서버에 전송하고, 서버 응답을 받아 출력하는 반복문
    while (Fgets(buf, MAXLINE, stdin) != NULL) {
        // 입력한 문자열을 서버로 전송
        Rio_writen(clientfd, buf, strlen(buf));

        // 서버로부터 한 줄을 읽음 (버퍼를 사용해 안정적이고 효율적으로 처리)
        Rio_readlineb(&rio, buf, MAXLINE);

        // 서버로부터 받은 응답을 화면에 출력
        Fputs(buf, stdout);
    }

    // 통신이 끝나면 소켓 닫고 종료
    Close(clientfd);
    exit(0);
}

 

main 함수(echo_server)

실행 시 주어진 포트로 TCP 서버를 열고 클라이언트의 연결을 무한히 수락하게 되는데요. 이후 연결된 클라이언트와 데이터를 주고받는 echo 서비스를 수행하는 간단한 서버 프로그램입니다.

int main(int argc, char **argv) {
    int listenfd, connfd;   // listenfd: 클라이언트 연결 요청을 수신하는 소켓
                            // connfd: 연결이 수락된 후 실제 데이터 통신에 사용되는 소켓
    socklen_t clientlen;    // 클라이언트 주소 구조체의 크기 (accept 시 필요)
    struct sockaddr_storage clientaddr; // 클라이언트 주소 정보를 저장 (IPv4/IPv6 모두 대응)
    char client_hostname[MAXLINE], client_port[MAXLINE]; // 연결된 클라이언트의 호스트 이름과 포트 번호 저장

    // 명령행 인자 확인: 포트 번호 하나만 입력받아야 함
    if (argc != 2) {
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }

    // 주어진 포트로 수신용 서버 소켓 생성
    listenfd = open_listenfd(argv[1]);

    // 클라이언트 연결을 무한 반복으로 기다림 (서버는 보통 계속 실행됨)
    while (1) {
        clientlen = sizeof(struct sockaddr_storage);  // 주소 구조체 크기 초기화

        // 클라이언트의 연결 요청 수락 → 새로운 통신용 소켓 생성
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);

        // 클라이언트의 주소 정보를 사람이 읽을 수 있는 문자열로 변환
        Getnameinfo((SA *)&clientaddr, clientlen,
                    client_hostname, MAXLINE,
                    client_port, MAXLINE, 0);

        // 연결된 클라이언트 정보 출력
        printf("Connected to (%s, %s)\n", client_hostname, client_port);

        // echo 함수 호출: 클라이언트와 데이터 송수신 처리 (클라이언트가 보낸 데이터를 그대로 돌려줌)
        echo(connfd);

        // 통신 종료 후 소켓 닫기
        Close(connfd);
    }

    // 정상 종료 (사실상 도달하지 않음)
    exit(0);
}

 

echo 함수

클라이언트로부터 한 줄씩 입력을 받아 그대로 다시 돌려주는 간단한 에코 기능을 수행하며, Robust I/O를 이용해 안정적인 데이터 입출력을 보장합니다.

void echo(int connfd) {
    size_t n;             // 읽어온 바이트 수
    char buf[MAXLINE];    // 데이터를 읽고 쓸 버퍼
    rio_t rio;            // Robust I/O 구조체 (CSAPP에서 제공)

    // Robust I/O 입력 버퍼 초기화 (connfd를 기반으로 rio 설정)
    Rio_readinitb(&rio, connfd);

    // 클라이언트로부터 한 줄씩 데이터를 읽어서 그대로 다시 돌려줌
    while ((n = Rio_readlineb(&rio, buf, MAXLINE)) != 0) {
        // 읽은 바이트 수 출력
        printf("server received %d bytes\n", (int)n);

        // 읽은 데이터를 클라이언트에게 다시 전송 (에코)
        Rio_writen(connfd, buf, n);
    }

    // 클라이언트가 EOF(연결 종료)를 보내면 반복문 탈출
}
저작자표시 비영리 변경금지 (새창열림)

'크래프톤 정글 > Code 정글(C언어)' 카테고리의 다른 글

[WebProxy] Tiny 서버 숙제 문제 분석 및 해결하기  (0) 2025.05.03
[WebProxy] Tiny 서버 구현 코드 해석 및 응답 확인하기  (0) 2025.05.03
[WebProxy] VSCode에서 빨간 줄이 뜨는 현상 해결하기  (0) 2025.05.02
[Malloc] (참고용) Segregated Free List 기반 동적 할당기 구현하기  (0) 2025.04.28
[Malloc] Explicit Free List 기반 동적 할당기 구현하기 + 최적화하기  (0) 2025.04.27
'크래프톤 정글/Code 정글(C언어)' 카테고리의 다른 글
  • [WebProxy] Tiny 서버 숙제 문제 분석 및 해결하기
  • [WebProxy] Tiny 서버 구현 코드 해석 및 응답 확인하기
  • [WebProxy] VSCode에서 빨간 줄이 뜨는 현상 해결하기
  • [Malloc] (참고용) Segregated Free List 기반 동적 할당기 구현하기
그냥사람_
그냥사람_
IT 관련 포스팅을 합니다. 크래프톤 정글 8기 정경호
  • 그냥사람_
    그냥코딩
    그냥사람_
  • 전체
    오늘
    어제
    • 글 전체보기 N
      • 크래프톤 정글 N
        • 로드 투 정글(입학시험)
        • CS기초(키워드, 개념정리) N
        • 컴퓨터구조(CSAPP)
        • Code 정글(C언어) N
        • 마이 정글(WIL, 에세이) N
      • 자료구조&알고리즘
        • 자료구조
        • 알고리즘
      • 일상
  • 블로그 메뉴

    • 홈
  • 링크

    • Github
  • hELLO· Designed By정상우.v4.10.3
그냥사람_
[WebProxy] echo 서버 구현 실습 및 코드 해석하기
상단으로

티스토리툴바