Redian新闻
>
当你在终端上按下一个键时会发生什么? | Linux 中国

当你在终端上按下一个键时会发生什么? | Linux 中国

科技
 
导读:当你在终端中按下键盘上的一个键(比如 Delete,或 Escape,或 a),发送了哪些字节?   
本文字数:7062,阅读时长大约:9分钟

我对终端(Terminal)是怎么回事困惑了很久。

但在上个星期,我使用 xterm.js🔗 xtermjs.org 在浏览器中显示了一个交互式终端,我终于想到要问一个相当基本的问题:当你在终端中按下键盘上的一个键(比如 Delete,或 Escape,或 a),发送了哪些字节?

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

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

首先,我想说的是,用 xterm.js 在浏览器中显示一个终端可能看起来像一个新事物,但它真的不是。在 70 年代,计算机很昂贵。因此,一个机构的许多员工会共用一台电脑,每个人都可以有自己的 “终端” 来连接该电脑。

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

DEC VT100终端

当然,在 70 年代,他们并没有使用 Websocket 来做这个,但来回发送的信息的方式和当时差不多。

(照片中的终端是来自西雅图的 活电脑博物馆🔗 livingcomputers.org(Living Computer Museum),我曾经去过那里,并在一个非常老的 Unix 系统上用 ed 编写了 FizzBuzz,所以我有可能真的用过那台机器或它的一个兄弟姐妹!我真的希望活电脑博物馆能再次开放,能玩到老式电脑是非常酷的。)

发送了什么信息?

很明显,如果你想连接到一个远程计算机(用 ssh 或使用 xterm.js 和 Websocket,或其他任何方式),那么需要在客户端和服务器之间发送一些信息。

具体来说:

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

让我们看看一个真正的程序,它在浏览器中运行一个远程终端,看看有哪些信息会被来回发送!

我们将使用 goterm 来进行实验

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

我 复刻了它🔗 github.com,使它能与最新的 xterm.js 一起工作,因为它最后一次更新是在 6 年前。然后,我添加了一些日志语句,以打印出每次通过 WebSocket 发送/接收的字节数。

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

示例:ls

首先,让我们运行 ls。下面是我在 xterm.js 终端上看到的情况:

  1. ~:/play$ ls
  2. file
  3. ~:/play$

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

  1. sent: "l"
  2. recv: "l"
  3. sent: "s"
  4. recv: "s"
  5. sent: "\r"
  6. recv: "\r\n\x1b[?2004l\r"
  7. recv: "file\r\n"
  8. recv: "\x1b[~:/play$ "

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

