Skip to content

搞英语 → 看世界

翻译英文优质信息和名人推特

Menu
  • 首页
  • 作者列表
  • 独立博客
  • 专业媒体
  • 名人推特
  • 邮件列表
  • 关于本站
Menu

当您在终端中按下一个键时会发生什么?

Posted on 2022-07-21

很长一段时间以来,我一直对终端发生的事情感到困惑。

但上周我使用xterm.js在浏览器中显示交互式终端,我终于想问一个非常基本的问题:当您在终端中按下键盘上的一个键时(例如Delete或Escape或a ) , 哪些字节被发送?

像往常一样,我们将通过做一些实验并看看会发生什么来回答这个问题:)

远程终端是非常古老的技术

首先,我想说的是,使用xterm.js在浏览器中显示终端可能看起来像是一个新事物,但实际上并非如此。在 70 年代,计算机价格昂贵。一个机构的这么多员工将共享一台计算机,每个人都可以拥有自己的“终端”到那台计算机。

例如,这是一张 70 或 80 年代 VT100 终端的照片。这看起来可能是一台计算机(它有点大!),但它不是——它只是显示实际计算机发送给它的任何信息。

DEC VT100 终端

当然,在 70 年代他们并没有为此使用 websockets,但是来回发送的信息与当时差不多。

(那张照片中的终端来自西雅图的生活计算机博物馆,我曾经参观过一次,并在一个非常古老的 Unix 系统上编写了ed ​​zz,所以我可能真的使用过那台机器或它的兄弟姐妹之一!我真希望活电脑博物馆能重新开张,能玩旧电脑真是太爽了。)

什么信息被发送?

很明显,如果您想连接到远程计算机(使用ssh或使用xterm.js和 websocket 或其他任何东西),则需要在客户端和服务器之间发送一些信息。

具体来说:

  • 客户端需要发送用户输入的击键(如ls -l )
  • 服务器需要告诉客户端在屏幕上显示什么

让我们看一个在浏览器中运行远程终端的真实程序,看看来回发送什么信息!

我们将使用goterm进行实验

我在 GitHub 上发现了一个名为goterm的小程序,它运行一个 Go 服务器,让您可以使用xterm.js与浏览器中的终端进行交互。这个程序非常不安全,但它很简单,非常适合学习。

我对它进行了 fork 以使其与最新的 xterm.js 一起使用,因为它上次更新是 6 年前。然后,我添加了一些日志语句以在每次通过 websocket 发送/接收字节时打印出来。

让我们看看在几个不同的终端交互过程中发送和接收!

示例: ls

首先,让我们运行ls 。这是我在xterm.js终端上看到的内容:

 bork@kiwi:/play$ ls file bork@kiwi:/play$

这是发送和接收的内容:(在我的代码中,我记录sent: [bytes]每次客户端发送字节时的recv: [bytes]每次从服务器接收字节时的 [bytes])

 sent: "l" recv: "l" sent: "s" recv: "s" sent: "\r" recv: "\r\n\x1b[?2004l\r" recv: "file\r\n" recv: "\x1b[?2004hbork@kiwi:/play$ "

