요약
- 텔넷의 네트워크 가상 터미널(NVT)은 서로 다른 시스템에 작은 공통 동작 어휘를 제공했습니다: 새 줄에는
CR LF, 캐리지 리턴 단독에는CR NUL, 그리고 기본 NVT ASCII에서는 홀로 나온CR을 피하는 규칙입니다. - CR 뒤에 오는 바이트는 양방향 판단의 근거였습니다. 발신 측은 NVT 동작을 선택했고, 수신 측은 그 동작을 자신의 터미널과 운영체제 관례로 번역했습니다.
- RFC 1123은 나중에 사람의 Enter 키를 둘러싼 모호성을 수습했고, 협상된 바이너리 모드는 줄 끝 변환을 비활성화했으며, Net-Unicode는 CRLF를 유지하되 CR NUL을 권장하지 않았습니다.
한 바이트만큼 미뤄진 판단
잠시 네트워크 가상 터미널을 문자 그대로 상상해 봅시다. 그 프린터는 한 줄의 중간쯤 놓여 있습니다. 옥텟 13이 도착했습니다: 캐리지 리턴입니다. NVT 모델에서 CR은 수직으로는 움직이지 않은 채 인쇄 위치를 왼쪽 여백으로 옮깁니다. 옥텟 10, 즉 라인 피드는 수평 위치를 그대로 둔 채 다음 줄로 이동합니다. 둘을 합치면 더 강력하고 편리한 의미가 됩니다: 다음 줄의 왼쪽 끝.
많은 실제 터미널은 이 두 동작을 독립적으로 구현하지 않았습니다. 어떤 기기는 리턴 키를 줄 이동과 묶었고, 어떤 호스트는 LF를 자체 레코드 구분자로 사용했습니다. 또 어떤 곳은 CR, 그 쌍, 개수를 세는 레코드, 아니면 전혀 다른 관례를 썼습니다. 수신 측이 CR을 즉시 실행하면 뒤이어 LF를 따로 실행해 의도된 단일 줄 바꿈 동작과 다른 결과를 낼 수 있었습니다. 기다리기로 했다면, 두 번째 이동이 오는지를 알아내는 유한한 방법이 필요했습니다.
RFC 318은 1972년에 답을 기술했습니다. 수신 측은 CR을 보류하고 다음 문자를 살펴볼 수 있었습니다.CR LF는 그 분기를 새 줄로 닫았습니다. 그 쌍에 속하지 않는 CR은CR NUL로 전송되어야 했습니다. NUL은 프린터 동작을 수행하지 않지만, 그 존재 자체가 발신 측의 선택을 완성했습니다.
따라서 널 바이트는 보편적인 공백도, 느슨한 지연 문자도 아니었습니다. 이 정확한 문법 위치에서, 그것은 그 CR에 LF가 딸려 오지 않는다는 근거였습니다.
가상 터미널은 공통 계층을 얇게 유지했다
RFC 854는 네트워크 가상 터미널을 텔넷의 기본 중간 표현으로 삼았습니다. 새 연결은 두 터미널의 전체 설명을 주고받는 것으로 시작하지 않았습니다. 각 호스트는 자신의 로컬 기기나 프로세스를 의도적으로 절제된 NVT로 사상하고, 상대도 그 반대를 할 것이라고 가정했습니다.
이런 역할 배분이 중요했습니다. 공유 프로토콜은 몇 가지 동작만 정의했습니다. NUL은 아무 동작도 아닙니다. LF는 수평 위치를 바꾸지 않은 채 아래로 이동합니다. CR은 현재 줄의 왼쪽 여백으로 이동합니다. 문자 변환, 터미널 타이밍, 물리적 폭, 페이지 길이, 그리고 네트워크 동작과 운영체제 인터페이스 사이의 관계는 여전히 로컬 코드의 몫이었습니다.
결합된 경우에 대해 RFC 854는 이전 권고를 규칙으로 강화했습니다. 결합 동작이 의도될 때CR LF는 하나의 줄 바꿈 문자로 다뤄야 했습니다.CR NUL은 캐리지 리턴 단독을 대표해야 했습니다. 기본 NVT ASCII의 다른 위치에서 CR은 피해야 했습니다. 이 규칙은 양방향으로 적용되었으며, 구현체가 실제 프린터가 개입하지 않는다고 믿을 때도 예외가 아니었습니다.
수신 시에는 CR 뒤의 NUL이 NVT 동작을 로컬 문자 집합에 사상하기 전에 제거되었습니다. 그것은 네트워크 표현에 속했고, 자동으로 애플리케이션 페이로드에 속하는 것이 아니었습니다. 바이트 스트림이 여러 라이브러리를 거칠 때 이 구분은 쉽게 잊힙니다.
문법은 Enter 키의 의미를 정하지 않았다
전송 문법은 정밀했지만, 사람의 키는 또 다른 불확실성을 만들었습니다. 누군가 리턴이나 Enter를 누를 때 사용자 텔넷(User Telnet)은 무엇을 보내야 할까요? 그것이 NVT 줄 바꿈 기능에 대한 요청인가, 아니면 로컬 CR 키를 그대로 전송하라는 요청인가?
RFC 1123은 이 실무적 분열을 기록했습니다. 일부 클라이언트는CR LF를, 다른 클라이언트는CR NUL을 보냈습니다. 기본 명세는 터미널 입력보다 프린터 출력을 더 명확히 기술했습니다. 구현체들은 서로 다르지만 각자 방어 가능한 해석을 내놓았고, 그 결과는 계속되는 상호운용성 문제가 되었습니다.
호스트 요구사항 문서는 역할과 문맥을 명명함으로써 경계를 수습했습니다. 사용자 텔넷은 CR LF, CR NUL, LF를 모두 보낼 수 있어야 했습니다. ASCII 클라이언트는 사용자가 선택할 수 있는 CR LF/CR NUL 모드를 제공하고, 줄 끝 키는 기본적으로 CR LF로 동작해야 했습니다. ASCII 서버 텔넷은 터미널 입력으로 수신될 때 CR LF와 CR NUL 모두가 로컬 줄 끝 키를 누른 것과 같은 효과를 갖도록 해야 했습니다.
마지막 규칙은 구분이 무너지는 것처럼 보이지만, 그것은 지정된 인터페이스에서만 그렇습니다. 원시(raw) 터미널 모드에서 서버는 CR을 애플리케이션에 그대로 넘길 수 있고, 포맷된 모드에서는 로컬 줄 관례를 적용할 수 있습니다. ASCII가 아닌 호스트는 여전히 홀로 나온 캐리지 리턴을 보존할 것인가와 기존 클라이언트가 내보내는 형태를 받아들일 것인가 사이의 실질적 긴장에 직면했습니다.
터미널에서 컴퓨터로 가는 입력이 아닌 트래픽—서버 출력 또는 텔넷을 통해 실려 가는 다른 애플리케이션 프로토콜—에 대해서는 RFC 1123이 줄 끝에 CR LF를 요구했습니다. 따라서 같은 옥텟이 방향과 목적에 따라 서로 다른 번역 의무를 받을 수 있었습니다.
바이너리는 더 큰 알파벳이 아니라 해석자의 교체였다
텔넷의 바이너리 옵션을 여덟 번째 데이터 비트가 추가된 NVT로 보고 싶은 유혹이 있습니다. 그러나 그것은 제어 경계를 놓치는 겁니다.RFC 856은 바이너리 전송을 각 방향별로 협상되는 상태로 만들었습니다. 한쪽이 요청하면 다른 쪽이 확인합니다. 반대 방향이 자동으로 바뀌지는 않습니다.
그 상태 안에서 일반 옥텟은 8비트 데이터입니다. 텔넷 명령 문법은 여전히 살아 있습니다: IAC가 명령을 시작하며, IAC와 같은 데이터 바이트는 두 번 반복해야 합니다. 그러나 줄 끝 변환은 멈춥니다. RFC 1123은 바이너리 모드에서 CR을 CR NUL이나 CR LF로 바꾸는 것을 명시적으로 금지했습니다.
협상된 모드가 어느 해석자가 그 바이트를 소유하는지 결정합니다. NVT ASCII에서 CR은 두 바이트짜리 줄/동작 판단을 시작합니다. 바이너리 모드에서 CR, LF, NUL은 애플리케이션 의미가 달리 말하지 않는 한 데이터로 남습니다. CR 뒤라는 이유만으로 NUL을 떼어내면 바이너리 스트림이 손상됩니다. 하나를 끼워 넣어도 마찬가지입니다.
그래서 인쇄 가능한 텍스트만 로깅하는 것은 부족합니다. 줄 끝 결함을 조사하는 운영자는 스트림의 해당 지점에서 옥텟, 방향, 옵션 상태를 필요로 합니다.
라인 모드는 출력 책임을 옮기지 않은 채 편집을 옮겼다
RFC 1184는 다른 비용을 다뤘습니다. 매 문자 단위의 상호작용은 긴 지연 링크에서는 고통스럽고, 패킷당 요금을 내는 네트워크에서는 비쌌습니다. 라인 모드(Linemode)는 클라이언트가 로컬에서 편집하고 완성된 명령줄을 보내도록 허용해, 모든 키 입력이 원격 에코를 기다리는 일을 없앴습니다.
이 옵션은 NVT 구분을 버리지 않았습니다. 라인 모드 편집이 활성화되면 정상적인 로컬 줄 종결자는 CR LF로 전송됩니다. 편집이 꺼져 있으면 캐리지 리턴은 CR NUL로, LF는 LF로, "줄 완성"을 뜻하는 특수 키는 CR LF로 전송됩니다.
서버 출력은 모든 모드에서 서버의 책임으로 남았습니다: 새 줄에는 CR LF, 캐리지 리턴 단독에는 CR NUL, 라인 피드 단독에는 LF입니다. 편집 권한이 사람에게 더 가까이 옮겨 간다고 해도, 클라이언트가 서버 출력의 의미를 만들어 내는 것은 허용되지 않았습니다.
이 분리는 유용한 설계 단서입니다. 위치와 의미론은 다른 질문입니다. 시스템은 경계에서 정준 표현을 안정적으로 유지한 채, 지연 시간 때문에 버퍼링이나 편집을 재배치할 수 있습니다.
낡은 프린터는 네트워크 텍스트 안에서 살아남았다
NVT 프린터는 골동품 비유처럼 느껴질 수 있지만, 그 줄 끝 선택은 하나의 원격 로그인 세션을 훌쩍 넓게 퍼졌습니다. 초기 인터넷 텍스트 프로토콜들은 로컬 저장과 전송 표현을 분리하는 네트워크 형태를 반복적으로 의존했습니다.
RFC 5198은 Net-Unicode를 정의하면서 이 역사를 다시 들여다봤습니다. 네트워크 텍스트의 줄 끝 형태로 CRLF를 유지했고, CR 뒤에는 LF나 NUL이 와야 한다는 구조적 요구를 보존했습니다. 그러나 CR NUL은 권장하지 않았습니다. 현대의 문자 합성과 마크업 덕분에 많은 프린터 이동 기교가 불필요해졌고, NUL은 문자열 종결자로 다루는 소프트웨어에서 위험합니다.
그렇다고 1972년의 선택이 어리석었다는 증거는 아닙니다. 이는 메커니즘이 정직하게 나이 드는 모습을 보여줍니다. 캐리지 이동이 공유 추상화의 일부였을 때 CR NUL은 실제 질문을 해결했습니다. 이후의 텍스트 형식들은 설치된 줄 경계를 유지하면서, 원래 용도가 사라진 예외 형태만 좁혔습니다.
IANA 텔넷 옵션 레지스트리는 여전히 바이너리 전송, 라인 모드, 캐리지 리턴 처리 및 이후의 문자 집합 옵션을 기록하고 있습니다. 레지스트리는 이 어휘에 안정적인 이름이 있다는 사실을 증명할 뿐, 현재 얼마나 많은 기기가 그것을 올바르게 구현하는지에 대해서는 아무것도 말하지 않습니다.
작은 문법이 하나의 헌법적 아이디어를 담았다
텔넷의 해법은 모든 터미널이 어떻게 동작해야 하는지를 한 호스트가 지시하도록 임명하지 않았습니다. 상호운용에 필요한 구분만을 공통 계층에 두었습니다. 발신 측은 두 NVT 동작 중 하나를 선택했습니다. 수신 측은 다음 바이트를 읽어 그 선택을 로컬에서 검증할 수 있었습니다. 각 측은 자신의 기기 사상에 대한 권한을 유지했습니다.
값비싼 실패는 이 경계를 넘을 때 왔습니다. Enter를 너무 일찍 사상한 클라이언트는 홀로 나온 CR의 가능성을 지웠습니다. 옵션 상태를 확인하지 않고 CR NUL을 정규화한 게이트웨이는 바이너리 데이터를 손상시켰습니다. 짝이 도착하기 전에 CR에 반응한 수신자는 프로토콜이 근거를 약속한 지점에서 추측했습니다. NUL을 문자열의 끝으로 다룬 라이브러리는 바로 그 모호성을 없애려던 시퀀스를 잘랐습니다.
교훈은 줄 끝보다 큽니다. 하나의 기호가 충분한 의미를 실을 수 없을 때, 프로토콜은 유한한 판별자를 추가하고 어느 계층이 그것을 소비하는지 밝혀야 합니다. 그 명시적 상태를 관례로 대체하는 순간 호환성은 취약해집니다.
출처와 근거의 한계
- https://www.rfc-editor.org/rfc/rfc318.html
- https://www.rfc-editor.org/rfc/rfc854.html
- https://www.rfc-editor.org/rfc/rfc856.html
- https://www.rfc-editor.org/rfc/rfc1123.html
- https://www.rfc-editor.org/rfc/rfc1184.html
- https://www.rfc-editor.org/rfc/rfc5198.html
- https://www.iana.org/assignments/telnet-options/telnet-options.xhtml
이 출처들은 프로토콜 모델, 역사적 규칙, 호스트 요구사항 명확화, 협상된 예외, 등록된 옵션 어휘를 확립합니다. 그러나 현재의 텔넷 사용을 측정하거나, 제품의 기본 설정을 인증하거나, 모든 역사적 구현체가 똑같이 동작했음을 확립하지는 않습니다. 따라서 이 글은 CR NUL을 텍스트의 보편적 속성이 아니라 범위가 지정된 표현 규칙으로 다룹니다.
회원 브리핑
프로필 심층 컨텍스트
올바른 멤버십 등급으로 로그인하면 전체 브리핑과 출처 노트를 볼 수 있습니다.
Strategic Circle 전용
Strategic Circle
모든 독자에게 공개됩니다. 가입 후 로그인하면 프로필 브리핑를 열람할 수 있습니다.
Strategic Circle 가입하기Leadership Alliance 전용
Leadership Alliance
자격을 갖춘 IP 자산 소유자 및 운영진을 위한 콘텐츠입니다. 로그인하면 Leadership Alliance 브리핑을 열람할 수 있습니다.
Leadership Alliance 가입하기





