Search
🌍

bc9.3__1.1_1. title: 모든 것이 파일인 유닉스 시스템에서 read() 혹은 write() 작업을 수행할 때 EoF 이라는 개념이 있다는 사실에 주의하라.

생성
🚀 prev note
♻️ prev note
bc9.3__2. title: 소켓은 애플리케이션 레이어와 전송 레이어의 인터페이스이다. 소켓을 생성하기 위해서는 TCP 또는 UDP 라는 통신방식을 정해 주어야 한다.
bb8.1_1__1. title: 유닉스(UNIX)와 유닉스 계열(‣)운영체제에서는 터미널에 출력하는 작업이나 디스크에 값을 쓰는 작업이나 모두 파일에 쓰는 작업으로 본다. 이 접근법과 그 장점은 1973년 톰프슨의 논문에서 찾을 수 있다.
bb8.1_1__2. title: 유닉스(UNIX) 시스템과 유닉스 계열(‣) 시스템에서는 1973년부터 파일에 접근할 때 파일 디스크립터(File descriptor)라는 추상적인 핸들을 이용한다. 이를 기준삼아 다양한 추상화 수준을 엿볼 수 있다. 우리에게 가장 익숙한 파일 포인터는 파일 디스크립터를 애플리케이션 프로그래머가 사용하기 쉽도록 추상화한 것이다. 파일 디스크립터는 각 프로세스에 의해 열린 파일들을 저장하는 구조체 배열인 파일 디스크립터 테이블을 추상화한다. 파일 디스크립터 테이블은 모든 프로세스에 의해 열린 파일들은 저장하는 파일 테이블(File table)을 추상화한다. 파일 테이블은 실제로 파일의 내용을 포함하고 있는 아이노드 테이블(Inode table)을 추상화한다.
🚀 next note
14 more properties
모든 것이 파일인 유닉스 시스템(from3)에서 read() 혹은 write() 작업을 수행할 때(from1) EoF 이라는 개념이 있다는 사실에 주의해야 한다. 이것이 중요한 이유가 무엇일까? read(fd, buffer, buffer_size) 는 기본적으로 블락(Block)모드로 실행된다. 즉, 파일 디스크립터를 통해 버퍼에 새롭게 기록되는 정보가 없다면 read() 함수는 실행을 마치지 않고 계속 대기한다(from4:파일과 파일 디스크립터의 관계). 그런데 이때 만약 EoF(End of File)이라는 것의 존재를 모른다면, 도대체 언제 프로그램의 실행이 멈추는 것인지, 도대체 언제 프로그램이 종료되는 것인지 갈피를 잡기 어렵게 된다. 이는 특히 소켓 파일과 일반 파일을 비교해볼 때 문제가 두드러진다.
이 글에 작성된 코드는 이해를 돕기 위해 에러 핸들링 등이 모두 제거되어 있다.
우선 소켓 파일의 상황을 살펴보자. 나는 아래와 같은 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); }
C
복사
그리고 아래는 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);
C
복사
tcp_server.c
이 서버 프로그램을 실행하면 아래와 같은 내용을 출력한다.
$ tcp_server Waiting for connections...
Plain Text
복사
서버
클라이언트 역할을 수행할 다른 터미널을 열고 nc 명령어를 이용해 서버 컴퓨터에 접속하면,
$ nc localhost 2048
Plain Text
복사
클라이언트
서버 프로그램은 아래와 같이 연결에 성공했음을 출력한 뒤 echo() 함수의 read() 함수의 실행을 마치지 않고 대기한다.
$ tcp_server Waiting for connections... Connection successful!
Plain Text
복사
서버
그리고 클라이언트 터미널을 이용해 서버에 데이터를 전송하면
$ nc localhost 2048 hello world!
Plain Text
복사
클라이언트
서버 컴퓨터의 표준 출력에 전송된 데이터가 출력된다.
$ tcp_server Waiting for connections... Connection successful! hello world!
Plain Text
복사
서버
그리고 서버는 또다시 클라이언트가 서버에 데이터를 전송할 때까지 read() 함수의 실행을 마치지 않고 대기한다. read() 함수의 실행을 마치지 않고 대기, 즉 Block 된 상태라는 점을 잊지 말자(참고1).
이번에는 파일 입출력의 상황을 살펴본다. 앞선 상황과 마찬가지로 입출력을 위해 echo() 함수를 사용했다. 이 프로그램은 명령행 인자를 통해 전달된 이름의 파일을 열고, 해당 파일의 파일 디스크립터를 이용해 파일을 읽어들인 뒤 표준 출력으로 내보낸다.
int fd = open(argv[1], O_RDONLY); echo(fd, STDOUT_FILENO);
C
복사
file_reader.c
temp.txt 파일은 단순히 다음과 같은 내용을 담고 있다.
hello world!
C
복사
temp.txt
아래와 같이 프로그램을 실행할 수 있다.
$ file_reader temp.txt hello world!
C
복사
그런데 이상하다. 이번에는 프로그램이 종료된다. 동일한 함수 echo() 를 사용했지 않은가! 일반적인 파일의 디스크립터를 전달하는 경우나 소켓 디스크립터를 전달하는 경우나 마찬가지로 read() 함수의 실행을 마치지 않고 대기해야 하는 것 아닐까?
프로그램이 종료되었다는 것은 다시말해 do - while 문을 탈출하게 되었다는 말이다. echo() 함수에서 do - while 문을 탈출하는 방법은 단 하나다. 바로 r이 0이 되는 경우에 해당한다. 즉, 파일에서 더이상 읽어들일 내용이 없음에도 불구하고 read() 함수에서 프로그램 실행이 Block 되면 안 된다. read() 함수가 실행되고, 리턴값으로 0을 변수에 전달해야 한다. 그런데 분명히 소켓 파일을 다룰 때에는 더이상 읽을 값이 없을 때 read() 함수의 실행을 마치지 않고 유야무야 기다렸기 때문에 변수 r 에 0 이 할당될 일이 없다.
이 차이를 이해하려면 EoF의 존재를 알아야 한다. read() 함수의 매뉴얼을 보면 “Zero indicates EoF(end of file)” 이라고 명세되어 있다(참고2).
이말은 곧 temp.txt 와 같은 파일을 모두 읽어들인 다음에는 EoF임이 자동으로 알려지지만, 소켓 파일을 읽어들일 때에는 EoF을 전달받을 수 없다고 이해할 수 있다.
일반 파일 디스크립터
소켓 파일 디스크립터
다 읽었을때 전달되는 값
EoF(End of File)
-
그럼 왜 소켓 파일 디스크립터는 자동으로 EoF을 전달받지 못하도록 설계되어 있을까? 소켓파일에 언제 값이 쓰일지 모르는 서버 입장에서는 아무 입력이 없을 때 그냥 대기하는 것이 훨씬 편하기 때문이다(참고3).
다시말해 일반적인 파일의 디스크립터를 통해 값을 읽어들일 때와 소켓 파일의 디스크립터를 통해 값을 읽어들일 때의 차이는 결국 EoF를 알 수 있는지 없는지의 여부다.
환경마다 다르지만 보통 ctr+D 는 EoF시그널인 경우가 많다. 앞서 말했던 내용들이 모두 사실이라면, nc 기반의 클라이언트에서 ctr+D(EoF)시그널을 주입한다면 서버 프로그램의 read() 명령이 0을 반환하고 while 문을 빠져나와 종료되어야 한다. 실제로 실험해 보아도 정상적으로 동작함을 확인할 수 있다.
$ nc localhost 2048 hello world! # ctr+d 입력
Plain Text
복사
클라이언트
$ tcp_server Waiting for connections... Connection successful! hello world! # 프로그램 종료
Plain Text
복사
서버
parse me : 언젠가 이 글에 쓰이면 좋을 것 같은 재료들.
from : 과거의 어떤 생각이 이 생각을 만들었는가?
1.
supplementary : 어떤 새로운 생각이 이 문서에 작성된 생각을 뒷받침하는가?
1.
None
opposite : 어떤 새로운 생각이 이 문서에 작성된 생각과 대조되는가?
1.
None
to : 이 문서에 작성된 생각이 어떤 생각으로 발전되고 이어지는가?
참고 : 레퍼런스