모든 것이 파일인 유닉스 시스템(from3)에서 read() 혹은 write() 작업을 수행할 때(from1) EoF 이라는 개념이 있다는 사실에 주의해야 한다. 이것이 중요한 이유가 무엇일까? read(fd, buffer, buffer_size) 는 기본적으로 블락(Block)모드로 실행된다. 즉, 파일 디스크립터를 통해 버퍼에 새롭게 기록되는 정보가 없다면 read() 함수는 실행을 마치지 않고 계속 대기한다(from4:파일과 파일 디스크립터의 관계). 그런데 이때 만약 EoF(End of File)이라는 것의 존재를 모른다면, 도대체 언제 프로그램의 실행이 멈추는 것인지, 도대체 언제 프로그램이 종료되는 것인지 갈피를 잡기 어렵게 된다. 이는 특히 소켓 파일과 일반 파일을 비교해볼 때 문제가 두드러진다.

<aside> 💡 이 글에 작성된 코드는 이해를 돕기 위해 에러 핸들링 등이 모두 제거되어 있다.

</aside>

우선 소켓 파일의 상황을 살펴보자. 나는 아래와 같은 echo 함수를 정의했다. fd_inread() 를 통해 읽어들일 파일 디스크립터, fd_outwrite() (아래 코드에서는 write() 의 내부적인 문제를 해결한 커스텀 함수인 rewrite()) 을 통해 쓸 파일 디스크립터다. read() 함수가 리턴하는 r 은 파일 디스크립터를 통해 읽어들인 바이트의 크기로, 이 값은 항상 BUFFER_SIZE 보다 작다. 파일 디스크립터에서 읽어들여야 하는 값이 BUFFER_SIZE 보다 크면, while 문을 반복하며 파일 디스크립터의 내용을 모두 읽어 나간다.

void echo(int fd_in, int fd_out)
{
    char buffer[BUFFER_SIZE];
    size_t r = 1;
    do {
        r = read(fd_in, buffer, BUFFER_SIZE);
        rewrite(fd_out, buffer, r);
    }
    while(r > 0);
}

그리고 아래는 localhost 2048번 포트에서 열리는 TCP서버의 소스코드이다(from2).

int sfd = get_binded_socket(result);
printf("Waiting for connections...\\n");
int cfd = accept(sfd, NULL, NULL);
printf("Connection successful!\\n");
echo(cfd, STDOUT_FILENO);

이 서버 프로그램을 실행하면 아래와 같은 내용을 출력한다.

$ tcp_server
Waiting for connections...

클라이언트 역할을 수행할 다른 터미널을 열고 nc 명령어를 이용해 서버 컴퓨터에 접속하면,

$ nc localhost 2048

서버 프로그램은 아래와 같이 연결에 성공했음을 출력한 뒤 echo() 함수의 read() 함수의 실행을 마치지 않고 대기한다.

$ tcp_server
Waiting for connections...
Connection successful!

그리고 클라이언트 터미널을 이용해 서버에 데이터를 전송하면

$ nc localhost 2048
hello world!

서버 컴퓨터의 표준 출력에 전송된 데이터가 출력된다.

$ tcp_server
Waiting for connections...
Connection successful!
hello world!

그리고 서버는 또다시 클라이언트가 서버에 데이터를 전송할 때까지 read() 함수의 실행을 마치지 않고 대기한다. read() 함수의 실행을 마치지 않고 대기, 즉 Block 된 상태라는 점을 잊지 말자(참고1).

이번에는 파일 입출력의 상황을 살펴본다. 앞선 상황과 마찬가지로 입출력을 위해 echo() 함수를 사용했다. 이 프로그램은 명령행 인자를 통해 전달된 이름의 파일을 열고, 해당 파일의 파일 디스크립터를 이용해 파일을 읽어들인 뒤 표준 출력으로 내보낸다.

int fd = open(argv[1], O_RDONLY);
echo(fd, STDOUT_FILENO);

temp.txt 파일은 단순히 다음과 같은 내용을 담고 있다.