개념
단순히 "강하다" 가 아니라
에러나 인터럽트(EINTR)에도 안전하게 동작하는 I/O라는 의미
일반 read(), write() 함수의 문제
1. 중간에 인터럽트 발생 (EINTR)
2. 일부만 읽힘 / 일부만 써짐 (partial read/write)
3. 다시 호출해야 함(직접 처리)
즉, 사용자가 직접 예외처리를 계속 해야 함
Robust I/O 함수의 특징
1. 자동 재시도 (retry)
2. 정확한 바이트 수 보장
3. EINTR 처리 포함
즉, 이렇기에 "robust" (견고한 I/O 라고 부름)
Robust I/O 함수는 "내부 버퍼를 직접 관리하느냐"를 기준으로 나눌 수 있음
cf. rio_readn은 표준 라이브러리가 아니라 CSAPP에서 제공하는 유틸 함수
즉, 내부 버퍼 없이 바로 read, write를 안정적으로 감싼 것 -> Unbuffered Robust I/O
내부 버퍼를 두고 읽기 효율과 편의성을 높인 것 -> Buffered Robust I/O
Unbuffered Robust I/O
내부 버퍼를 사용하지 않고, 시스템 콜 read, write를 감싸서 더 안전하게 사용할 수 있도록 만든 함수
대표적인 함수
rio_readn
rio_writen
- 목적
ⓐ. EINTR 같은 인터럽트 상황에 대해 재시도
ⓑ. 일부만 읽히거나 일부만 써지는 문제를 보완
ⓒ. 사용자가 원하는 바이트 수만큼 최대한 정확하게 처리
즉, 성능 최적화보단 안정적인 입출력 보장에 더 초점
Buffered Robust I/O
내부 버퍼를 직접 관리하는 구조체(rio_t)를 사용해서 입력을 더 효율적으로 처리하는 함수
대표적인 함수
rio_readinitb
rio_readnb
rio_readlineb
- 목적
ⓐ. 여러 번의 작은 read() 호출을 줄임
ⓑ. 한 번 크게 읽어 내부 버퍼에 저장한 뒤, 필요한 만큼 잘라서 전달
ⓒ. 텍스트 기반 프로토콜에서 줄 단위 입력 처리가 쉬움
즉, 안정성에 더해 성능과 편의성까지 고려한 입력 방식
rio_readn
rio_readn() 함수란?
파일 디스크립터로부터 "정확히 n바이트를 읽으려고 시도하는" Robust I/O 함수
Why Used?
기본 read()는 생각보다 불완전하다.
ssize_t read(int fd, void *buf, size_t n);
요청한 n바이트를 항상 다 읽어주지 않음
중간에 인터럽트(EINTR) 발생 가능
소켓에서는 특히 "조금씩 끊어서" 들어옴
즉, read(fd, buf, 100); 했는데 실제로는 20바이트만 읽히는 경우도 많음
함수 원형
ssize_t rio_readn(int fd, void *usrbuf, size_t n);
| 매개변수 | 설명 |
| fd | 소켓 파일 디스크립터 |
| usrbuf | 데이터를 저장할 버퍼 |
| n | 읽고 싶은 바이트 수 |
rio_readn이 해결하는 것
- 반복해서 read() 호출
- 누적해서 n바이트 채움
- EINTR 자동 처리
즉, 가능한 한 n바이트를 다 읽을 때까지 계속 시도
#include <unistd.h> // read()
#include <errno.h> // errno
ssize_t rio_readn(int fd, void *usrbuf, size_t n)
{
size_t nleft = n; // 아직 읽어야 할 바이트 수
ssize_t nread;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nread = read(fd, bufp, nleft)) < 0) {
if (errno == EINTR) // 인터럽트 발생
nread = 0; // 다시 시도
else
return -1; // 실제 에러
} else if (nread == 0) {
break; // EOF
}
nleft -= nread; // 남은 바이트 감소
bufp += nread; // 버퍼 포인터 이동
}
return (n - nleft); // 실제 읽은 바이트 수
}
사용 예시
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
ssize_t rio_readn(int fd, void *usrbuf, size_t n); // 선언
int main() {
int fd = open("test.txt", O_RDONLY);
char buf[100];
int n = rio_readn(fd, buf, 100);
if (n < 0) {
perror("rio_readn error");
return 1;
}
printf("읽은 바이트 수: %d\n", n);
close(fd);
return 0;
}
rio_writen
rio_writen() 함수란?
파일 디스크립터에 "정확히 n바이트를 쓰려고 시도하는" Robust I/O 함수
Why Used?
기본 write()도 read()처럼 문제가 있다.
ssize_t write(int fd, const void *buf, size_t n);
한 번에 n바이트를 다 못 쓸 수 있음
일부만 쓰고 끝날 수 있음 (partial write)
EINTR로 중단될 수 있음
즉, 1024바이트를 보내려고 했는데 -> 300 바이트만 전송될 수 있음
함수 원형
ssize_t rio_writen(int fd, void *usrbuf, size_t n);
| 매개변수 | 설명 |
| fd | 소켓 파일 디스크립터 |
| usrbuf | 데이터를 저장할 버퍼 |
| n | 읽고 싶은 바이트 수 |
rio_writen이 해결하는 것
- 반복해서 write() 호출
- 누적해서 n바이트 채움
- EINTR 자동 처리
즉, 가능한 한 n바이트를 다 읽을 때까지 계속 시도
#include <unistd.h>
#include <errno.h>
ssize_t rio_writen(int fd, void *usrbuf, size_t n)
{
size_t nleft = n; // 남은 바이트
ssize_t nwritten;
char *bufp = usrbuf;
while (nleft > 0) {
if ((nwritten = write(fd, bufp, nleft)) <= 0) {
if (errno == EINTR) // 인터럽트 발생
nwritten = 0; // 다시 시도
else
return -1; // 실제 에러
}
nleft -= nwritten; // 남은 바이트 감소
bufp += nwritten; // 포인터 이동
}
return n;
}
사용 예시
// 파일에 데이터 쓰기
int fd = open("test.txt", O_WRONLY | O_CREAT, 0644);
char msg[] = "Hello World";
rio_writen(fd, msg, sizeof(msg));
close(fd);
rio_readinitb
rio_readinitb() 함수란?
Buffered Robust I/O를 사용하기 위해 내부 버퍼(rio_t)를 초기화하는 함수
반드시 rio_readnb, rio_readlineb 쓰기 전에 반드시 먼저 호출해야 하는 초기화 함수
Why Used?
typedef struct {
int rio_fd; // 파일 디스크립터
int rio_cnt; // 버퍼에 남은 바이트 수
char *rio_bufptr; // 현재 읽을 위치
char rio_buf[8192];// 내부 버퍼
} rio_t;
실제로 Buffered RIO는 위와 같은 구조를 사용하므로
그냥 쓰면 안되고 초기 상태를 설정해줘야 함
함수 원형
void rio_readinitb(rio_t *rp, int fd);
| 매개변수 | 설명 |
| rp | rio_t 버퍼 구조체 |
| fd | 사용할 파일 디스크립터 |
void rio_readinitb(rio_t *rp, int fd)
{
rp->rio_fd = fd; // 사용할 파일 디스크립터
rp->rio_cnt = 0; // 버퍼에 남은 데이터 없음
rp->rio_bufptr = rp->rio_buf; // 버퍼 시작 위치
}
사용 예시
rio_t rio;
rio_readinitb(&rio, fd); // 1. 초기화
rio_readlineb(&rio, buf, MAXLINE); // 2. 사용
rio_readnb(&rio, buf, n);
rio_readnb
rio_readnb() 함수란?
내부 버퍼를 사용해서 "n바이트를 읽는" Buffered Robust I/O 함수
Why Used?
rio_readn은 "직접 read 반복"
rio_readnb는 "버퍼에 미리 읽어놓고 가져다 씀"
함수 원형
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n);
| 매개변수 | 설명 |
| rp | rio_t 버퍼 구조체 |
| usrbuf | 데이터를 저장할 버퍼 |
| n | 읽고 싶은 바이트 수 |
/**
* 1. 한 번 크게 읽고 재사용
* read 8192 bytes -> 여러 번 나눠서 사용
* 2. 내부 버퍼를 계속 유지
* 다음 호출에서도 남은 데이터 활용
* 3. 성능 최적화
* 시스템 콜은 비싸다 -> read 횟수를 줄이면 성능이 좋아진다.
*/
ssize_t rio_readnb(rio_t *rp, void *usrbuf, size_t n)
{
size_t nleft = n;
ssize_t nread;
char *bufp = usrbuf;
while (nleft > 0) {
if (rp->rio_cnt <= 0) { // 버퍼가 비어있으면
rp->rio_cnt = read(rp->rio_fd, rp->rio_buf, sizeof(rp->rio_buf));
if (rp->rio_cnt < 0) {
if (errno != EINTR)
return -1;
}
else if (rp->rio_cnt == 0) {
return 0; // EOF
}
else {
rp->rio_bufptr = rp->rio_buf;
}
}
int cnt = rp->rio_cnt < nleft ? rp->rio_cnt : nleft;
memcpy(bufp, rp->rio_bufptr, cnt);
rp->rio_bufptr += cnt;
rp->rio_cnt -= cnt;
nleft -= cnt;
bufp += cnt;
}
return n;
}
사용 예시
rio_t rio;
char buf[100];
rio_readinitb(&rio, fd);
rio_readnb(&rio, buf, 100); // 8192 byte를 읽어서 그 중 100 byte만 buf에 저장
rio_readlineb
rio_readlineb() 함수란?
내부 버퍼를 사용해서 "한 줄(\n)"을 읽는 Buffered Robust I/O 함수
Why Used?
"줄 단위 입력을 안전하고 효율적으로 처리하기 위해 사용"
함수 원형
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen);
| 매개변수 | 설명 |
| rp | rio_t 버퍼 구조체 |
| usrbuf | 데이터를 저장할 버퍼 |
| maxlen | 사용자가 제공한버퍼(usrbuf)에 "최대 몇 바이트까지 읽을 수 있는지" 제한하는 값 |
/**
* 1. 한 글자씩 읽는다.
* 내부 버퍼 사용 때문에 (이미 8192byte 읽은 상태) 성능이 괜찮다.
* 2. \n 만나면 종료
* 3. 문자열 끝 처리 *bufp = 0;
*/
ssize_t rio_readlineb(rio_t *rp, void *usrbuf, size_t maxlen)
{
int n, rc;
char c, *bufp = usrbuf;
for (n = 1; n < maxlen; n++) {
if ((rc = rio_readnb(rp, &c, 1)) == 1) {
*bufp++ = c;
if (c == '\n')
break;
} else if (rc == 0) {
if (n == 1)
return 0; // EOF
else
break;
} else {
return -1; // error
}
}
*bufp = 0;
return n;
}
사용 예시
/**
* HTTP 요청일 경우
* GET / HTTP/1.1\r\n
* \r\n
* header 내용들....
*
* 위와 같이 요청이 들어올 때, 한줄만 읽고 종료하는 것을 아래처럼 구현
* 결국 필요한 정보는 method uri version 이기 때문에
*/
rio_t rio;
char buf[1024];
rio_readinitb(&rio, connfd);
while (rio_readlineb(&rio, buf, 1024) > 0) {
printf("%s", buf);
if (strcmp(buf, "\r\n") == 0)
break; // 헤더 끝
}
결론
Robust I/O를 배우는 이유는 "네트워크 I/O의 불안정성을 해결하기 위해서"
그 과정에서 HTTP 요청 처리에 매우 잘 맞기 때문에 자연스럽게 사용되는 것
'정글캠프-WIL > 서브아이템' 카테고리의 다른 글
| Pintos - 어떻게 부팅해서 운영체제가 되는가? [2] (0) | 2026.05.01 |
|---|---|
| Pintos - 어떻게 부팅해서 운영체제가 되는가? [1] (0) | 2026.04.30 |
| 하네스 엔지니어링 - 이해의 폭을 넓히기 위해 (0) | 2026.04.10 |
| xterm.js 라이브러리란? (0) | 2026.04.09 |
| React의 React Flow 라이브러리란? (0) | 2026.04.09 |