대부분의 프로그램들은 그래픽 형태로 보여주는 GUI(Graphics User Interface) 방식입니다. 하지만 일부 프로그램은 터미널의 검은 화면 바탕인 CLI(Command Line Interface) 방식으로 보여줍니다(ex. git, mysql cli, aws cli 등등). 저는 CLI 환경이 GUI 환경에 못지 않게 기능들을 제공하고 CLI 환경이 더 가볍게 사용할 수 있을 것 같은 믿음(?)으로 CLI 환경을 좋아합니다. 서버 작업을 하다보면 자연스럽게 터미널 환경을 많이 접해서 그런지 오히려 그래픽 환경보다 터미널 환경이 편할 때도 있습니다.

기능이 많은 OpenSSL  프로그램은 CLI 환경이 더 편할지도 모릅니다 🥲 출처

기능이 많은 OpenSSL 프로그램은 CLI 환경이 더 편할지도 모릅니다 🥲 출처

터미널의 시-꺼먼 환경을 좋아하는 저한테는, 웹 터미널은 반갑고 흥미로운 아이입니다. 예전에 인턴을 하면서 웹 터미널을 잠깐 구현할 기회가 있어서 그 때의 기억을 더듬어 socketIO, xtermjs, node-pty 모듈을 통해 웹 터미널을 구현 해보고 한술 더 떠서 rollup을 이용하여 라이브러리로 만들어보는 시간을 가지도록 하겠습니다. 웹 터미널을 구현하기 전에 terminal, tty, shell와 같은 헷갈리는 용어 정리와 리눅스 pty 작동 방식에 대해 알아보면 좋습니다.

Terminal, tty, shell 이 세 단어는 모두 같은 의미를 뜻하는 용어 같은데 각각의 의미 차이가 궁금했습니다. 먼저 terminal(이하 터미널)부터 알아보도록 합시다. 초기의 터미널이라는 용어는 데이터를 입력하거나 출력하는데 쓰이는 하드웨어를 말했습니다. ‘종료하다', ‘종점에 닿는다'라는 의미를 가지는 terminal의 동사 형태인 terminate 단어에서 볼 수 있듯이 통신의 처음과 마지막에 위치하는 장치를 터미널이라고 합니다. 하드웨어 형태의 터미널도 있지만 기술의 발전으로 컴퓨터 커널의 도움으로 물리적인 터미널을 ssh, xterm, puTTY와 같은 소프트웨어로 모방한(emulate) 터미널도 있습니다. 이런 아이들을 terminal emulator라고 하며, terminal emulator를 통해 접속하면 가상 터미널(pty, pesudo tty)이 만들어집니다. 반면에 시리얼 포트와 같이 하드웨어에 직접적으로 연결된 터미널을 tty(Teletype)라 합니다[1]. 즉, 컴퓨터에서 터미널이라고 하면 넓은 의미로는 컴퓨터와 사용자 사이를 소통해주는 물건을(하드웨어든, 소프트웨어든) 말하고 좁은 의미로는 흔히 말하는 터미널 검은 창의 CLI 환경을 말합니다.

비슷한 용어인 console(이하 콘솔)이라는 아이도 있는데 터미널과 콘솔을 동의어라 생각하셔도 될 것 같습니다. 콘솔이라고 하면 Xbox, Playstation과 같은 게임 전용 단말기를 떠올릴 수 있는데 소프트웨어적인 의미보다 하드웨어적인 의미가 내포있는 단어인 것 같습니다.

Shell(이하 쉘)은 문자, 명령어를 처리하고 해석해주는 프로그램입니다. 터미널은 컴퓨터와 사용자 간의 창구이지만 터미널만 ****가지고 둘 사이를 소통을 할 수 없습니다. 쉘은 명령어 해석을 하거나 프로그램을 실행하여 그 결과값을 출력하는 역할을 합니다. 보통 사용자는 터미널과 쉘을 활용한 CLI 환경에서 컴퓨터와 소통합니다. 쉘의 예로 sh, bash, zsh 등이 있습니다.

웹 터미널을 구현하는데 쓸 socketIO, xtermjs, node-pty 모듈들을 살펴봅시다. xtermjsGitHub readme는 다음과 같이 설명합니다.

Xterm.js is a front-end component written in TypeScript that lets applications bring fully-featured terminals to their users in the browser.

xtermjs는 ‘터미널’로 웹 브라우저에서 사용자가 키보드를 통해 입력을 하면 그저 문자를 출력을 하는 역할만 합니다. xtermjs만 가지고 bash와 같이 명령어 해석이나 프로그램을 실행시킬 수 없습니다. bash와 같이 프로그램을 실행할 수 있도록 만들려면 리눅스 서버에 pty(가상 터미널)를 만들고, 이를 통해 xtermjs과 쉘 사이를 연결하여 데이터를 주고 받아야합니다. node-pty는 pty를 생성하여 쉘과 통신할 수 있도록 합니다. xtermjsnode-pty 사이에는 socketIO 모듈로 소켓 통신을 하여 실시간으로 데이터를 주고 받습니다. 이를 정리하면 다음과 같이 나타낼 수 있습니다.

웹 터미널 통신 구조

웹 터미널 통신 구조

리눅스 운영체제가 설치되어 있는 컴퓨터의 터미널(tty)을 통해서나 ssh로 리눅스 서버에 원격으로 접속하여 가상 터미널(pty)을 통해 명령어를 실행해 본 경험이 있을겁니다. 이 때, 검은 창 터미널에 문자 ‘A’를 입력하면 바로 화면에 ‘A’가 출력되는 것이 아니라 여러 단계를 거쳐 화면에 보이게됩니다.

내부 터미널 동작 방식[2]

내부 터미널 동작 방식[2]

사용자가 키보드를 통해 입력을 하면 키보드 디바이스 드라이버를 통해 키보드를 제어하고 문자를 처리(Line discipline)한 뒤[3] bash(User process)가 문자 캐릭터들을 해석하고 그 결과를 터미널(Terminal emulator)로 보내 VGA 그래픽의 도움으로 화면에 랜더링되는 과정을 거칩니다. Line discipline은 단순히 문자를 처리할 뿐만 아니라 backspace(^?, ^H), enter(^M), 인터럽트(^C)와 같은 문자를 처리할 수 있으며, enter를 눌렀을 때 입력 값을 LF 형식으로 변환합니다. Line discipline은 사용자가 잘못 입력한 문자를 지우거나 space로 빈 칸을 만드는 line editing의 기능을 합니다. 내부 터미널은 위처럼 동작하지만 xtermjsssh와 같은 외부 터미널은 다르게 동작합니다.

외부 터미널 동작 방식[4]

외부 터미널 동작 방식[4]

xterm는 리눅스 pty master side(이하 ptm)에 붙고 중간에 Line discipline을 두고 pty slave side(이하 pts)에 연결됩니다. pts는 bash(User process)와 같은 프로세스와 통신하게 됩니다. 복잡하게 생겼지만 하나의 예를 보도록 합시다[5].