포트 버퍼와 위치

포트 버퍼와 위치 (Port Buffers and Positions)

파일에서 읽고 파일로 쓰는 포트 같은 일부 포트는 내부적으로 버퍼링돼요. 버퍼링 덕분에 읽기·쓰기 성능이 좋아지지만, 파일이 바뀌었을 때 낡은 데이터를 읽게 되는 부작용도 생길 수 있어요. 이 절에서는 버퍼 모드를 바꾸고 포트의 위치를 다루는 함수들을 살펴볼게요.

출처: Racket Reference

본문

일부 포트—특히 파일을 읽고 쓰는 포트—는 내부적으로 버퍼링돼요:

  • 입력 포트는 보통 기본적으로 블록 버퍼링(block-buffered)돼요. 어떤 읽기에서든 즉시 사용 가능한 바이트로 버퍼가 채워져서 이후의 읽기를 빠르게 해준다는 뜻이에요. 따라서 읽기 사이에 파일이 수정되면 두 번째 읽기가 낡은 데이터를 줄 수 있어요. file-position을 호출해 입력 포트의 파일 위치를 설정하면 그 버퍼를 비워요.

  • 출력 포트는 보통 기본적으로 블록 버퍼링되지만, 터미널 출력 포트는 라인 버퍼링(line-buffered)이고, 초기 오류 출력 포트는 버퍼링되지 않아요. 출력 버퍼는 한 묶음으로 커밋될 일련의 쓰여진 바이트로 채워지는데, 버퍼가 가득 차거나(블록 모드), 개행 문자가 쓰이거나(라인 모드), close-output-port로 포트가 닫히거나, flush-output 같은 프로시저로 플러시가 명시적으로 요청될 때 커밋돼요.

포트가 버퍼링을 지원하면 file-stream-buffer-mode로 버퍼 모드를 바꿀 수 있어요(포트가 file-stream 포트가 아니더라도).

입력 포트의 경우, 버퍼 모드가 'none이어도 peek은 항상 peek된 바이트를 포트의 버퍼에 넣어요. 게다가 어떤 플랫폼에서는(char-ready?sync로) 포트에 입력이 있는지 검사하는 게 peek으로 구현될 수 있어요. 입력 포트의 버퍼 모드가 'none이면 read-bytes-avail!*, read-bytes-avail!, peek-bytes-avail!*, peek-bytes-avail!을 위해 기껏해야 한 바이트가 읽혀요. 포트에 버퍼링된 바이트가 있으면(예: 이전 peek을 충족시키기 위해) 프로시저들이 여러 버퍼링된 바이트에 접근할 수 있지만, 그 이상의 바이트는 읽히지 않아요.

또한 초기 current-output-portcurrent-error-port는 이들이 터미널 포트일 때(terminal-port? 참고)와, 초기 표준 입력 포트에서 read, read-line, read-bytes, read-string 등이 수행될 때 자동으로 플러시돼요. (더 정확히는 read 대신, 플러시는 기본 포트 읽기 핸들러가 수행해요. port-read-handler 참고.)

procedure

(flush-output [out]) → void?
  out : output-port? = (current-output-port)

주어진 출력 포트에서 모든 버퍼링된 데이터가 실제로 쓰이도록 강제해요. 파일 스트림 포트, TCP 포트, 그리고 사용자 정의 포트(Custom Ports 참고)만 버퍼를 사용하며, 버퍼가 없는 포트에 호출하면 flush-output는 효과가 없어요.

파일 스트림 포트나 TCP 포트를 플러시할 때 쓰기에서 오류를 만나면, 포트의 모든 버퍼링된 바이트가 버려져요. 따라서 이후의 플러시나 닫기 시도는 실패하지 않아요.

base 패키지의 7.4.0.10 버전에서 TCP 출력 포트를 포함해 오류 시 버퍼링된 바이트를 일관되게 버리도록 변경됐어요.

procedure