我在这个输出中注意到 3 件事:

  1. 回显:客户端发送l ,然后立即收到一个回传的l 。我想这里的想法是客户端真的很笨——它不知道当我输入l时,我希望l回显到屏幕上。服务器进程必须明确告知它才能显示它。
  2. 换行符:当我按下回车键时,它发送一个\r (回车)符号而不是一个\n (换行符)
  3. 转义序列: \x1b是 ASCII 转义字符,因此\x1b[?2004h告诉终端显示某些内容或其他内容。我认为这是一个颜色序列,但我不确定。稍后我们将更多地讨论转义序列。

好的,现在让我们做一些稍微复杂一点的事情。

示例: Ctrl+C

接下来,让我们看看当我们使用Ctrl+C中断进程时会发生什么。这是我在终端中看到的:

 bork@kiwi:/play$ cat ^C bork@kiwi:/play$

这是客户端发送和接收的内容。

 sent: "c" recv: "c" sent: "a" recv: "a" sent: "t" recv: "t" sent: "\r" recv: "\r\n\x1b[?2004l\r" sent: "\x03" recv: "^C" recv: "\r\n" recv: "\x1b[?2004h" recv: "bork@kiwi:/play$ "

当我按Ctrl+C时,客户端发送\x03 。如果我查找一个 ASCII 表, \x03是“文本结尾”,这似乎是合理的。我认为这真的很酷,因为我一直对 Ctrl+C 的工作原理有点困惑——很高兴知道它只是发送一个\x03字符。

我相信当我们按下Ctrl+C时cat被打断的原因是服务器端的 Linux 内核接收到这个\x03字符,识别它意味着“中断”,然后向拥有伪终端进程组的进程发送一个SIGINT .所以它是在内核中处理的,而不是在用户空间中处理的。

示例: Ctrl+D

让我们尝试完全相同的事情,除了Ctrl+D 。这是我在终端中看到的:

 bork@kiwi:/play$ cat bork@kiwi:/play$

以下是发送和接收的内容:

 sent: "c" recv: "c" sent: "a" recv: "a" sent: "t" recv: "t" sent: "\r" recv: "\r\n\x1b[?2004l\r" sent: "\x04" recv: "\x1b[?2004h" recv: "bork@kiwi:/play$ "

它与Ctrl+C非常相似,除了发送\x04而不是\x03 。凉爽的! \x04对应于 ASCII“传输结束”。

Ctrl + 另一个字母呢?

接下来我很好奇——如果我发送Ctrl+e ,会发送什么字节?

事实证明,它实际上只是字母表中该字母的编号,如下所示:

  • Ctrl+a => 1
  • Ctrl+b => 2
  • Ctrl+c => 3
  • Ctrl+d => 4
  • …
  • Ctrl+z => 26

此外, Ctrl+Shift+b的作用与Ctrl+b完全相同(它写入0x2 )。

键盘上的其他键呢?这是他们映射到的内容:

  • Tab -> 0x9(与 Ctrl+I 相同,因为 I 是第 9 个字母)
  • 转义 -> \x1b
  • 退格 -> \x7f
  • 主页 -> \x1b[H
  • 结束: \x1b[F
  • 打印屏幕: \x1b\x5b\x31\x3b\x35\x41
  • 插入: \x1b\x5b\x32\x7e
  • 删除 -> \x1b\x5b\x33\x7e
  • 我的Meta键什么都不做

阿尔特呢?从我的实验(和一些谷歌搜索)来看, Alt似乎与“Escape”字面意思相同,只是单独按Alt不会向终端发送任何字符,而单独按Escape则可以。所以:

  • alt + d => \x1bd (其他每个字母都一样)
  • alt + shift + d => \x1bD (其他字母相同)
  • 等等

让我们再看一个例子!

示例: nano

这是我运行文本编辑器nano时发送和接收的内容:

 recv: "\r\x1b[Kbork@kiwi:/play$ " sent: "n" [[]byte{0x6e}] recv: "n" sent: "a" [[]byte{0x61}] recv: "a" sent: "n" [[]byte{0x6e}] recv: "n" sent: "o" [[]byte{0x6f}] recv: "o" sent: "\r" [[]byte{0xd}] recv: "\r\n\x1b[?2004l\r" recv: "\x1b[?2004h" recv: "\x1b[?1049h\x1b[22;0;0t\x1b[1;16r\x1b(B\x1b[m\x1b[4l\x1b[?7h\x1b[39;49m\x1b[?1h\x1b=\x1b[?1h\x1b=\x1b[?25l" recv: "\x1b[39;49m\x1b(B\x1b[m\x1b[H\x1b[2J" recv: "\x1b(B\x1b[0;7m GNU nano 6.2 \x1b[44bNew Buffer \x1b[53b \x1b[1;123H\x1b(B\x1b[m\x1b[14;38H\x1b(B\x1b[0;7m[ Welcome to nano. For basic help, type Ctrl+G. ]\x1b(B\x1b[m\r\x1b[15d\x1b(B\x1b[0;7m^G\x1b(B\x1b[m Help\x1b[15;16H\x1b(B\x1b[0;7m^O\x1b(B\x1b[m Write Out \x1b(B\x1b[0;7m^W\x1b(B\x1b[m Where Is \x1b(B\x1b[0;7m^K\x1b(B\x1b[m Cut\x1b[15;61H"

您可以在其中看到一些来自 UI 的文本,例如“GNU nano 6.2”,这些\x1b[27m内容是转义序列。让我们稍微谈谈转义序列!

ANSI 转义序列

vim向客户端发送的这些\x1b[内容称为“转义序列”或“转义码”。这是因为它们都以“转义”字符\x1b 。 .他们改变光标的位置,使文本加粗或加下划线,改变颜色等。如果你感兴趣的话,维基百科有一些历史。

作为一个简单的例子:如果你运行

echo -e '\e[0;31mhi\e[0m there'

在您的终端中,它会打印出“hi there”,其中“hi”为红色,“there”为黑色。这个页面有一些很好的颜色和格式转义码的例子。

我认为转义码有几种不同的标准,但我的理解是人们在 Unix 上使用的最常见的转义码集来自 VT100(博文顶部图片中的那个旧终端),并且在过去的 40 年里并没有真正改变太多。

转义码就是为什么如果你将cat二进制文件放到屏幕上,你的终端会变得混乱——通常你最终会不小心打印出一堆随机的转义码,这会弄乱你的终端——里面肯定有一个0x1b字节如果您cat足够的二进制文件添加到终端,则在某个地方。

你可以手动输入转义序列吗?

前几节,我们讨论了Home键如何映射到\x1b[H 。这 3 个字节是Escape + [ + H (因为 Escape 是\x1b )。

如果我在xterm.js终端中手动键入 Escape,然后是 [,然后是 H,我会在行首结束,就像我按Home一样。

我注意到这在我的计算机上的fish中不起作用——如果我输入Escape然后[ ,它只是打印出[而不是让我继续转义序列。我问了我的朋友 Jesse,他写了一堆 Rust 终端代码,Jesse 告诉我很多程序都实现了转义码的超时——如果你在最短的时间后不按另一个键,它会确定它实际上不再是转义码。

显然这可以在 fish 中使用fish_escape_delay_ms进行配置,所以我运行了set fish_escape_delay_ms 1000然后我可以手动输入转义码。凉爽的!

终端编码有点奇怪

我想在这里暂停一分钟,并说您按下的键映射到字节的方式非常奇怪。就像,如果我们今天从头开始设计密钥的编码方式,我们可能不会这样设置:

  • Ctrl + a作用与Ctrl + Shift + a完全相同
  • Alt与Escape相同
  • 控制序列(如颜色/移动光标)使用与Escape键相同的字节,因此您需要依靠时间来确定它是否是用户的控制序列,只是打算按Escape

但是所有这些都是在 70 年代或 80 年代或其他什么时候设计的,然后为了向后兼容需要永远保持不变,所以这就是我们得到的 🙂

改变窗口大小

并非您在终端中可以做的所有事情都是通过来回发送字节来实现的。例如,当终端调整大小时,我们必须告诉 Linux 窗口大小以不同的方式发生了变化。

下面是goterm中的 Go 代码的样子:

 syscall.Syscall( syscall.SYS_IOCTL, tty.Fd(), syscall.TIOCSWINSZ, uintptr(unsafe.Pointer(&resizeMessage)), )

这是使用ioctl系统调用。我对ioctl的理解是,它是对一堆其他系统调用未涵盖的随机内容的系统调用,我猜通常与 IO 相关。

syscall.TIOCSWINSZ是一个整数常量,它告诉ioctl在这种情况下我们希望它做什么(更改终端的窗口大小)。

这也是 xterm 的工作原理

在这篇文章中,我们一直在讨论远程终端,其中客户端和服务器位于不同的计算机上。但实际上,如果您使用像xterm这样的终端仿真器,所有这些都以完全相同的方式工作,只是更难注意到,因为字节不是通过网络连接发送的。

目前为止就这样了!

关于终端肯定还有很多要了解的(我们可以讨论更多关于颜色、原始模式与熟模式、unicode 支持或 Linux 伪终端接口),但我会在这里停下来,因为现在是晚上 10 点,这有点长,而且我认为我的大脑今天无法处理更多关于终端的新信息。

感谢Jesse Luehrs回答了我关于终端的十亿个问题,所有的错误都是我的 🙂

原文: https://jvns.ca/blog/2022/07/20/pseudoterminals/

本站文章系自动翻译,站长会周期检查,如果有不当内容,请点此留言,非常感谢。
  • Abhinav
  • Abigail Pain
  • Adam Fortuna
  • Alberto Gallego
  • Alex Wlchan
  • Answer.AI
  • Arne Bahlo
  • Ben Carlson
  • Ben Kuhn
  • Bert Hubert
  • Bits about Money
  • Brian Krebs
  • ByteByteGo
  • Chip Huyen
  • Chips and Cheese
  • Christopher Butler
  • Colin Percival
  • Cool Infographics
  • Dan Sinker
  • David Walsh
  • Dmitry Dolzhenko
  • Dustin Curtis
  • Elad Gil
  • Ellie Huxtable
  • Ethan Marcotte
  • Exponential View
  • FAIL Blog
  • Founder Weekly
  • Geoffrey Huntley
  • Geoffrey Litt
  • Greg Mankiw
  • Henrique Dias
  • Hypercritical
  • IEEE Spectrum
  • Investment Talk
  • Jaz
  • Jeff Geerling
  • Jonas Hietala
  • Josh Comeau
  • Lenny Rachitsky
  • Liz Danzico
  • Lou Plummer
  • Luke Wroblewski
  • Matt Baer
  • Matt Stoller
  • Matthias Endler
  • Mert Bulan
  • Mostly metrics
  • News Letter
  • NextDraft
  • Non_Interactive
  • Not Boring
  • One Useful Thing
  • Phil Eaton
  • Product Market Fit
  • Readwise
  • ReedyBear
  • Robert Heaton
  • Rohit Patel
  • Ruben Schade
  • Sage Economics
  • Sam Altman
  • Sam Rose
  • selfh.st
  • Shtetl-Optimized
  • Simon schreibt
  • Slashdot
  • Small Good Things
  • Taylor Troesh
  • Telegram Blog
  • The Macro Compass
  • The Pomp Letter
  • thesephist
  • Thinking Deep & Wide
  • Tim Kellogg
  • Understanding AI
  • 英文媒体
  • 英文推特
  • 英文独立博客
©2025 搞英语 → 看世界 | Design: Newspaperly WordPress Theme