refchan — 반영된 채널(reflected channel)의 명령 핸들러 API
refchan — 반영된 채널(reflected channel)의 명령 핸들러 API
Tcl의 채널은 보통 네이티브 확장이 만들지만, 순수 Tcl 코드로도 채널을 만들 수 있어요. 그런 채널을 "반영된 채널"(reflected channel)이라고 불러요. chan create로 만들고, refchan은 그 채널의 동작을 직접 정의하는 명령 핸들러가 지켜야 할 API를 설명해요. 문자열에서 읽는 채널, 특수한 가상 채널 등 자신만의 채널을 만들고 싶다면 이 페이지가 기준이 돼요.
출처: 문서
본문
문법
cmdPrefix option ?arg arg ...?
반영된 채널의 Tcl 레벨 핸들러는 서브커맨드를 가진 명령(엔상블(ensemble))이어야 해요. namespace ensemble create가 만드는 것과 같은 명령처럼 동작하지만, 반영된 채널의 핸들러 구현이 네임스페이스 엔상블에 묶여 있는 건 아니에요. cmdPrefix는 chan create 호출에서 지정한 것이며 여러 인자로 구성될 수 있는데, 이때 프리픽스 대신 여러 단어로 확장돼요.
가능한 서브커맨드 중 핸들러는 initialize, finalize, watch를 반드시 지원해야 해요. 나머지 서브커맨드 지원은 선택이에요.
필수 서브커맨드
cmdPrefix initialize channelId mode
이 서브커맨드의 호출은 지정된 새 channelId에 대해 cmdPrefix가 받는 첫 번째 호출이에요. 채널과 그 상태를 추적하는 데 필요한 내부 데이터 구조를 설정하는 책임이 있어요. 이 메서드의 반환값은 cmdPrefix가 지원하는 모든 서브커맨드 이름의 리스트여야 해요. 또한 이 명령 핸들러가 사용하는 반영된 채널 API의 버전을 Tcl 코어에 알려줘요.
메서드가 오류를 던지면 채널 생성이 중단되고 채널은 만들어지지 않아요. 그 오류는 chan create가 던진 오류로 나타나요. 오류가 아닌 예외(예: break 등)는 오류로 처리(및 변환)돼요.
참고: 여기서 실패로 채널 생성이 중단됐다면 finalize 서브커맨드는 호출되지 않아요.
mode 인자는 채널이 읽기, 쓰기, 또는 둘 다로 열렸는지 핸들러에 알려줘요. read 또는 write 문자열 중 아무거나 포함할 수 있는 리스트예요. 리스트는 비어 있을 수 있지만 보통 최소 하나의 요소를 포함해요. 선택한 모드가 cmdPrefix에서 지원되지 않으면 이 서브커맨드는 오류를 던져야 해요.
cmdPrefix finalize channelId
이 서브커맨드의 호출은 지정된 channelId에 대해 cmdPrefix가 받는 마지막 호출이에요. Tcl 코어가 보유한 채널의 데이터 구조가 파괴되기 직전에 생성돼요. 명령 핸들러는 더 이상 어떤 방식으로든 channelId에 접근해서는 안 돼요. 이 서브커맨드가 호출되면 이 채널에 할당된 내부 리소스를 모두 정리해야 해요. 반환값은 무시돼요.
서브커맨드가 오류를 던지면 그것을 호출하게 한 명령(보통 chan close)이 그 오류를 던진 것처럼 보여요. error를 넘는 예외(예: break)는 오류로 처리(및 변환)돼요.
이 서브커맨드는 initialize 중에 채널 생성이 중단된 경우에는 호출되지 않아요.
cmdPrefix watch channelId eventspec
이 서브커맨드는 지정된 channelId가 eventspec에 나열된 이벤트에 관심이 있음을 cmdPrefix에 알려줘요. 이 인자는 read와 write 중 아무거나 포함할 수 있는 리스트예요. 리스트가 비어 있으면 채널이 어떤 이벤트도 통지받길 원하지 않는다는 신호로, 이 상황에서 핸들러는 이벤트 생성을 완전히 비활성화해야 해요.
경고: 서브커맨드의 반환값은 무시돼요. 서브커맨드가 던진 모든 오류, break, continue, 사용자 지정 반환 코드 포함해서 모두 무시돼요.
이 서브커맨드는 chan postevent와 상호작용해요. 마지막 watch 호출에 나열되지 않은 이벤트를 게시하려 하면 chan postevent가 오류를 던지게 돼요.
선택 서브커맨드
cmdPrefix read channelId count — 사용자가 channelId에서 데이터를 요청할 때 호출되는 선택 서브커맨드예요. count는 요청된 바이트 수를 지정해요. 지원되지 않으면 그 명령이 다루는 채널에서 읽기가 불가능해요. 반환값은 요청된 데이터 바이트로 취급돼요. 반환된 데이터가 요청보다 많으면 오류가 신호되고 나중에 읽기를 수행한 명령(보통 gets나 read)이 던져요. 하지만 요청보다 적은 바이트를 반환하는 건 허용돼요. 참고로 아무것도 반환하지 않는 것(0바이트)은 상위 계층에 그 채널에서 EOF에 도달했음을 알리는 신호예요. 채널에 지금 데이터가 없지만 아직 EOF는 아님을 알리려면 "EAGAIN" 오류를 던져야 해요:
return -code error EAGAIN
또는
error EAGAIN
확장성을 위해 값이 음의 정수인 모든 오류는 상위 계층이 C 레벨 변수 errno를 그 절대값으로 설정해 시스템 오류를 신호하게 해요. 다만 이 오류 번호와 의미 사이의 정확한 매핑은 운영체제에 따라 달라요. 예를 들어 Linux에서는 return -code error -11과 error -11이 위 예제들과 동등하지만(더 읽기 쉬운 문자열 "EAGAIN" 사용), BSD에서는 그렇지 않아요. BSD의 등가 숫자는 -35예요. 그러나 기호 문자열은 시스템 전체에서 동일하고 내부적으로 올바른 숫자로 번역돼요. 다른 오류 값에는 그런 기호 문자열로의 매핑이 없어요.
서브커맨드가 다른 오류를 던지면 그것을 호출하게 한 명령(보통 gets 또는 read)이 그 오류를 던진 것처럼 보여요.
cmdPrefix write channelId data — 사용자가 channelId에 데이터를 쓸 때 호출되는 선택 서브커맨드예요. data 인자는 문자(character)가 아닌 바이트를 포함해요. 채널에 설정된 어떤 변환(EOL, 인코딩)도 이미 이 시점에 적용됐어요. 지원되지 않으면 그 명령이 다루는 채널에 쓰는 게 불가능해요. 반환값은 채널이 쓴 바이트 수로 취급돼요. 비숫자 값이면 오류가 신호되고 나중에 쓰기를 수행한 명령이 던져요. 음수 값은 쓰기가 실패했음을 뜻해요. 핸들러에 주어진 바이트 수보다 큰 값이나 0을 반환하는 건 금지이며 Tcl 코어가 오류를 던져요.
채널이 지금 쓰기 데이터를 받아들일 수 없음을 알리려면 "EAGAIN" 오류를 던져야 해요 (read와 같은 방식). 음의 정수 오류 값은 errno 설정을 유발하고, 기호 문자열 "EAGAIN"이 시스템 전체에서 동일하다는 점도 동일해요.
cmdPrefix seek channelId offset base — chan seek와 chan tell 요청을 처리하는 선택 서브커맨드예요. 지원되지 않으면 채널에 대한 시킹이 불가능해요. base 인자는 내장 chan seek의 해당 인자와 같아요:
start— 채널의 시작을 기준으로 시킹current— 현재 시크 위치를 기준으로 시킹end— 채널의 끝을 기준으로 시킹
offset은 앞으로 또는 뒤로 시킹할 바이트 양을 지정하는 정수예요. 양수는 앞으로, 음수는 뒤로 시킹해야 해요. 채널은 제한된 시킹만 제공할 수 있어요. 예를 들어 소켓은 앞으로는 시킹할 수 있지만 뒤로는 못 해요. 반환값은 시작에서 센 채널의 (새) 위치로 취급되는데, 0보다 크거나 같은 정수여야 해요. 오류를 던지면 호출을 유발한 명령(보통 chan seek나 chan tell)이 그 오류를 던진 것처럼 보여요. offset/base 조합 0/current는 chan tell 요청을 신호하는데, 즉 현재 위치를 기준으로 아무것도 시킹하지 않아 새 위치가 현재와 동일해지고 그 값이 반환돼요.
cmdPrefix configure channelId option value — channelId의 타입별 옵션을 설정하는 선택 서브커맨드예요. option 인자는 쓸 옵션을, value 인자는 그 옵션에 설정할 값을 나타내요. 이 서브커맨드는 한 번에 하나 이상의 옵션을 갱신하려 하지 않아요. 그것은 Tcl 채널 코어에서 구현된 동작이에요. 반환값은 무시돼요. 오류를 던지면 (재)구성이나 질의를 수행한 명령(보통 fconfigure나 chan configure)이 그 오류를 던진 것처럼 보여요.
cmdPrefix cget channelId option — channelId의 단일 타입별 옵션을 읽을 때 사용하는 선택 서브커맨드예요. 지원된다면 cgetall 서브커맨드도 반드시 지원해야 해요. 서브커맨드는 지정된 옵션의 값을 반환해야 해요. 오류 처리 규칙은 위와 같아요.
cmdPrefix cgetall channelId — channelId의 모든 타입별 옵션을 읽는 데 사용하는 선택 서브커맨드예요. 지원된다면 cget도 반드시 지원해야 해요. 모든 옵션과 그 값의 리스트를 반환해야 하고, 이 리스트는 짝수 개의 요소를 가져야 해요. 오류 처리 규칙은 위와 같아요.
cmdPrefix blocking channelId mode — channelId의 블로킹 모드 변경을 처리하는 선택 서브커맨드예요. mode는 불리언 플래그예요. 참이면 채널을 블로킹으로, 거짓이면 논블로킹으로 설정해야 해요. 반환값은 무시돼요. 오류 처리 규칙은 위와 같아요.
참고 사항
Tcl의 C 인터페이스에 정의된 채널의 함수 중 일부는 Tcl 레벨로 반영된 채널에는 사용할 수 없어요.
Tcl_DriverGetHandleProc는 지원되지 않아요. 즉 반영된 채널에는 OS 특정 핸들이 없어요.Tcl_DriverHandlerProc는 지원되지 않아요. 이 드라이버 함수는 스택 채널, 즉 변환(transformation)에만 관련돼요. 반영된 채널은 항상 기본(base) 채널이지 변환이 아니에요.Tcl_DriverFlushProc는 지원되지 않아요. 현재 Tcl의 제네릭 I/O 계층이 이 함수를 전혀 사용하지 않기 때문이에요.
예제
문자열에서 읽는 채널을 만드는 방법이에요.
oo::class create stringchan {
variable data pos
constructor {string {encoding {}}} {
if {$encoding eq ""} {set encoding [encoding system]}
set data [encoding convertto $encoding $string]
set pos 0
}
method initialize {ch mode} {
return "initialize finalize watch read seek"
}
method finalize {ch} {
my destroy
}
method watch {ch events} {
# 이벤트를 게시하지 않으므로 무시하지만 필수로 존재해야 함
}
# 읽을 수 있는 채널에는 필수
method read {ch count} {
set d [string range $data $pos [expr {$pos+$count-1}]]
incr pos [string length $d]
return $d
}
# 선택이지만 아래 예제에 유용
method seek {ch offset base} {
switch $base {
start {set pos $offset}
current {incr pos $offset}
end {set pos [string length $data]; incr pos $offset}
}
if {$pos < 0} {
set pos 0
} elseif {$pos > [string length $data]} {
set pos [string length $data]
}
return $pos
}
}
# 이제 인스턴스를 만든다...
set string "The quick brown fox jumps over the lazy dog.\n"
set ch [chan create read [stringchan new $string]]
puts [gets $ch]; # 전체 문자열 출력
seek $ch -5 end;
puts [read $ch]; # 마지막 단어만 출력
더 알아보기
chan: 채널 명령 (특히chan create,chan postevent,chan configure)transchan: 채널 변환(트랜스포메이션) API