Rio 함수 ( Robust I/O ) [ 크래프톤 정글 58일차 ]

jinsung·2025년 7월 9일

크래프톤 정글 9기

목록 보기
50/59
post-thumbnail



이게 진짜 핀토스 "준비" 하는거라고요??

거짓말하지마!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

나 지금 장난칠 기분 아니야!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

지금까진 어떻게든 이해는 갔다..

근데 이제부터는 진짜 "이해"가 안감 "이해"가 안간다고요..

✅ [ WEEK08 ] 탐험준비 - 웹서버 만들기


WEEK 진행률

🎯 1. 키워드 공부

  • ☑️ 네트워크 계층 (OSI7 Layer, TCP/IP Layer)
  • ☑️ 클라이언트-서버 모델
  • ☑️ 소켓(socket, bind, listen, accept, connect, close)
  • ☑️ 파일 디스크립터
  • ☑️ Datagram Socket vs Stream Socket
  • ☑️ CGI / WebServer / MIME Type
  • ☑️ HTTP (요청/응답, 헤더, 메소드, 상태코드, HEAD 메소드)
  • ☑️ Proxy

🎯 2. 테스트 케이스
echo

  • ☑️ open_clientfd()
  • ☑️ 클라이언트 main()
  • ☑️ 서버 main()
  • ☑️ 서버 echo()
  • ☑️ telnet 접속 > 입력내용 > 입력내용을 응답으로 출력
  • ☑️ open_listenfd()

tiny

  • ☑️ main()
  • ☑️ doit()
  • ☑️ read_requesthdrs()
  • ☑️ parse_uri()
  • ☑️ serve_static()
  • ☑️ serve_dynamic()
  • ☑️ get_filetype()
  • ☑️ clienterror()
  • ☑️ cgi adder 구현
  • ☑️ 숙제 11.6c (930p - csapp)
  • ☑️ 숙제 11.7
  • ☑️ 숙제 11.9
  • ☑️ 숙제 11.10
  • ☑️ 숙제 11.11
  • ☑️ telnet으로 tiny의 http 버전 정보 확인
  • ☑️ 브라우저에서 home.html 요청/응답 확인
  • ☑️ telnet 접속 > 입력내용 > 입력내용을 응답으로 출력
  • ☑️ 브라우저에서 cgi-bin/adder?123&456 요청/응답 확인 (숙제 11.10을 안하고 adder 구현한 경우)
  • ☑️ 숙제 11.10을 했다면 결과 확인

proxy

  • ❗️ 웹 프록시 (순차적 처리)
  • ❗️ 동시 병렬 처리
  • ❗️ 캐싱 웹 오브젝트
  • ❗️ PC 네트워크 설정 > 프록시 설정
  • ❗️ 로컬 > 프록시 > tiny home.html 요청 (성공)
  • ❗️ 원격 프록시 서버 중지 후, 로컬 > 프록시 > tiny home.html 요청 (실패)
  • ❗️ 프록시 TC수행점수

🎯 3. 책 읽기

  • ☑️ 8장: 예외적인 제어흐름 (특히 8.1, 8.5)
  • ☑️ 10장: 시스템 수준 입출력 (특히 10.5)
  • ☑️ 11장: 네트워크 프로그래밍
  • ❗️ 12장: 동시성 프로그래밍

진행률 : 30% ( 이해를 못하겠다.. )

Rio 함수 ( Robust I/O )

Rio 함수는 시스템 수준의 read write를 좀 더 안전하게 하기 위한 래퍼(창모아님) 함수입니다.

최대한 이해하기 쉽게 단순화해서

Rio 함수는 어떤 파일에 읽고 쓰는 함수다! 라고 해볼게요.

우리가 구한 어떤 파일 디스크립터 fd 가 64Byte 짜리 File의 위치를 가리키고 있어요.
File 에는 64바이트 까지 다양한 값들로 채워져 있습니다.

저 File에 있는 정보를 읽고 써야해요. Rio 함수를 써서 안전하게요.

Rio 함수 종류

지금은 총 5가지의 Rio 함수가 있습니다.

  • Rio_readinitb파일을 읽기 위해 준비하는 함수 예요.
  • Rio_readnn 바이트 만큼 파일을 메모리에서 직접 읽는 함수예요.
  • Rio_readnbn 바이트 만큼 파일을 버퍼를 사용해서 읽는 함수예요.
  • Rio_readlineb파일을 "한줄씩" 버퍼를 사용해서 읽는 함수예요.
  • Rio_writenn 바이트 만큼 파일에 쓰는 함수예요.

!!!!!주목!!!!!

  • 뒤에 b가 붙은 친구들은 모두 버퍼를 사용하는 친구들이예요.ㅅ
  • 쓰기에 버퍼를 사용한 함수가 없는 점에 주목할 필요가 있어요.

쓰기 작업은 굳이 버퍼를 거칠 필요 없이 즉각적이고 단순하게 반영하기 때문에 버퍼가 없이 바로 "n 바이트를 파일에 써줘!!"하고 요청하는게 좋아요.

Rio_readinitbRio_readlineb 만 시각화를 하면서 살펴볼게요.
본인의 파일에 있는 코드와 비교하면서 입력값이 어떻게 다른지 확인해보는 것도 좋아요.

Rio_readinitb

Rio_readinitb읽기 위해 초기화하는 함수 라고 했죠?

void rio_readinitb(rio_t *rp, int fd) 
{
    rp->rio_fd = fd;  
    rp->rio_cnt = 0;  
    rp->rio_bufptr = rp->rio_buf;
}