(file-stream-buffer-mode port) → (or/c 'none 'line 'block #f)
  port : port?
(file-stream-buffer-mode port mode) → void?
  port : port?
  mode : (or/c 'none 'line 'block)

가능하면 port의 버퍼 모드를 얻거나 설정해요. 파일 스트림 포트는 버퍼 모드 설정을 지원하고, TCP 포트(Networking 참고)는 버퍼 모드를 설정·조회를 지원하며, 사용자 정의 포트(Custom Ports 참고)는 버퍼 모드 조회·설정을 지원할 수 있어요.

mode가 주어지면 'none, 'line(출력 전용), 'block 중 하나여야 하며 포트의 버퍼링이 그에 따라 설정돼요. 포트가 모드 설정을 지원하지 않으면 exn:fail 예외가 발생해요.

mode가 주어지지 않으면 현재 모드가 돌아오거나, 모드를 결정할 수 없으면 #f가 돌아와요. port가 입력 포트이고 mode'line이면 exn:fail:contract 예외가 발생해요.

procedure

(file-position port) → exact-nonnegative-integer?
  port : port?
(file-position port pos) → void?
  port : port?
  pos : (or/c exact-nonnegative-integer? eof-object?)

port의 현재 읽기/쓰기 위치를 돌려주거나 설정해요.

파일 스트림 포트나 문자열 포트가 아닌 포트에 위치 없이 file-position을 호출하면, 그 포트에서 읽힌 바이트 수가 알려져 있으면(위치·줄·열 계산하기 참고) 돌려주고, 그렇지 않으면 exn:fail:filesystem 예외가 발생해요.

파일 스트림 포트와 문자열 포트의 경우, 위치 설정 변형은 pos가 숫자면 파일 또는 (바이트) 문자열의 시작을 기준으로 읽기/쓰기 위치를 pos로 설정하고, poseof면 파일 또는 (바이트) 문자열의 현재 끝으로 설정해요. 위치 설정 모드에서 file-position은 파일 스트림 포트와 문자열 포트가 아닌 포트 종류에 대해 exn:fail:contract 예외를 발생해요. 게다가 모든 파일 스트림 포트가 위치 설정을 지원하지는 않아요. 그런 파일 스트림 포트에 위치 인자로 file-position을 호출하면 exn:fail:filesystem 예외가 발생해요.

file-position이 출력 파일 또는 (바이트) 문자열의 현재 크기 너머에 위치 pos를 설정하면, 파일/문자열이 크기 pos로 확대되고 새 영역은 0 바이트로 채워져요. 파일 출력 포트의 경우 파일에 더 많은 데이터가 쓰일 때까지 확대되지 않을 수 있어요. 그 경우 Unix와 Mac OS에서 'append 모드로 열린 파일에 쓰는 것은 각 쓰기 전에 파일 포인터를 파일 끝으로 재설정해서, file-position을 통한 파일 확대를 무산시킬 수 있으니 주의해야 해요. pos가 입력 파일 또는 (바이트) 문자열의 끝 너머면, 이후의 읽기는 포트의 위치를 바꾸지 않고 eof를 돌려줘요.

출력 포트의 파일 위치를 바꿀 때, 그 버퍼가 비어 있지 않으면 포트가 먼저 플러시돼요. 마찬가지로 입력 포트의 위치를 설정하면 새 위치가 이전 위치와 같아도 포트의 버퍼가 비워져요. 그러나 open-input-output-file이 만든 입력·출력 포트는 파일 위치를 공유하지만, 한 포트를 통해 위치를 설정해도 다른 포트의 버퍼는 플러시되지 않아요.

procedure

(file-position* port) → (or/c exact-nonnegative-integer? #f)
  port : port?

한 인자 file-position과 같지만 위치를 알 수 없으면 #f를 돌려줘요.

procedure

(file-truncate port size) → void?
  port : (and/c output-port? file-stream-port?)
  size : exact-nonnegative-integer?

port가 쓴 파일의 크기를 size로 설정해요. 단 port가 크기를 설정할 수 있는 파일에 연관돼 있다고 가정해요.

새 파일 크기는 현재 크기보다 크거나 작을 수 있어요. 다만 이 함수 이름의 "truncate"는 파일에 쓰거나 file-position을 사용하면 파일 크기를 늘릴 수 있으므로, 보통 파일 크기를 줄이는 데 사용된다는 점을 반영한 거예요.

procedure

(terminal-file-position) → exact-nonnegative-integer?

원래 출력·오류 포트처럼 터미널에 연결된 포트에 쓰인 바이트 수를 보고해요. 이 개수는 같은 터미널에 다른 프로세스가 쓴 바이트는 포함하지 않아요. Racket 프로세스가 시작한 하위 프로세스라도 마찬가지예요.

base 패키지의 9.1.0.5 버전에서 추가됐어요.

더 알아보기