여기는 에코! 언제나 배울 준비가 돼있어요.

전두엽힘주기·2025년 5월 4일

Computer System

목록 보기
12/13
post-thumbnail

Overview of network applications based on the sockets interface.

에코 서버는 클라이언트가 보낸 메시지를 그대로 다시 돌려주는 단순한 서버
클라이언트는 사용자의 입력을 서버에 전송하고, 서버는 그 입력을 다시 돌려주며, 클라이언트는 이를 화면에 출력


실행 방법

터미널 1 (서버 실행)

./echoserveri 8000

터미널 2 (클라이언트 실행)

./echoclient localhost 8000

에코 서버 모델의 특징

  • 클라이언트가 전송한 만큼의 바이트를 그대로 돌려줌
  • 예측 가능한 송수신 구조 (e.g., "Hi\n" → 3 bytes)
  • 단방향 스트림이 아닌 양방향 스트림 기반 통신
  • TCP 기반 연결: 신뢰성 보장 (순서 보장, 손실 없음)
  • blocking I/O 모델: 읽기/쓰기 중 이벤트 발생까지 대기

클라이언트 ↔ 서버 상호작용 요약

  1. 클라이언트 실행 후 서버에 연결 요청
  2. 서버가 연결 수락 (accept)
  3. 클라이언트 입력 받음 (Fgets)
  4. 서버에 전송 (Rio_writen)
  5. 서버가 읽음 (Rio_readlineb)
  6. 다시 클라이언트로 전송 (Rio_writen)
  7. 클라이언트가 응답을 읽고 출력 (Rio_readlineb, Fputs)
  8. Ctrl+D 입력 시 연결 종료

데이터 송수신 흐름 요약

[클라이언트]
입력 "Hi" → "Hi\n"
↓ Rio_writen
서버로 전송

[서버]
Rio_readlineb → "Hi\n" 수신
↓ Rio_writen
클라이언트로 재전송

[클라이언트]
Rio_readlineb → "Hi\n" 수신
Fputs → 화면 출력

전체 흐름 시각화

1. 서버 시작

[서버]
  Open_listenfd(port)
     ↓
  listenfd 소켓 생성 및 리슨 상태
     ↓
  Accept(listenfd)
     ↓
  [BLOCKED] (클라이언트 연결 대기)

2. 클라이언트 시작

[클라이언트]
  Open_clientfd("localhost", port)
     ↓
  connect() → 커널에 연결 요청
     ↓
[커널]
  TCP 3-way handshake 진행
  연결 완료 이벤트 → 서버 & 클라이언트 깨움

3. 연결 수립 완료

[서버]
  Accept() 반환 → connfd 생성
  echo(connfd) 호출
     ↓
  Rio_readinitb → 읽기 준비
  Rio_readlineb → [BLOCKED] (클라이언트 데이터 대기)

[클라이언트]
  Rio_readinitb → 읽기 준비
  Fgets(stdin) → [BLOCKED] (사용자 입력 대기)

4. 데이터 전송 ("Hi\n")

[사용자]
  입력: "Hi" + Enter

[클라이언트]
  Fgets → "Hi\n" 획득
  Rio_writen(clientfd, "Hi\n") 전송
  Rio_readlineb → [BLOCKED] (서버 응답 대기)

[커널]
  데이터 → 서버 connfd로 전달

[서버]
  Rio_readlineb → "Hi\n" 수신
  printf("received 3 bytes")
  Rio_writen(connfd, "Hi\n")

[커널]
  데이터 → 클라이언트로 전달

[클라이언트]
  Rio_readlineb → "Hi\n" 수신
  Fputs → 화면 출력
  Fgets(stdin) → 다음 입력 대기

5. 연결 종료 (Ctrl+D)

[사용자]
  입력: Ctrl+D (EOF)

[클라이언트]
  Fgets → NULL 반환
  Close(clientfd) → FIN 전송
  exit(0)

[커널]
  FIN → 서버로 전달

[서버]
  Rio_readlineb → 0 반환 (EOF)
  echo() 종료
  Close(connfd)
  다시 Accept(listenfd)

주요 개념 정리

개념설명
listenfd서버가 클라이언트 연결을 기다리는 소켓
connfd연결이 수락된 후, 데이터를 주고받는 전용 소켓
Rio_readlineb줄 단위로 읽고, 도중에 입력이 없으면 BLOCKED
Rio_writen전체 버퍼를 정확히 다 보냄 (부분 쓰기 해결)
Blocked 상태데이터가 오거나 입력이 들어올 때까지 멈춰있는 상태
EOF클라이언트가 Ctrl+D로 종료 의사 표시할 때 발생

참고 코드

echoclient.c

#include "csapp.h"

int main(int argc, char **argv){
    int clientfd;
    char *host, *port, buf[MAXLINE];
    rio_t rio;

    if (argc!=3){
        fprintf(stderr, "usage: %s <host> <port> \n", argv[0]);
        exit(0);
    }

    host=argv[1];
    port=argv[2];

    // open_clientfd : client (getaddrinfo -> socket -> connect)
    clientfd = Open_clientfd(host, port);
    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);
}

echoserveri.c

#include "csapp.h"
void echo(int connfd);

int main(int argc, char **argv){
    int listenfd, connfd;
    socklen_t clientlen;
    struct sockaddr_storage clientaddr;
    char client_hostname[MAXLINE];
    char client_port[MAXLINE];

    if (argc != 2){
        fprintf(stderr, "usage: %s <port>\n", argv[0]);
        exit(0);
    }

    // Open_listenfd : server (getaddrinfo -> socket -> bind -> listen)
    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(connfd);
        Close(connfd);
    }

    exit(0);
}

echo.c

#include "csapp.h"

// 연결된 클라이언트 소켓을 받아서 클라이언트와 대화할 함수 정의
void echo(int connfd){
    size_t n;
    char buf[MAXLINE];
    rio_t 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);
    }
}

정리

  • 에코 서버는 TCP 연결을 통해 데이터를 받아 그대로 돌려줌
  • Robust I/O (rio)를 사용해 부분 읽기/쓰기 문제 방지
  • 소켓은 연결된 전화선, listenfd는 기다리는 전화기
  • Blocking I/O 모델로 작동 → 클라이언트/서버 모두 대기 기반 구조

2개의 댓글

comment-user-thumbnail
2025년 5월 4일

유용해요!

답글 달기
comment-user-thumbnail
2025년 5월 5일

에코 개웃기네 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

답글 달기