chan — 채널 읽고 쓰고 다루기

chan — 채널 읽고 쓰고 다루기

파일이나 소켓, 표준 입출력 등 Tcl에서 "채널"이라고 부르는 입출력 스트림은 한곳에서 관리하면 훨씬 편해요. chan 명령은 이런 채널에 대한 읽기·쓰기·설정·이벤트 처리 등 여러 작업을 한 명령으로 제공합니다.

출처: chan manual page

본문

chan option ?arg arg ...?

이 명령은 열려 있는 채널(예: open·socket 명령으로 만들거나, 프로세스의 표준 입출력·오류 스트림에 해당하는 기본 채널 stdin, stdout, stderr)에서 읽고 쓰고 다루는 여러 작업을 제공합니다. Option은 채널로 무엇을 할지 나타내며, 유일하게 축약 가능합니다. 유효한 옵션들:

chan blocked channelId

채널 channelId에 대한 마지막 입력 연산이, 그렇지 않았다면 프로세스가 블로킹됐을 상황 때문에 실패했는지 검사해 그런 경우 1을 리턴합니다. 그 외엔 0을 리턴해요. 채널이 논블로킹으로 설정된 경우에만 1을 리턴한다는 점을 기억하세요. 모든 Tcl 채널은 기본적으로 블로킹이 켜져 있습니다.

chan close channelId ?direction?

채널 channelId를 닫고 파괴합니다. 이는 채널에 등록된 모든 기존 파일 이벤트를 삭제해요. direction 인자(readwrite, 또는 그 유일 축약)가 있으면 채널을 반만 닫아, 읽기-쓰기에서 쓰기 전용 또는 읽기 전용으로 바꿀 수 있습니다. 읽기 전용 채널을 읽기로 닫으면 완전히 닫는 것과 같고, 쓰기 전용 채널도 비슷해요. direction 인자 없이 채널은 읽기·쓰기 모두(현재 열려 있는 방향에 한해) 닫힙니다. 읽기 전용 채널을 쓰기로, 또는 쓰기 전용 채널을 읽기로 닫는 것은 오류예요.

채널을 닫는 일부로 모든 버퍼 출력을 출력 장치로 flush하고(채널이 더 이상 쓰기 가능하지 않게 되는 경우에만), 버퍼된 입력은 버리고(읽기 가능하지 않게 되는 경우에만), 밑의 운영체제 자원을 닫고 channelId를 더 이상 쓸 수 없게 만듭니다(완전히 닫힐 때만).

채널이 블로킹이고 쓰기 가능하지 않게 되는 중이면, 명령은 모든 출력이 flush될 때까지 리턴하지 않아요. 채널이 논블로킹이고 flush되지 않은 출력이 있으면 채널은 열린 채로 리턴하며, 출력은 백그라운드에서 flush되고 모두 끝나면 채널이 닫힙니다.

channelId가 명령 파이프라인의 블로킹 채널이면 chan close는 자식 프로세스가 끝나길 기다립니다.

채널이 인터프리터들 사이에 공유되면 chan close는 호출한 인터프리터에서 channelId를 못 쓰게 할 뿐, 공유하는 모든 인터프리터가 닫기 전까지 다른 효과는 없어요. 채널이 등록된 마지막 인터프리터가 chan close(또는 close)를 호출하면 위의 정리 작업이 일어납니다. 반쪽 닫기의 경우, 채널의 반쪽 닫기는 모든 채널이 그 방향(또는 완전히)으로 닫을 때까지 현재 인터프리터의 채널 보기에만 적용됩니다. 채널 공유는 interp 명령을 참조하세요.

채널은 인터프리터가 파괴되고 프로세스가 종료될 때 완전히 닫힙니다. 프로세스 종료 전에 모든 출력이 제대로 flush되도록 블로킹 모드로 전환됩니다. 이 명령은 빈 문자열을 리턴하며 flush 중 오류가 나면 오류를 낼 수 있어요. open으로 만든 파이프라인 명령이 오류를 리턴하면 chan close가 오류를 발생시킵니다(exec 명령과 비슷).