void Rio_readinitb(rio_t *rp, int fd)
{
    rio_readinitb(rp, fd);
} 

rio_t RobustIO
Rio_readinitb(&RobustIO , fd);
// Rio_readinitb 함수는 인자로 rio_t 구조체의 주소와, 파일디스크립터 정보를 받습니다.

이렇게 사용했을 때 어떻게 변할까요?

정답을 말하자면: rio_t 타입의 RobustIO 구조체가 초기화 됩니다!

RobustIO 구조체의 값들 중 rio_fd 가 이제 fd로 변경되었기 때문에 파일에 접근할 수 있습니다.
그리고 따로 rio_buf 라는 데이터를 저장할 공간을 만들었어요.
뒤에 b가 붙은 함수들은 이렇게 만든 rio_buf공간을 계속 사용해 줄 겁니다.

Rio_readlineb

Rio_readlineb 함수의 정의를 모으면 이렇게 돼요.

static ssize_t rio_read(rio_t *RobustIo, char *usrbuf, size_t n)
{
    int cnt;

    while (RobustIo->rio_cnt <= 0) {  
    	/* 버퍼가 비어있으면 가득 채운다. */
	    /* 그리고 rio_cnt 를 read 의 return 값으로 채워준다 */
        /* read 의 return 값은 "실제 읽은 데이터의 값" 이다. */
        RobustIo->rio_cnt = read(rp->rio_fd, rp->rio_buf,  sizeof(rp->rio_buf));
       
        if (RobustIo->rio_cnt < 0) {
            if (errno != EINTR) /* Interrupted by sig handler return */
            return -1;
        } else if (RobustIo->rio_cnt == 0) {
            /* EOF */
            return 0;
        } else {
            RobustIo->rio_bufptr = RobustIo->rio_buf; /* Reset buffer ptr */
        } 
    }
    
   // 나머지 코드...
}

ssize_t rio_readlineb(rio_t *RobustIo, void *usrbuf, size_t maxlen) 
{
    int n, rc;
    char c, *bufp = usrbuf;

    for (n = 1; n < maxlen; n++) { 
    	/* rio_read 의 매개변수에 1 을 넣어줘서 한글자씩만 읽는다 */
        if ((rc = rio_read(rp, &c, 1)) == 1) { 
	    
            *bufp++ = c;
	    
        if (c == '\n') {
            n++;
     		break;
        }
	}
    // 나머지 코드..
}

ssize_t Rio_readlineb(rio_t *RobustIo, void *usrbuf, size_t maxlen) 
{
    ssize_t rc;

    if ((rc = rio_readlineb(RobustIo, usrbuf, maxlen)) < 0)
	unix_error("Rio_readlineb error");
    return rc;
} 

Rio_readlineb(&RobustIo, buf, MAXLINE);

이제 Rio_readinitb로 만들어준 RobustIO구조체를 활용해서 파일에 있는 값을 읽어볼게요.
구조체 안에 이미 fd정보가 들어있습니다.

Rio_readlineb 는

  • "만약 버퍼가 비어있다면 파일의 내용으로 우선 버퍼를 가득 채웁니다."
  • "그리고 RobustIO의 rio_cnt 값을 read시스템 콜에서 읽은 데이터 바이트로 변경해줍니다"

구조체의 rio_buf 가 file 의 문자열 데이터로 채워졌고, rio_cnt가 32로 채워졌습니다.
rio_cntrio_buf안에 읽지 않은 데이터가 32바이트다라는 뜻입니다.

Rio_readlineb(&RobustIo, buf, MAXLINE);

보시는 것처럼 Rio_readlineb&RobustIO 주소와 buf를 받고있는데요.
&RobustIO주소에서 buf데이터를 한 줄만 읽어서 이동시켜요. 한 줄이란 /0 을 만날 때 까지 입니다

  • rio_cnt가 18로 줄어들었어요. rio_buf에서 읽을 수 있는 바이트가 18바이트 남았단 뜻입니다.
  • rio_bufptr이 마지막으로 읽은 /0의 다음을 가리키고 있어요. 저 포인터를 기준으로 나머지 rio_buf를 읽습니다.

엥?? 진짜 쓰일곳이 뭐지?? 라고 생각하실 수 있는데

잠시 echoclient.c 의 코드를 살펴볼게요.

RobustIO 구조체의 내부에서 메모리에 여러번 접근하지 않기 위해 buf를 사용해 데이터를 저장하는 거지, 실제 사용은 외부의 buf 에서하고 Fputs 로 출력하는 것을 확인할 수 있습니다.

Fputs 또한 buf 내에서 null 종료 문자를 만날때까지만 출력합니다.
null 종료문자는 readlineb 에서 나오기 때문에 무조건 한 줄 씩만 출력되죠.

  • Rio_readlineb함수는 그 파일에서 알아서 버퍼링해서 2번째 매개변수로 값을 한줄만 넣어줍니다.

처음에는 c에서 값을 return 해서 넣어주지 않고
주소를 넣어서 주소의 값을 직접 바꾸는게 어색한데, 그것을 이해한다면 나머지 Rio 함수를 이해하기가 좀 더 쉬워집니다.

미안합니다. 모든 함수를 그리기엔 시간이 부족했습니다.

내가 멘탈을 잡는 방법

  1. 불안할 땐 증거 있어? 라고 묻기
  1. 나는 최고다. 나는 할 수 있다. 나는 핀토스를 부순다.

1개의 댓글

comment-user-thumbnail
2025년 7월 9일

팬입니다

답글 달기