1. 回显:客户端发送 l,然后立即收到一个 l 发送回来。我想这里的意思是,客户端真的很笨 —— 它不知道当我输入l 时,我想让 l 被回显到屏幕上。它必须由服务器进程明确地告诉它来显示它。
2. 换行:当我按下回车键时,它发送了一个 \r'(回车)符号,而不是\n'(换行)。
3. 转义序列:\x1b 是 ASCII 转义字符,所以 \x1b[?2004h 是告诉终端显示什么或其他东西。我想这是一个颜色序列,但我不确定。我们稍后会详细讨论转义序列。

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

示例:Ctrl+C

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

  1. ~:/play$ cat
  2. ^C
  3. ~:/play$

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

  1. sent: "c"
  2. recv: "c"
  3. sent: "a"
  4. recv: "a"
  5. sent: "t"
  6. recv: "t"
  7. sent: "\r"
  8. recv: "\r\n\x1b[?2004l\r"
  9. sent: "\x03"
  10. recv: "^C"
  11. recv: "\r\n"
  12. recv: "\x1b[?2004h"
  13. recv: "~:/play$ "

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

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

示例:Ctrl+D

让我们试试完全相同的事情,只是用 Ctrl+D。下面是我在终端看到的情况:

  1. ~:/play$ cat
  2. ~:/play$

而这里是发送和接收的内容:

  1. sent: "c"
  2. recv: "c"
  3. sent: "a"
  4. recv: "a"
  5. sent: "t"
  6. recv: "t"
  7. sent: "\r"
  8. recv: "\r\n\x1b[?2004l\r"
  9. sent: "\x04"
  10. recv: "\x1b[?2004h"
  11. recv: "~:/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 个字母)
◈ Escape -> \x1b
◈ Backspace -> \x7f
◈ Home -> \x1b[H
◈ End -> \x1b[F
◈ Print Screen -> \x1b\x5b\x31\x3b\x35\x41
◈ Insert -> \x1b\x5b\x32\x7e
◈ Delete -> \x1b\x5b\x33\x7e
◈ 我的 Meta 键完全没有作用

那 Alt 呢?根据我的实验(和一些搜索),似乎 Alt 和 Escape 在字面上是一样的,只是按 Alt 本身不会向终端发送任何字符,而按 Escape 本身会。所以:

◈ alt + d => \x1bd(其他每个字母都一样)
◈ alt + shift + d => \x1bD(其他每个字母都一样)
◈ 诸如此类

让我们再看一个例子!

示例:nano

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

  1. recv: "\r\x1b[~:/play$ "
  2. sent: "n" [[]byte{0x6e}]
  3. recv: "n"
  4. sent: "a" [[]byte{0x61}]
  5. recv: "a"
  6. sent: "n" [[]byte{0x6e}]
  7. recv: "n"
  8. sent: "o" [[]byte{0x6f}]
  9. recv: "o"
  10. sent: "\r" [[]byte{0xd}]
  11. recv: "\r\n\x1b[?2004l\r"
  12. recv: "\x1b[?2004h"
  13. 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"
  14. recv: "\x1b[39;49m\x1b(B\x1b[m\x1b[H\x1b[2J"
  15. 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"

你可以看到一些来自用户界面的文字,如 “GNU nano 6.2”,而这些 \x1b[27m 的东西是转义序列。让我们来谈谈转义序列吧!

ANSI 转义序列

上面这些 nano 发给客户端的 \x1b[ 东西被称为“转义序列”或 “转义代码”。这是因为它们都是以 “转义”字符 \x1b 开头。它们可以改变光标的位置,使文本变成粗体或下划线,改变颜色,等等。维基百科介绍了一些历史🔗 en.wikipedia.org,如果你有兴趣的话可以去看看。

举个简单的例子:如果你在终端运行

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

它将打印出 “hi there”,其中 “hi” 是红色的,“there” 是黑色的。本页🔗 misc.flogisoft.com 有一些关于颜色和格式化的转义代码的例子。

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

转义代码是为什么你的终端会被搞乱的原因,如果你 cat 一些二进制数据到你的屏幕上 —— 通常你会不小心打印出一堆随机的转义代码,这将搞乱你的终端 —— 如果你 cat 足够多的二进制数据到你的终端,那里一定会有一个 0x1b 的字节。

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

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

如果我在 xterm.js 终端手动键入 Escape ,然后是 [,然后是 H,我就会出现在行的开头,与我按下 Home 完全一样。

我注意到这在我的电脑上的 Fish shell 中不起作用 —— 如果我键入 Escape,然后输入 [,它只是打印出 [,而不是让我继续转义序列。我问了我的朋友 Jesse,他写过 一堆 Rust 终端代码🔗 github.com,Jesse 告诉我,很多程序为转义代码实现了一个 超时 —— 如果你在某个最小的时间内没有按下另一个键,它就会决定它实际上不再是一个转义代码了。

显然,这在 Fish shell 中可以用 fish_escape_delay_ms 来配置,所以我运行了 set fish_escape_delay_ms 1000,然后我就能用手输入转义代码了。工作的很好!

终端编码有点奇怪

我想在这里暂停一下,我觉得你按下的键被映射到字节的方式是非常奇怪的。比如,如果我们今天从头开始设计按键的编码方式,我们可能不会把它设置成这样:

◈ Ctrl + a 和 Ctrl + Shift + a 做的事情完全一样。
◈ Alt 与 Escape 是一样的
◈ 控制序列(如颜色/移动光标)使用与 Escape 键相同的字节,因此你需要依靠时间来确定它是一个控制序列还是用户只是想按 Escape

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

改变窗口大小

在终端中,并不是所有你能做的事情都是通过来回发送字节发生的。例如,当终端被调整大小时,我们必须以不同的方式告诉 Linux 窗口大小已经改变。

下面是 goterm🔗 github.com 中用来做这件事的 Go 代码的样子:

  1. syscall.Syscall(
  2. syscall.SYS_IOCTL,
  3. tty.Fd(),
  4. syscall.TIOCSWINSZ,
  5. uintptr(unsafe.Pointer(&resizeMessage)),
  6. )

这是在使用 ioctl 系统调用。我对 ioctl 的理解是,它是一个系统调用,用于处理其他系统调用没有涉及到的一些随机的东西,通常与 IO 有关,我猜。

syscall.TIOCSWINSZ 是一个整数常数,它告诉 ioctl 我们希望它在本例中做哪件事(改变终端的窗口大小)。

这也是 xterm 的工作方式。

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

文章到此结束啦

关于终端,肯定还有很多东西要了解(我们可以讨论更多关于颜色,或者原始与熟化模式,或者 Unicode 支持,或者 Linux 伪终端界面),但我将在这里停止,因为现在是晚上 10 点,这篇文章有点长,而且我认为我的大脑今天无法处理更多关于终端的新信息。

感谢 Jesse Luehrs🔗 github.com 回答了我关于终端的十亿个问题,所有的错误都是我的 :smiley:


via: https://jvns.ca/blog/2022/07/20/pseudoterminals/

作者:Julia Evans 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出


LCTT 译者 :Xingyu.Wang
💎💎💎
翻译: 869.0 篇
|
贡献: 2922 天
2014-07-25
2022-07-25
https://linux.cn/lctt/wxy
欢迎遵照 CC-BY-SA 协议规定转载,
如需转载,请在文章下留言 “转载:公众号名称”,
我们将为您添加白名单,授权“转载文章时可以修改”。

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
Linux 内核 5.18 版本正式发布,新增显卡驱动以及硬件支持 | Linux 中国小聚连续看一个月小电影,身体会发生什么变化?🤫用 ranger 在 Linux 终端管理你的文件 | Linux 中国一晚上不睡觉,身体会发生什么?诺基亚勒令一个开源 Linux 手机项目 “NOTKIA” 改名字 | Linux 中国突破能量禁忌的项目。。。临死前24小时,你的身体会发生什么奇怪反应?如何在 Fedora Linux 中安装多媒体编码器 | Linux 中国坚持阅读《经济学人》40年,会发生什么?春心荡漾,返老还童(十二)在 Linux 上使用 Bash 创建一个临时文件 | Linux 中国Linux 优先的 AI 图像提升器 Upscayl 发布了第一个版本 | Linux 中国突发!96岁英国女王伊丽莎白二世去世后会发生什么?吃饭重口味,身体会发生什么变化?有了扩展,GNOME Web 正逐渐成为 Linux 桌面上一个有吸引力的选择 | Linux 中国【人人原创】《一曲新词酒一杯》 (浣溪沙-玉楼春) 晏殊词 唐歌演唱 雅歌曲CentOS 的继承者 AlmaLinux 9 发布 | Linux 中国开源朗读者:我为什么从 Mac 转到 Linux | Linux 中国安倍遇袭之后,会发生什么?当房地产企业开始退地,楼市会发生什么?神秘的 GeckoLinux 创建者推出了一个新的 Debian 合成发行版 | Linux 中国《无名姐妹》:禁止堕胎的美国会发生什么?System76 与惠普合作为开发者提供功能强大的 Linux 笔记本电脑 | Linux 中国Blackbox:极简主义 Linux 用户的美观终端 | Linux 中国关于 Linux 和 Git 的创造者 Linus Torvalds 的 20 件趣事 | Linux 中国女王去世后英国会发生什么?深度 I 拜登刚检出新冠阳性,接下来会发生什么?“作弊”:只需要知道这一个 Linux 命令就够了 | Linux 中国生活中那些不能随便碰的按钮,按下去会发生什么?大家乐港式快餐Linux 中国开通播客频道:“开源朗读者”和“硬核观察” | Linux 中国当得房率达到100%,将会发生什么Linux 终端,它不可怕,拥抱它 | Linux 中国“32岁、离婚3次、裸辞”当一个单亲妈妈决定为自己而活,会发生什么?
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。