소켓과 명령 파이프라인의 반쪽 닫기는 밑의 시스템 자원의 shutdown()/close()를 초래해 다른 프로세스나 시스템이 Tcl 프로그램에 응답하는 방식을 바꿀 수 있으므로 중요한 부작용이 있을 수 있어요.

chan configure channelId ?optionName? ?value? ?optionName value?...

채널 channelId의 구성 옵션을 조회하거나 설정합니다. optionName·value 인자가 없으면 채널의 옵션 이름과 값을 번갈아 담은 목록을 리턴해요. optionName만 있고 value가 없으면 해당 옵션의 현재 값을 리턴합니다. optionName·value 쌍이 하나 이상 주어지면 각 이름 있는 옵션을 해당 value로 설정하고, 이 경우 리턴값은 빈 문자열입니다.

아래 옵션은 모든 채널에 지원됩니다. 그 외에 각 채널 타입은 자신만 지원하는 옵션을 추가할 수 있어요. 각 채널 타입을 만드는 명령의 매뉴얼을 참조하세요(예: 소켓 추가 옵션은 socket, 직렬 장치 추가 옵션은 open).

  • -blocking* boolean — I/O 연산이 프로세스를 무기한 블로킹할 수 있는지 결정. 값은 적절한 불리언이어야 해요. 채널은 보통 블로킹 모드이며, 논블로킹 모드로 두면 chan gets, chan read, chan puts, chan flush, chan close의 동작에 영향 줍니다. 논블로킹 모드가 제대로 동작하려면 애플리케이션이 Tcl 이벤트 루프를 사용해야 해요(예: Tcl_DoOneEvent 호출이나 vwait 명령).
  • -buffering newValuefull이면 내부 버퍼가 가득 차거나 chan flush가 호출될 때까지 출력을 버퍼링. line이면 개행 문자가 출력될 때마다 자동 flush. none이면 매 출력 연산 후 자동 flush. 기본은 채널이 터미널류 장치에 연결된 경우를 제외하고 full이며, 터미널 채널의 초기 설정은 line이에요. 추가로 stdin·stdout은 초기 line, stderrnone.
  • -buffersize newSize — 이후 채널에 할당할 버퍼 크기(바이트). 백만 이하의 정수여야 해요.
  • -encoding name — 채널 인코딩을 encoding names가 돌려주는 명명된 인코딩 중 하나 또는 특수 값 binary로 지정합니다. 예를 들어 일본어 파일을 shiftjis로 읽고 제대로 처리·표시하려면 인코딩을 shiftjis로 설정해요. 순수 이진 데이터(JPEG 이미지 등) 파일이면 인코딩을 binary로 해 데이터를 로(raw) 바이트로 읽고 써요. 이진 데이터를 전송할 때는 -translation 옵션을 binary로 설정하는 게 더 나은데, 스트림의 바이트에 대한 다른 자동 해석도 꺼 주기 때문입니다. 새로 연 채널의 기본 인코딩은 운영체제 인터페이스에 쓰이는 플랫폼·로케일 의존 시스템 인코딩(encoding system이 리턴)입니다.
  • -eofchar char 또는 -eofchar {inChar outChar} — DOS 파일시스템의 Control-z(\x1A) 파일 끝 표시를 지원. char가 빈 문자열이 아니면 입력 중 이 문자가 파일 끝을 알립니다. 출력용 파일 끝 문자는 채널이 닫힐 때 출력돼요. 읽기-쓰기 채널은 두 요소 목록으로 입력·출력 파일 끝 표시를 지정합니다. 설정 시 단일 값으로 읽기·쓰기 모두에 적용할 수 있고, 조회 시 항상 두 요소 목록을 리턴합니다. 기본값은 모든 경우 빈 문자열이며, Windows의 파일에서만 읽기용 -eofchar가 Control-z(\x1A), 쓰기용이 빈 문자열입니다. 유효 범위는 \x01 - \x7f이며 범위 밖은 오류.
  • -translation translation 또는 -translation {inTranslation outTranslation} — Tcl 스크립트에서 줄 끝은 항상 개행 문자(\n) 하나로 표현하지만, 실제 파일·장치에선 플랫폼에 따라(그리고 같은 플랫폼의 장치에 따라) 줄 끝이 다르게 표현될 수 있어요. 입력(chan gets·chan read) 시 Tcl I/O 시스템이 외부 줄 끝 표현을 개행 문자로 자동 번역하고, 출력(chan puts) 시 개행을 외부 표현으로 번역합니다. 기본 auto가 일반적인 경우를 다 처리하지만 -translation 옵션이 명시적 제어를 제공합니다. 읽기 전용/쓰기 전용 채널은 단일 항목, 읽기-쓰기 채널은 두 요소 목록(첫째가 읽기 번역 모드, 둘째가 쓰기 번역 모드)입니다. 지원 값:
    • auto — 입력 번역 모드로 개행(lf), 캐리지 리턴(cr), 캐리지 리턴+개행(crlf) 중 어떤 것을 줄 끝으로 취급하고 모두 개행으로 번역. 출력 모드로 플랫폼별 표현을 고르며, 모든 플랫폼의 소켓은 crlf, 모든 Unix는 lf, 각종 Windows는 crlf를 선택. 입력·출력 모두 기본 auto.
    • binarylf처럼 줄 끝 번역을 하지 않으면서 -eofchar를 빈 문자열로 꺼 버리고 -encodingiso8859-1로 설정. 이 설정 하나로 채널이 이진 입출력에 완전히 구성돼요.
    • cr — 밑의 파일/장치의 줄 끝이 단일 캐리지 리턴. 입력은 캐리지 리턴을 개행으로, 출력은 개행을 캐리지 리턴으로.
    • crlf — 줄 끝이 캐리지 리턴+라인피드. Windows 플랫폼과 네트워크 연결에서 보통 사용.
    • lf — 줄 끝이 단일 개행(라인피드). 입출력 모두 번역 없음. Unix 플랫폼에서 보통 사용.

chan copy inputChan outputChan ?-size size? ?-command callback?

읽기용으로 열린 inputChan에서 쓰기용으로 열린 outputChan으로 데이터를 복사합니다. Tcl I/O 시스템의 버퍼링을 활용해 불필요한 복사를 피하고, 큰 파일을 네트워크 소켓 같은 느린 목적지로 복사할 때 메모리를 너무 많이 버퍼링하지 않아요. 파일 끝 또는 size 바이트/문자가 전송될 때까지 데이터를 전송합니다. size는 두 채널이 같은 인코딩을 쓰면 바이트, 아니면 문자 단위예요. -size가 없으면 파일 끝까지 복사합니다. -command 옵션이 없으면 블로킹하며 완료 시 outputChan에 쓰인 바이트/문자 수를 리턴해요. -command 인자는 chan copy가 백그라운드로 동작하게 합니다. 이 경우 즉시 리턴하고 복사 완료 시 callback이 나중에 호출돼요. 복사 중 inputChan·outputChan으로 다른 I/O 연산을 하면 안 됩니다. 복사 중 닫히면 현재 복사가 중지되고 콜백은 호출되지 않아요.

chan create mode cmdPrefix

cmdPrefix를 핸들러로 쓰는 새 스크립트 레벨 채널을 만듭니다. 이런 채널을 reflected 채널이라고 해요. mode는 읽기/쓰기/둘 다를 지정하며 read·write 문자열을 담은 목록이어야 하고 최소 한 요소가 필요합니다. 새 채널의 핸들을 리턴하며 채널은 열려 있어요. closechan close로 제거합니다. 이 서브명령은 safe하며 safe 인터프리터에 접근 가능합니다.

chan eof channelId

채널 channelId에 대한 마지막 입력 연산이 데이터 스트림 끝 때문에 실패했는지 검사해 파일 끝이면 1, 아니면 0을 리턴합니다.

chan event channelId event ?script?

채널 channelIdevent(readable 또는 writable)가 설명하는 상태가 될 때마다 호출될 파일 이벤트 핸들러로 Tcl 스크립트 script를 설치합니다. 채널당 이벤트당 한 개의 핸들러만 설치 가능해요. script가 빈 문자열이면 현재 핸들러가 삭제되고, script가 생략되면 현재 설치된 스크립트를 리턴합니다. 이벤트 루프가 서비스될 때만 콜백이 수행됩니다(예: vwait·update를 통해).

chan flush channelId

채널 channelId의 모든 보류 출력이 쓰이게 보장합니다. 블로킹 모드면 모든 버퍼 출력이 flush될 때까지 리턴하지 않아요. 논블로킹 모드면 전부 flush되기 전에 리턴할 수 있고, 나머지는 백그라운드에서 flush됩니다.

chan gets channelId ?varName?

채널 channelId에서 다음 줄을 읽습니다. varName 미지정 시 읽은 줄(끝 개행 제외) 또는 파일 끝/논블로킹에서 데이터 소진 시 빈 문자열을 리턴해요. varName 지정 시 읽은 줄을 varName에 쓰고 읽은 문자 수(파일 끝 또는 논블로킹 데이터 소진 시 -1)를 리턴합니다.

chan names ?pattern?

모든 채널 이름의 목록을 만듭니다. pattern이 있으면 그것과 일치하는(string match 규칙) 이름만 리턴해요.

chan pending mode channelId

modeinput이면 입력, output이면 출력의 현재 내부 버퍼링 바이트 수를 리턴합니다. 읽기 이벤트 콜백에서 입력 줄 길이에 애플리케이션별 한계를 두어 악성 사용자가 메모리를 넘는 극단적으로 긴 줄을 만드는 서비스 거부 공격을 피하는 데 특히 유용해요. 해당 모드로 열리지 않은 채널이면 -1을 리턴합니다.

chan pipe

읽기 쪽과 쓰기 쪽 채널을 2요소 목록(첫째 읽기, 둘째 쓰기)으로 리턴하는 독립 파이프를 만듭니다. 서브프로세스에서 stderrstdout을 따로 리다이렉트할 때 유용해요. 파이프 버퍼링 의미는 운영체제 수준에서 크게 달라질 수 있으므로, 쓰기 쪽 쓰기가 즉시 입력 쪽에 나타난다고 가정하는 건 안전하지 않습니다.

chan pop channelId

채널 channelId의 최상위 변환(transformation)을 제거합니다. 변환이 없으면 이는 그 채널의 chan close와 동등해요. 결과는 보통 빈 문자열입니다.

chan postevent channelId eventSpec

chan create로 지정한 명령 핸들러가 사용합니다. eventSpec 목록(read·write 문자열)의 이벤트들이 발생했음을 채널에 알립니다. chan create로 만든 채널 핸들에만 쓸 수 있어요.

chan push channelId cmdPrefix

채널 channelId 위에 새 변환을 추가합니다. cmdPrefix는 변환을 구현할 핸들러를 나타내는 한 개 이상의 단어 목록입니다. 이 서브명령의 결과는 변환의 핸들이에요.

chan puts ?-nonewline**? ?channelId? string**

string을 채널 channelId에 개행과 함께 씁니다. -nonewline 플래그가 주어지지 않으면 끝에 개행을 써요. channelId가 생략되면 표준 출력 stdout에 씁니다. Tcl은 출력을 내부 버퍼링하므로 chan puts로 쓴 문자가 즉시 나타나지 않을 수 있고, 버퍼가 가득 차거나 채널이 닫힐 때까지 지연돼요. chan flush로 즉시 출력하게 할 수 있어요.

chan read channelId ?numChars?

chan read ?-nonewline**? channelId**

첫 형태는 채널 channelId에서 다음 numChars 문자를 리턴하고, numChars가 생략되면 채널이 실패 신호(파일 끝·블로킹·오류)를 낼 지점까지 모든 문자를 읽습니다. 둘째 형태(numChars 생략)에선 -nonewline 플래그로 읽은 문자열의 뒤쪽 개행을 제거할 수 있어요. 논블로킹 모드이면 요청만큼 읽지 못할 수 있고, 가용 입력을 모두 읽으면 더 입력을 위해 블로킹하지 않고 가용 데이터를 리턴합니다.

chan seek channelId offset ?origin?

채널 channelId의 밑 데이터 스트림 내 현재 접근 위치를 origin에 상대적인 offset 바이트로 설정합니다. offset은 정수(음수 가능)이고 originstart(파일 시작에서), current(현재 위치에서, 음수 offset은 뒤로), end(파일/장치 끝에서, 음수는 끝 앞, 양수는 끝 뒤) 중 하나예요. origin 기본은 start입니다. 이 명령은 논블로킹 모드여도 리턴 전에 채널의 모든 버퍼 출력을 flush하고, 버퍼된 읽지 않은 입력도 버려요. offset은 바이트 오프셋이지 문자 오프셋이 아니에요(chan read와 달리 chan seek·chan tell 둘 다 바이트 단위).

chan tell channelId

채널 channelId의 밑 데이터 스트림 내 현재 접근 위치를 나타내는 숫자를 리턴합니다. chan seek에 넘겨 특정 위치로 설정할 수 있는 바이트 오프셋이에요. 시킹을 지원하지 않는 채널은 -1을 리턴합니다.

chan truncate channelId ?length?

채널 channelId의 밑 데이터 스트림의 바이트 길이를 length(또는 생략 시 현재 바이트 오프셋)로 설정합니다. 잘라내기 전에 채널을 flush해요.

예시

stdout으로의 출력을 터미널이든 아니든 항상 즉시 보내도록 지시:

chan configure stdout -buffering none

Windows에서 흔한 CP1252 인코딩으로 파일을 열고, 문자열을 찾아 그 부분을 다시 쓰고, 두 줄 뒤에서 파일을 자르는 예:

set f [open somefile.txt r+]
chan configure $f -encoding cp1252
set offset 0

# Search for string "FOOBAR" in the file
while {[chan gets $f line] >= 0} {
    set idx [string first FOOBAR $line]
    if {$idx >= 0} {
        # Found it; rewrite line

        chan seek $f [expr {$offset + $idx}]
        chan puts -nonewline $f BARFOO

        # Skip to end of following line, and truncate
        chan gets $f
        chan gets $f
        chan truncate $f

        # Stop searching the file now
        break
    }

    # Save offset of start of next line for later
    set offset [chan tell $f]
}
chan close $f

다른 연결 처리를 막지 않으면서 입력을 줄 단위로 되돌려주는(echo) 네트워크 서버:

# This is a very simple logger...
proc log {message} {
    chan puts stdout $message
}

# This is called whenever a new client connects to the server
proc connect {chan host port} {
    set clientName [format <%s:%d> $host $port]
    log "connection from $clientName"
    chan configure $chan -blocking 0 -buffering line
    chan event $chan readable [list echoLine $chan $clientName]
}

# This is called whenever either at least one byte of input
# data is available, or the channel was closed by the client.
proc echoLine {chan clientName} {
    chan gets $chan line
    if {[chan eof $chan]} {
        log "finishing connection from $clientName"
        chan close $chan
    } elseif {![chan blocked $chan]} {
        # Didn't block waiting for end-of-line
        log "$clientName - $line"
        chan puts $chan $line
    }
}

# Create the server socket and enter the event-loop to wait
# for incoming connections...
socket -server connect 12345
vwait forever

더 알아보기

  • close, eof, fblocked, fconfigure, fcopy, file, fileevent, flush, gets, open, puts, read, seek, socket, tell, refchan, transchan, Tcl_StandardChannels — 채널 관련 명령들
  • 키워드: blocking, channel, end of file, events, input, non-blocking, offset, output, readable, seek, stdio, tell, writable