2 Star 13 Fork 3

advanceflow/Elisp

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
该仓库未声明开源许可证文件(LICENSE),使用请关注具体项目描述及其代码上游依赖。
克隆/下载
39-进程.org 125.73 KB
一键复制 编辑 原始数据 按行查看 历史
advanceflow 提交于 2022-05-28 10:12 . finish s2

39 进程

在操作系统的术语中,进程是程序可以执行的空间。 Emacs 在一个进程中运行。 Emacs Lisp 程序可以在自己的进程中调用其他程序。 这些被称为 Emacs 进程的子进程或子进程,这是它们的父进程。

Emacs 的子进程可能是同步的或异步的,这取决于它是如何创建的。 当您创建同步子进程时,Lisp 程序会等待子进程终止,然后再继续执行。 当您创建一个异步子进程时,它可以与 Lisp 程序并行运行。 这种子进程在 Emacs 中由 Lisp 对象表示,该对象也称为“进程”。 Lisp 程序可以使用这个对象与子进程通信或控制它。 例如,您可以发送信号、获取状态信息、接收进程的输出或向其发送输入。

除了运行程序的进程之外,Lisp 程序还可以打开多种类型的连接到在同一台机器或其他机器上运行的设备或进程。 支持的连接类型有:TCP 和 UDP 网络连接、串口连接和管道连接。 每个这样的连接也由一个过程对象表示。

Function: processp object ¶

如果 object 表示 Emacs 进程对象,则此函数返回 t,否则返回 nil。 进程对象可以表示运行程序的子进程或任何受支持类型的连接。

除了当前 Emacs 会话的子进程之外,您还可以访问在您的机器上运行的其他进程。 请参阅访问其他进程。

39.1 创建子进程的函数

有三个原语创建了一个新的子进程来运行程序。 其中之一,make-process,创建一个异步进程并返回一个进程对象(请参阅创建异步进程)。 另外两个,call-process 和 call-process-region,创建一个同步进程并且不返回一个进程对象(参见创建一个同步进程)。 有各种高级函数利用这些原语来运行特定类型的进程。

同步和异步进程将在以下部分进行说明。 由于这三个函数都以类似的方式调用,因此这里描述了它们的共同参数。

在所有情况下,函数指定要运行的程序。 如果找不到文件或无法执行文件,则会发出错误信号。 如果文件名是相对的,则变量 exec-path 包含要搜索的目录列表。 Emacs 在启动时根据环境变量 PATH 的值初始化 exec-path。 标准文件名结构“~”、“.”和“..”在 exec-path 中照常解释,但无法识别环境变量替换(“$HOME”等); 使用替换文件名来执行它们(请参阅扩展文件名的函数)。 此列表中的 nil 指的是默认目录。

执行程序也可以尝试给指定名称加后缀:

User Option: exec-suffixes ¶

此变量是一个后缀(字符串)列表,可尝试添加到指定的程序文件名。 如果您希望完全按照指定的名称尝试该名称,则该列表应包含“”。 默认值取决于系统。

请注意:参数 program 仅包含程序文件的名称; 它可能不包含任何命令行参数。 您必须使用单独的参数 args 来提供这些参数,如下所述。

每个创建子进程的函数都有一个缓冲区或名称参数,用于指定程序的输出将去往何处。 它应该是一个缓冲区或缓冲区名称; 如果它是一个缓冲区名称,如果它不存在,它将创建缓冲区。 它也可以是 nil,表示丢弃输出,除非自定义过滤器函数处理它。 (请参阅进程过滤器函数,以及读取和打印 Lisp 对象。)通常,您应该避免让多个进程将输出发送到同一个缓冲区,因为它们的输出会随机混合。 对于同步进程,您可以将输出发送到文件而不是缓冲区(因此相应的参数更适合称为目标)。 默认情况下,标准输出和标准错误流都去同一个目的地,但是所有 3 个原语都允许选择性地将标准错误流导向不同的目的地。

所有三个子进程创建函数都允许为要运行的进程指定命令行参数。 对于 call-process 和 call-process-region,它们以 &rest 参数 args 的形式出现。 对于 make-process,要运行的程序及其命令行参数都被指定为字符串列表。 命令行参数必须都是字符串,并且它们作为单独的参数字符串提供给程序。 通配符和其他 shell 结构在这些字符串中没有特殊含义,因为这些字符串直接传递给指定的程序。

子进程从 Emacs 继承其环境,但您可以使用 process-environment 为其指定覆盖。 请参阅操作系统环境。 子进程从 default-directory 的值中获取其当前目录。

Variable: exec-directory ¶

该变量的值是一个字符串,一个目录的名称,其中包含 GNU Emacs 附带的程序,并打算供 Emacs 调用。 movemail 程序就是这种程序的一个例子。 Rmail 使用它从收件箱中获取新邮件。

User Option: exec-path ¶

此变量的值是用于搜索要在子进程中运行的程序的目录列表。 每个元素要么是目录的名称(即字符串),要么是 nil,它代表默认目录(即 default-directory 的值)。 有关此搜索的详细信息,请参见可执行文件查找。

当程序参数不是绝对文件名时,调用进程和启动进程使用 exec-path 的值。

通常,您不应直接修改 exec-path。 相反,请确保在启动 Emacs 之前正确设置了 PATH 环境变量。 尝试独立于 PATH 修改 exec-path 可能会导致令人困惑的结果。

Function: exec-path ¶

此函数是变量 exec-path 的扩展。 如果 default-directory 指示远程目录,则此函数返回用于在相应远程主机上搜索程序的目录列表。 在本地默认目录的情况下,该函数仅返回变量 exec-path 的值。

39.2 Shell 参数

Lisp 程序有时需要运行一个 shell 并给它一个包含用户指定文件名的命令。 这些程序应该能够支持任何有效的文件名。 但是shell对某些字符进行了特殊处理,如果这些字符出现在文件名中,就会混淆shell。 要处理这些字符,请使用函数 shell-quote-argument:

Function: shell-quote-argument argument ¶

此函数返回一个字符串,该字符串以 shell 语法表示一个参数,其实际内容是参数。 它应该可靠地将返回值连接到 shell 命令中,然后将其传递给 shell 以执行。

这个函数的确切作用取决于您的操作系统。 该函数旨在使用系统标准外壳的语法; 如果您使用不寻常的外壳,则需要重新定义此功能。 请参阅安全注意事项。

;; This example shows the behavior on GNU and Unix systems.
(shell-quote-argument "foo > bar")
     ⇒ "foo\\ \\>\\ bar"

;; This example shows the behavior on MS-DOS and MS-Windows.
(shell-quote-argument "foo > bar")
     ⇒ "\"foo > bar\""

下面是使用 shell-quote-argument 构造 shell 命令的示例:

(concat "diff -u "
	      (shell-quote-argument oldfile)
	      " "
	      (shell-quote-argument newfile))

以下两个函数可用于将单独的命令行参数字符串列表组合成单个字符串,并将字符串拆分成单独的命令行参数列表。 这些函数主要用于将 minibuffer 中的用户输入(Lisp 字符串)转换为要传递给 make-process、call-process 或 start-process 的字符串参数列表,或将此类参数列表转换为单个 Lisp要在 minibuffer 或 echo 区域中显示的字符串。 请注意,如果涉及 shell(例如,如果使用 call-process-shell-command),则参数仍应受 shell-quote-argument 保护; combine-and-quote-strings 并非旨在保护特殊字符免受 shell 评估。

Function: split-string-shell-command string ¶

此函数将字符串拆分为子字符串,同时考虑双引号和单引号以及反斜杠引用。

(split-string-shell-command "ls /tmp/'foo bar'")
     ⇒ ("ls" "/tmp/foo bar")
Function: split-string-and-unquote string &optional separators ¶

此函数在匹配正则表达式分隔符时将字符串拆分为子字符串,就像 split-string 所做的一样(请参阅创建字符串); 此外,它从子字符串中删除了引号。 然后它创建一个子字符串列表并返回它。

如果分隔符被省略或为零,则默认为“\s-+”,这是一个正则表达式,匹配一个或多个具有空格语法的字符(参见语法类表)。

此函数支持两种类型的引用:将整个字符串括在双引号 “…” 中,以及使用反斜杠转义 ‘' 引用单个字符。 后者也用于 Lisp 字符串,因此该函数也可以处理这些字符串。

Function: combine-and-quote-strings list-of-strings &optional separator ¶

此函数将字符串列表连接成单个字符串,并根据需要引用每个字符串。 它还将分隔字符串粘贴在每对字符串之间; 如果分隔符被省略或为零,则默认为“”。 返回值是结果字符串。

需要引用的字符串列表中的字符串是那些包含分隔符作为其子字符串的字符串。 引用字符串会将其括在双引号“…”中。 在最简单的情况下,如果您从单个命令行参数中使用命令,则包含嵌入空格的每个参数都将被引用。

39.3 创建同步进程

创建同步进程后,Emacs 会等待进程终止后再继续。 在 GNU 或 Unix23 上启动 Dired 就是一个例子:它在同步进程中运行 ls,然后稍微修改输出。 因为这个过程是同步的,整个目录列表在 Emacs 尝试对它做任何事情之前到达缓冲区。

在 Emacs 等待同步子进程终止时,用户可以通过键入 Cg 退出。 第一个 Cg 尝试使用 SIGINT 信号终止子进程; 但它会等到子进程实际终止后再退出。 如果在此期间用户键入另一个 Cg,则立即使用 SIGKILL 终止子进程并立即退出(MS-DOS 除外,在此情况下终止其他进程不起作用)。 请参阅退出。

同步子进程函数返回进程如何终止的指示。

同步子进程的输出通常使用编码系统进行解码,就像从文件中读取的文本一样。 通过 call-process-region 发送到子进程的输入使用编码系统进行编码,就像写入文件的文本一样。 请参阅编码系统。

Function: call-process program &optional infile destination display &rest args ¶

该函数调用程序并等待它完成。

如果子进程的当前工作目录是本地的(由 unhandled-file-name-directory 确定),则将其设置为当前缓冲区的 default-directory 值,否则设置为“~”。 如果要在远程目录中运行进程,请使用 process-file。

如果 infile 不为零,则新进程的标准输入来自文件 infile,否则来自 null 设备。 参数destination 说明将流程输出放在哪里。 以下是可能性:

a buffer

将输出插入该缓冲区中的点之前。 这包括进程的标准输出流和标准错误流。

a buffer name (a string)

在点之前将输出插入到具有该名称的缓冲区中。

t

将输出插入当前缓冲区中的点之前。

nil

丢弃输出。

0

丢弃输出,并立即返回 nil 而无需等待子进程完成。

在这种情况下,进程并不是真正同步的,因为它可以与 Emacs 并行运行; 但是你可以认为它是同步的,因为只要这个函数返回,Emacs 就基本上完成了子进程。

MS-DOS 不支持异步子进程,所以这个选项在那里不起作用。

(:file file-name)

将输出发送到指定的文件名,如果它已经存在则覆盖它。

(real-destination error-destination

将标准输出流与标准错误流分开; 按照real-destination指定的处理普通输出,按照error-destination处理错误输出。 如果error-destination为nil,则表示丢弃错误输出,t表示将其与普通输出混合,字符串指定将错误输出重定向到的文件名。

您不能直接指定一个缓冲区来放置错误输出; 这太难实施了。 但是您可以通过将错误输出发送到临时文件,然后在子进程完成时将文件插入缓冲区来实现此结果。

如果 display 不为零,则调用进程在插入输出时重新显示缓冲区。 (但是,如果解码输出选择的编码系统未确定,即从实际数据中推断出编码,那么一旦遇到非 ASCII 字符,重新显示有时就无法继续。这是难以解决的根本原因;请参阅接收进程的输出。)

否则,函数调用过程不会重新显示,并且只有当 Emacs 在正常事件过程中重新显示该缓冲区时,结果才会在屏幕上可见。

其余参数 args 是为程序指定命令行参数的字符串。 每个字符串作为单独的参数传递给程序。

call-process 返回的值(除非您告诉它不要等待)指示进程终止的原因。 一个数字给出了子进程的退出状态; 0 表示成功,任何其他值表示失败。 如果进程以信号终止,则 call-process 返回一个描述该信号的字符串。 如果你告诉调用进程不要等待,它会返回 nil。

在下面的示例中,缓冲区“foo”是当前的。

  (call-process "pwd" nil t)
	   ⇒ 0

  ---------- Buffer: foo ----------
  /home/lewis/manual
  ---------- Buffer: foo ----------


  (call-process "grep" nil "bar" nil "lewis" "/etc/passwd")
	   ⇒ 0

  ---------- Buffer: bar ----------
  lewis:x:1001:1001:Bil Lewis,,,,:/home/lewis:/bin/bash

  ---------- Buffer: bar ----------

下面是一个使用调用过程的例子,它可以在插入目录函数的定义中找到:

   (call-process insert-directory-program nil t nil switches
		    (if full-directory-p
			(concat (file-name-as-directory file) ".")
		      file))
Function: process-file program &optional infile buffer display &rest args ¶

此函数在单独的进程中同步处理文件。 它类似于 call-process,但可以根据变量 default-directory 的值调用文件名处理程序,该变量指定子进程的当前工作目录。

参数的处理方式几乎与 call-process 相同,但有以下区别:

某些文件名处理程序可能不支持参数 infile、buffer 和 display 的所有组合和形式。 例如,某些文件名处理程序可能表现得好像 display 为 nil,而不管实际传递的值如何。 作为另一个示例,某些文件名处理程序可能不支持通过缓冲区参数分隔标准输出和错误输出。

如果调用文件名处理程序,它会根据第一个参数 program 确定要运行的程序。 例如,假设调用了远程文件的处理程序。 那么用于搜索程序的路径可能与 exec-path 不同。

第二个参数 infile 可以调用文件名处理程序。 文件名处理程序可能与为进程文件函数本身选择的处理程序不同。 (例如,default-directory 可以在一个远程主机上,而 infile 在不同的远程主机上。或者 default-directory 可以是非特殊的,而 infile 在远程主机上。)

如果 buffer 是 (real-destination error-destination) 形式的列表,并且 error-destination 命名一个文件,则适用与 infile 相同的注释。

剩余的参数 (args) 将逐字传递给进程。 Emacs 不参与处理 args 中存在的文件名。 为避免混淆,最好避免在 args 中使用绝对文件名,而是将所有文件名指定为相对于默认目录。 函数 file-relative-name 对于构造这样的相对文件名很有用。 或者,您可以使用 file-local-name(请参阅使某些文件名“变魔术”)来获取从远程主机的角度来看的绝对文件名。

Variable: process-file-side-effects ¶

此变量指示 process-file 的调用是否更改远程文件。

默认情况下,此变量始终设置为 t,这意味着调用 process-file 可能会更改远程主机上的任何文件。 当设置为 nil 时,文件名处理程序可以针对远程文件属性缓存优化其行为。

您应该只使用 let-binding 更改此变量; 从不使用 setq。

User Option: process-file-return-signal-string ¶

此用户选项指示 process-file 的调用是否返回描述中断远程进程的信号的字符串。

当一个进程返回一个大于 128 的退出代码时,它被解释为一个信号。 process-file 需要返回一个描述这个信号的字符串。

由于存在违反此规则的进程,返回大于 128 且未绑定到信号的退出代码,因此 process-file 始终将退出代码作为远程进程的自然数返回。 将此用户选项设置为 non-nil 会强制 process-file 将此类退出代码解释为信号,并返回相应的字符串。

Function: call-process-region start end program &optional delete destination display &rest args ¶

此函数将文本从头到尾作为标准输入发送到正在运行的程序的进程。 如果 delete 不为零,则删除发送的文本; 当目标为 t 时,这很有用,可将输出插入当前缓冲区以代替输入。

参数 destination 和 display 控制如何处理来自子进程的输出,以及是否在显示进入时更新它。有关详细信息,请参阅上面的 call-process 描述。 如果destination 是整数0,call-process-region 丢弃输出并立即返回nil,而不等待子进程完成(这仅在支持异步子进程时有效;即,在MS-DOS 上不可用)。

其余参数 args 是为程序指定命令行参数的字符串。

call-process-region 的返回值和 call-process 一样:如果你告诉它不等待就返回,则返回 nil; 否则,一个数字或字符串,指示子进程如何终止。

在下面的示例中,我们使用 call-process-region 来运行 cat 实用程序,标准输入是缓冲区“foo”(单词“input”)中的前五个字符。 cat 将其标准输入复制到其标准输出中。 由于参数目标是 t,因此该输出被插入到当前缓冲区中。



  ---------- Buffer: foo ----------
  input∗
  ---------- Buffer: foo ----------


  (call-process-region 1 6 "cat" nil t)
	   ⇒ 0

  ---------- Buffer: foo ----------
  inputinput∗
  ---------- Buffer: foo ----------

例如,shell-command-on-region 命令以类似于以下方式使用 call-shell-region:

(call-shell-region
 start end
 command              ; shell command
 nil                  ; do not delete region
 buffer)              ; send output to buffer
Function: call-process-shell-command command &optional infile destination display ¶

该函数同步执行shell命令命令。 其他参数在调用过程中处理。 旧的调用约定允许在显示后传递任意数量的附加参数,这些参数连接到命令; 这仍然受到支持,但强烈反对。

Function: process-file-shell-command command &optional infile destination display ¶

此函数类似于 call-process-shell-command,但在内部使用 process-file。 根据默认目录,命令也可以在远程主机上执行。 旧的调用约定允许在显示后传递任意数量的附加参数,这些参数连接到命令; 这仍然受到支持,但强烈反对。

Function: call-shell-region start end command &optional delete destination ¶

此函数将文本从头到尾作为标准输入发送到下级 shell 运行命令。 这个函数类似于 call-process-region,进程是一个 shell。 参数 delete、destination 和返回值类似于 call-process-region。 请注意,此函数不接受其他参数。

Function: shell-command-to-string command ¶

此函数将命令(字符串)作为 shell 命令执行,然后将命令的输出作为字符串返回。

Function: process-lines program &rest args ¶

该函数运行程序,等待它完成,并将其输出作为字符串列表返回。 列表中的每个字符串都包含程序输出的一行文本; 从每一行中删除行尾字符。 程序之外的参数 args 是指定用于运行程序的命令行参数的字符串。

如果程序以非零退出状态退出,则此函数会发出错误信号。

此函数通过调用 call-process 工作,因此程序输出的解码方式与 call-process 相同。

Function: process-lines-ignore-status program &rest args ¶

这个函数就像进程行,但如果程序以非零退出状态退出,则不会发出错误信号。

脚注 (23)

在其他系统上,Emacs 使用 ls 的 Lisp 模拟; 请参阅目录的内容。

39.4 创建一个异步进程

在本节中,我们将描述如何创建异步进程。 创建异步进程后,它与 Emacs 并行运行,并且 Emacs 可以使用以下部分中描述的功能与它通信(请参阅向进程发送输入,并参阅从进程接收输出)。 请注意,进程通信只是部分异步的:Emacs 仅在调用这些函数时向进程发送和接收数据。

异步进程通过 pty(伪终端)或管道进行控制。 在创建流程时选择 pty 或 pipe,默认情况下基于变量 process-connection-type 的值(见下文)。 如果可用,ptys 通常更适合用户可见的进程,例如在 Shell 模式下,因为它们允许在进程及其子进程之间进行作业控制(Cc、Cz 等),并且因为交互式程序将 ptys 视为终端设备,而管道不支持这些功能。 然而,对于 Lisp 程序用于内部目的的子进程(即,不需要用户与子进程交互),需要在子进程和 Lisp 程序之间交换大量数据,通常最好使用管道,因为管道效率更高。 此外,pty 的总数在许多系统上是有限的,最好不要不必要地浪费它们。

Function: make-process &rest args ¶

此函数是启动异步子进程的基本低级原语。 它返回一个代表子流程的流程对象。 与下面描述的更高级的启动进程相比,它采用关键字参数,更灵活,并允许在单个调用中指定进程过滤器和哨兵。

参数 args 是关键字/参数对的列表。 省略关键字始终等同于使用值 nil 指定它。 以下是有意义的关键字:

:name name

使用字符串名称作为进程名称; 如果已存在具有此名称的进程,则将名称修改(通过附加“<1>”等)使其唯一。

:buffer buffer

使用缓冲区作为进程缓冲区。 如果值为 nil,则子进程不与任何缓冲区关联。

:command command

使用 command 作为进程的命令行。 该值应该是一个以程序的可执行文件名开头的列表,后跟作为参数提供给程序的字符串。 如果列表的第一个元素为 nil,则 Emacs 会打开一个新的伪终端(pty)并将其输入和输出与缓冲区相关联,而不实际运行任何程序; 在这种情况下,其余的列表元素将被忽略。

:coding coding

如果编码是一个符号,它指定用于从连接读取和向连接写入数据的编码系统。 如果 encoding 是一个 cons 单元(解码.编码),那么解码将用于读取和编码用于写入。 用于对写入程序的数据进行编码的编码系统也用于对命令行参数进行编码(但不是程序本身,其文件名被编码为任何其他文件名;请参阅文件名编码系统)。

如果 coding 为 nil,则将应用查找编码系统的默认规则。 请参阅默认编码系统。

:connection-type type

初始化用于与子进程通信的设备类型。 可能的值是使用 pty 的 pty、使用管道的 pipe 或使用从 process-connection-type 变量的值派生的默认值的 nil。 如果为 :stderr 参数指定了非 nil 值,则忽略此参数和 process-connection-type 的值; 在这种情况下,类型将始终是管道。 在 pty 不可用的系统 (MS-Windows) 上,此参数同样被忽略,并且无条件使用管道。

:noquery query-flag

将进程查询标志初始化为query-flag。 请参阅退出前查询。

:stop stopped

如果提供,stopped 必须为零; 使用任何非零值都是错误的。 :stop 键在其他情况下会被忽略并保留以与其他进程类型(例如管道进程)兼容。 异步子进程永远不会在停止状态下启动。

:filter filter

初始化流程过滤器进行过滤。 如果未指定,将提供默认过滤器,稍后可以覆盖该过滤器。 请参阅处理过滤器函数。

:sentinel sentinel

初始化进程哨兵到哨兵。 如果未指定,将使用默认哨兵,以后可以覆盖。 请参阅 Sentinels:检测进程状态更改。

:stderr stderr

将 stderr 与流程的标准错误相关联。 非零值应该是缓冲区或使用 make-pipe-process 创建的管道进程,如下所述。 如果 stderr 为 nil,则标准错误与标准输出混合,两者都被发送到缓冲区或过滤器。

如果 stderr 是一个缓冲区,Emacs 将创建一个管道进程,即标准错误进程。 该进程将具有默认过滤器(参见进程过滤器功能)、哨兵(参见哨兵:检测进程状态更改)和编码系统(参见默认编码系统)。 另一方面,它将使用 query-flag 作为退出时的查询标志(请参阅退出前的查询)。 它将与 stderr 缓冲区相关联(请参阅进程缓冲区)并将其输出(这是主进程的标准错误)发送到那里。 要获取标准错误进程的进程对象,请将 stderr 缓冲区传递给 get-buffer-process。

如果 stderr 是一个管道进程,Emacs 会将它用作新进程的标准错误进程。

:file-handler file-handler

如果 file-handler 不为 nil,则为当前缓冲区的默认目录查找文件名处理程序,并调用该文件名处理程序来创建进程。 如果没有这样的处理程序,就好像文件处理程序是 nil 一样继续。

使用实际连接信息修改的原始参数列表可通过过程联系功能获得。

如果子进程的当前工作目录是本地的(由 unhandled-file-name-directory 确定),则将其设置为当前缓冲区的 default-directory 值,否则设置为 ~。 如果要在远程目录中运行进程,请将 :file-handler t 传递给 make-process。 在这种情况下,当前工作目录是 default-directory 的本地名称组件(由 file-local-name 确定)。

根据文件名处理程序的实现,可能无法将过滤器或哨兵应用于生成的进程对象。 :stderr 参数不能是管道进程,文件名处理程序不支持管道进程。 接受作为 :stderr 参数的缓冲区,其内容在不使用管道进程的情况下显示。 请参阅流程过滤器函数、哨兵:检测流程状态更改和接受流程的输出。

某些文件名处理程序可能不支持 make-process。 在这种情况下,这个函数什么都不做并且返回 nil。

Function: make-pipe-process &rest args ¶

此函数创建一个可以附加到子进程的双向管道。 这对 make-process 的 :stderr 关键字很有用。 该函数返回一个进程对象。

参数 args 是关键字/参数对的列表。 省略关键字始终等同于使用值 nil 指定它。

以下是有意义的关键字:

:name name

使用字符串名称作为进程名称。 与 make-process 一样,如果需要,它会被修改以使其唯一。

:buffer buffer

使用缓冲区作为进程缓冲区。

:coding coding

如果编码是一个符号,它指定用于从连接读取和向连接写入数据的编码系统。 如果 encoding 是一个 cons 单元(解码.编码),那么解码将用于读取和编码用于写入。

如果 coding 为 nil,则将应用查找编码系统的默认规则。 请参阅默认编码系统。

:noquery query-flag

将进程查询标志初始化为query-flag。 请参阅退出前查询。

:stop stopped

如果stopped 不为零,则在stopped 状态下启动进程。 在停止状态下,管道进程不接受传入数据,但可以发送传出数据。 停止状态由 stop-process 设置并由 continue-process 清除(请参阅向进程发送信号)。

:filter filter

初始化流程过滤器进行过滤。 如果未指定,将提供默认过滤器,以后可以更改。 请参阅处理过滤器函数。

:sentinel sentinel

初始化进程哨兵到哨兵。 如果未指定,将使用默认哨兵,以后可以更改。 请参阅 Sentinels:检测进程状态更改。

使用实际连接信息修改的原始参数列表可通过过程联系功能获得。

Function: start-process name buffer-or-name program &rest args ¶

这个函数是 make-process 的一个更高级别的包装器,它暴露了一个类似于 call-process 的接口。 它创建一个新的异步子进程并启动在其中运行的指定程序。 它返回一个进程对象,它代表 Lisp 中的新子进程。 参数名称指定进程对象的名称; 与 make-process 一样,如有必要,它会被修改以使其唯一。 缓冲区 buffer-or-name 是与进程关联的缓冲区。

如果 program 为 nil,Emacs 会打开一个新的伪终端(pty)并将其输入和输出与缓冲区或名称相关联,而不创建子进程。 在这种情况下,其余参数 args 将被忽略。

其余的 args 是为子进程指定命令行参数的字符串。

在下面的示例中,第一个进程启动并运行(而不是休眠)100 秒(立即创建输出缓冲区“foo”)。 同时,启动第二个进程,为唯一起见,将其命名为“my-process<1>”。 在第一个进程完成之前,它将目录列表插入到缓冲区“foo”的末尾。 然后它完成,并且在缓冲区中插入一条具有该效果的消息。 很久以后,第一个进程完成,另一个消息被插入到它的缓冲区中。



  (start-process "my-process" "foo" "sleep" "100")
	   ⇒ #<process my-process>


  (start-process "my-process" "foo" "ls" "-l" "/bin")
	   ⇒ #<process my-process<1>>

  ---------- Buffer: foo ----------
  total 8336
  -rwxr-xr-x 1 root root 971384 Mar 30 10:14 bash
  -rwxr-xr-x 1 root root 146920 Jul  5  2011 bsd-csh
  …
  -rwxr-xr-x 1 root root 696880 Feb 28 15:55 zsh4

  Process my-process<1> finished

  Process my-process finished
  ---------- Buffer: foo ----------
Function: start-file-process name buffer-or-name program &rest args ¶

与 start-process 一样,该函数在其中启动一个新的异步子进程运行程序,并返回其进程对象。

与 start-process 不同的是,该函数可以根据 default-directory 的值调用文件名处理程序。 这个处理程序应该运行程序,可能在本地主机上,可能在对应于默认目录的远程主机上。 在后一种情况下,default-directory 的本地部分成为进程的工作目录。

此函数不会尝试为程序或其余 args 调用文件名处理程序。 因此,如果程序或任何 args 使用远程文件语法(请参阅使某些文件名“魔术”),则必须将它们转换为相对于默认目录的文件名,或者转换为在本地标识文件的名称远程主机,通过文件本地名称运行它们。

根据文件名处理程序的实现,可能无法将 process-filter 或 process-sentinel 应用于生成的进程对象。 请参阅进程过滤器函数和哨兵:检测进程状态更改。

某些文件名处理程序可能不支持 start-file-process(例如函数 ange-ftp-hook-function)。 在这种情况下,这个函数什么都不做并且返回 nil。

Function: start-process-shell-command name buffer-or-name command ¶

这个函数类似于 start-process,只是它使用一个 shell 来执行指定的命令。 参数 command 是一个 shell 命令字符串。 变量 shell-file-name 指定使用哪个 shell。

通过 shell 而不是直接使用 make-process 或 start-process 运行程序的目的是,您可以在参数中使用通配符等 shell 功能。 因此,如果您在命令中包含任何用户指定的任意参数,您应该首先用 shell-quote-argument 引用它们,以便任何特殊的 shell 字符没有它们特殊的 shell 含义。 请参阅 Shell 参数。 当然,在基于用户输入执行命令时,您还应该考虑安全隐患。

Function: start-file-process-shell-command name buffer-or-name command ¶

此功能类似于 start-process-shell-command,但在内部使用 start-file-process。 因此,命令也可以在远程主机上执行,具体取决于默认目录。

Variable: process-connection-type ¶

此变量控制用于与异步子进程通信的设备类型。 如果它不是 nil,则在可用时使用 pty。 否则,使用管道。

process-connection-type 的值在调用 make-process 或 start-process 时生效。 因此,您可以通过将变量绑定到对这些函数的调用来指定如何与一个子进程通信。

请注意,当使用 :stderr 参数的非零值调用 make-process 时,将忽略此变量的值; 在这种情况下,Emacs 将使用管道与进程通信。 如果 ptys 不可用(MS-Windows),它也会被忽略。

     (let ((process-connection-type nil))  ; use a pipe
	(start-process …))

要确定给定的子进程实际上是否获得了管道或 pty,请使用函数 process-tty-name(请参阅进程信息)。

39.5 删除进程

删除一个进程会立即断开 Emacs 与子进程的连接。 进程在终止后会自动删除,但不一定立即删除。 您可以随时明确删除进程。 如果您在自动删除之前明确删除已终止的进程,则不会造成任何损害。 删除一个正在运行的进程会发送一个信号来终止它(及其子进程,如果有的话),并调用进程哨兵。 请参阅 Sentinels:检测进程状态更改。

当一个进程被删除时,只要其他 Lisp 对象指向它,该进程对象本身就会继续存在。 所有在进程对象上工作的 Lisp 原语都接受已删除的进程,但那些执行 I/O 或发送信号的进程将报告错误。 进程标记继续指向与以前相同的位置,通常指向插入进程输出的缓冲区。

User Option: delete-exited-processes ¶

此变量控制自动删除已终止的进程(由于调用 exit 或信号)。 如果它是 nil,那么它们将继续存在,直到用户运行列表进程。 否则,它们会在退出后立即被删除。

Function: delete-process process ¶

此函数删除一个进程,如果该进程正在运行程序,则使用 SIGKILL 信号终止它。 参数可以是进程、进程名称、缓冲区或缓冲区名称。 (缓冲区或缓冲区名称代表 get-buffer-process 返回的进程。)对正在运行的进程调用 delete-process 会终止它,更新进程状态,并立即运行哨兵。 如果进程已经终止,调用 delete-process 对其状态或哨兵的运行没有影响(迟早会发生)。

如果进程对象代表网络、串行或管道连接,则其状态更改为关闭; 否则,它变为信号,除非进程已经退出。 请参阅进程状态。

  (delete-process "*shell*")
	   ⇒ nil

39.6 过程信息

几个函数返回有关进程的信息。

Command: list-processes &optional query-only buffer ¶

此命令显示所有活动进程的列表。 此外,它最终会删除任何状态为“已退出”或“已发出信号”的进程。 它返回零。

进程显示在名为 Process List 的缓冲区中(除非您使用可选参数缓冲区另外指定),其主要模式是进程菜单模式。

如果 query-only 为非 nil,它只列出查询标志为非 nil 的进程。 请参阅退出前查询。

Function: process-list ¶

此函数返回所有尚未删除的进程的列表。

  (process-list)
	   ⇒ (#<process display-time> #<process shell>)
Function: num-processors &optional query ¶

该函数返回处理器的数量,一个正整数。 每个可用的线程执行单元都算作一个处理器。 默认情况下,计数包括可用处理器的数量,您可以通过设置 OpenMP 的 OMP_NUM_THREADS 环境变量来覆盖它。 如果可选参数查询是当前的,则此函数忽略 OMP_NUM_THREADS; 如果查询是全部,则此函数还计算系统上但当前进程不可用的处理器。

Function: get-process name ¶

此函数返回名为 name 的进程(一个字符串),如果没有则返回 nil。 参数名称也可以是一个进程对象,在这种情况下它被返回。

  (get-process "shell")
	   ⇒ #<process shell>
Function: process-command process ¶

此函数返回为启动进程而执行的命令。 这是一个字符串列表,第一个字符串是执行的程序,其余的字符串是给程序的参数。 对于网络、串行或管道连接,这要么是 nil,这意味着进程正在运行,要么是 t(进程已停止)。

  (process-command (get-process "shell"))
	   ⇒ ("bash" "-i")
Function: process-contact process &optional key no-block ¶

此函数返回有关如何设置网络、串行或管道连接的信息。 当 key 为 nil 时,它为网络连接返回 (hostname service),为串行连接返回 (port speed),为管道连接返回 t。 对于一个普通的子进程,这个函数在使用 nil 键调用时总是返回 t。

如果key为t,则value为连接、服务器、串口或管道的完整状态信息; 即在 make-network-process、make-serial-process 或 make-pipe-process 中指定的关键字和值的列表,除了某些值表示当前状态而不是您指定的状态。

对于网络进程,这些值包括(请参阅 make-network-process 以获取完整列表):

:buffer

关联的值是进程缓冲区。

:filter

关联的值是过程过滤器功能。 请参阅处理过滤器函数。

:sentinel

关联的值是进程哨兵函数。 请参阅 Sentinels:检测进程状态更改。

:remote

在连接中,远程对等点的内部格式的地址。

:local

本地地址,采用内部格式。

:service

在服务器中,如果您为服务指定了 t,则此值是实际的端口号。

:local 和 :remote 包括在内,即使它们没有在 make-network-process 中明确指定。

对于串行连接,请参阅 make-serial-process 和 serial-process-configure 以获取密钥列表。 对于管道连接,请参阅 make-pipe-process 以获取键列表。

如果 key 是关键字,则函数返回与该关键字对应的值。

如果进程是尚未完全设置的非阻塞网络流,则此函数将阻塞直到发生。 如果给定可选的 no-block 参数,此函数将返回 nil 而不是阻塞。

Function: process-id process ¶

该函数返回进程的PID。 这是一个整数,用于将进程进程与当前在同一台计算机上运行的所有其他进程区分开来。 进程的 PID 在进程启动时由操作系统内核选择,并且只要进程存在就保持不变。 对于网络、串行和管道连接,此函数返回 nil。

Function: process-name process ¶

此函数以字符串形式返回进程的名称。

Function: process-status process-name ¶

此函数将进程名称的状态作为符号返回。 参数 process-name 必须是进程、缓冲区或进程名称(字符串)。

实际子流程的可能值为:

run

对于正在运行的进程。

stop

对于已停止但可继续的过程。

exit

对于已退出的进程。

signal

对于已收到致命信号的进程。

open

用于打开的网络、串行或管道连接。

closed

对于已关闭的网络、串行或管道连接。 连接关闭后,您将无法重新打开它,但您可能可以打开与同一位置的新连接。

connect

对于等待完成的非阻塞连接。

failed

对于未能完成的非阻塞连接。

listen

对于正在侦听的网络服务器。

nil

如果 process-name 不是现有进程的名称。

  (process-status (get-buffer "*shell*"))
	⇒ run

对于网络、串行或管道连接,process-status 返回符号打开、停止或关闭之一。 后者意味着对方关闭了连接,或者Emacs做了delete-process。 值 stop 表示在连接上调用了 stop-process。

Function: process-live-p process ¶

如果进程处于活动状态,则此函数返回非零。 如果进程的状态为运行、打开、侦听、连接或停止,则认为进程处于活动状态。

Function: process-type process ¶

此函数返回符号 network 表示网络连接或服务器,serial 表示串行端口连接,pipe 表示管道连接,或 real 表示为运行程序而创建的子进程。

Function: process-exit-status process ¶

此函数返回进程的退出状态或杀死它的信号号。 (使用 process-status 的结果来确定它是哪一个。)如果进程尚未终止,则值为 0。对于已经关闭的网络、串行和管道连接,值为 0 或 256,取决于连接是正常关闭还是异常关闭。

Function: process-tty-name process ¶

此函数返回进程用于与 Emacs 通信的终端名称——如果它使用管道而不是 pty,则返回 nil(请参阅创建异步进程中的 process-connection-type)。 如果 process 表示在远程主机上运行的程序,则远程主机上该程序使用的终端名称作为进程属性 remote-tty 提供。 如果 process 表示网络、串行或管道连接,则值为 nil。

Function: process-coding-system process ¶

此函数返回一个 cons 单元(decode .encode),描述用于对进程的输出进行解码和对进程的输入进行编码的编码系统(请参阅编码系统)。

Function: set-process-coding-system process &optional decoding-system encoding-system ¶

此函数指定用于后续输出和输入到处理的编码系统。 它将使用解码系统对子进程输出进行解码,并使用编码系统对子进程输入进行编码。

每个进程还有一个属性列表,您可以使用它来存储与进程关联的杂项值。

Function: process-get process propname ¶

此函数返回 process 的 propname 属性的值。

Function: process-put process propname value ¶

此函数将 process 的 propname 属性的值设置为 value。

Function: process-plist process ¶

该函数返回进程的进程plist。

Function: set-process-plist process plist ¶

该函数将进程的进程plist设置为plist。

39.7 向进程发送输入

异步子进程在 Emacs 向它们发送输入时接收输入,这是通过本节中的函数完成的。 您必须指定将输入发送到的进程以及要发送的输入数据。 如果子进程运行一个程序,则数据出现在该程序的标准输入中; 对于连接,数据被发送到连接的设备或程序。

某些操作系统在 pty 中用于缓冲输入的空间有限。 在这些系统上,Emacs 会定期在其他字符之间发送一个 EOF,以强制它们通过。 对于大多数程序,这些 EOF 没有害处。

子进程输入通常在子进程接收它之前使用编码系统进行编码,就像写入文件的文本一样。 您可以使用 set-process-coding-system 指定要使用的编码系统(请参阅流程信息)。 否则,编码系统来自coding-system-for-write,如果不是零的话; 或者来自默认机制(请参阅默认编码系统)。

有时系统无法接受该进程的输入,因为输入缓冲区已满。 发生这种情况时,发送函数会等待片刻,接受来自子进程的输出,然后重试。 这使子进程有机会读取更多待处理的输入并在缓冲区中腾出空间。 它还允许过滤器(包括当前运行的过滤器)、哨兵和计时器运行——所以在编写代码时要考虑到这一点。

在这些函数中,进程参数可以是进程或进程名称,或者缓冲区或缓冲区名称(通过 get-buffer-process 表示进程)。 nil 表示当前缓冲区的进程。

Function: process-send-string process string ¶

此函数将字符串的内容作为标准输入发送处理。 它返回零。 例如,要制作一个 Shell 缓冲区列表文件:

  (process-send-string "shell<1>" "ls\n")
	   ⇒ nil
Function: process-send-region process start end ¶

此函数将由 start 和 end 定义的区域中的文本作为标准输入发送到处理。

除非 start 和 end 都是整数或指示当前缓冲区中位置的标记,否则会发出错误信号。 (哪个数字更大并不重要。)

Function: process-send-eof &optional process ¶

此函数使进程在其输入中看到文件结尾。 EOF 出现在已经发送给它的任何文本之后。 函数返回进程。

  (process-send-eof "shell")
	   ⇒ "shell"
Function: process-running-child-p &optional process ¶

这个函数会告诉你一个进程,它不能是一个连接,而是一个真正的子进程,是否已经将其终端的控制权交给了它自己的子进程。 如果为真,则函数返回进程的前台进程组的数字 ID; 如果 Emacs 可以确定不是这样,它返回 nil。 如果 Emacs 无法判断这是否为真,则值为 t。 如果进程是网络、串行或管道连接,或者子进程未处于活动状态,则此函数会发出错误信号。

39.8 向进程发送信号

向子流程发送信号是中断其活动的一种方式。 有几种不同的信号,每一种都有自己的含义。 信号集及其名称由操作系统定义。 例如,信号 SIGINT 表示用户键入了 Cc,或者发生了类似的事情。

每个信号对子进程都有标准的影响。 大多数信号会终止子进程,但有些信号会停止(或恢复)执行。 大多数信号可以选择由程序处理; 如果程序处理了信号,那么我们一般就不能说它的效果。

您可以通过调用本节中的函数来显式发送信号。 Emacs 也会在某些时候自动发送信号:杀死一个缓冲区会向它的所有相关进程发送一个 SIGHUP 信号; 杀死 Emacs 会向所有剩余进程发送 SIGHUP 信号。 (SIGHUP 是一个信号,通常表示用户“挂断电话”,即断开连接。)

每个信号发送函数都有两个可选参数:process 和 current-group。

参数 process 必须是进程、进程名称、缓冲区、缓冲区名称或 nil。 缓冲区或缓冲区名称通过 get-buffer-process 代表一个进程。 nil 代表与当前缓冲区关联的进程。 除了 stop-process 和 continue-process 之外,如果 process 没有识别活动进程,或者它表示网络、串行或管道连接,则会发出错误信号。

参数 current-group 是一个标志,当您将作业控制 shell 作为 Emacs 子进程运行时会有所不同。 如果它是非零,那么信号被发送到 Emacs 用来与子进程通信的终端的当前进程组。 如果进程是一个作业控制外壳,这意味着外壳的当前子作业。 如果 current-group 为 nil,则将信号发送到 Emacs 的直接子进程的进程组。 如果子进程是一个作业控制外壳,这就是外壳本身。 如果 current-group 是 lambda,则信号被发送到拥有终端的进程组,但前提是它不是 shell 本身。

当使用管道与子进程通信时,标志 current-group 无效,因为操作系统不支持管道情况下的区分。 出于同样的原因,当使用管道时,作业控制 shell 将不起作用。 请参阅创建异步进程中的 process-connection-type。

Function: interrupt-process &optional process current-group ¶

该函数通过发送信号 SIGINT 来中断进程进程。 在 Emacs 之外,键入中断字符(通常在某些系统上是 Cc,而在其他系统上是 DEL)会发送此信号。 当参数 current-group 不为 nil 时,您可以将此函数视为在 Emacs 与子进程对话的终端上键入 Cc。

Function: kill-process &optional process current-group ¶

该函数通过发送信号 SIGKILL 来终止进程进程。 该信号立即杀死子进程,子进程无法处理。

Function: quit-process &optional process current-group ¶

该函数向进程进程发送信号 SIGQUIT。 当您不在 Emacs 中时,此信号是由退出字符(通常是 C-\)发送的信号。

Function: stop-process &optional process current-group ¶

该函数停止指定的进程。 如果它是运行程序的真正子进程,它会向该子进程发送信号 SIGTSTP。 如果 process 表示网络、串行或管道连接,则此函数禁止处理来自连接的传入数据; 对于网络服务器,这意味着不接受新连接。 使用 continue-process 恢复正常执行。

在 Emacs 之外,在具有作业控制的系统上,停止字符(通常是 Cz)通常会将 SIGTSTP 信号发送到子进程。 当 current-group 不为 nil 时,您可以将此函数视为在 Emacs 用来与子进程通信的终端上键入 Cz。

Function: continue-process &optional process current-group ¶

该函数恢复进程进程的执行。 如果是运行程序的真实子进程,则向该子进程发送信号 SIGCONT; 这假定该过程先前已停止。 如果 process 表示网络、串行或管道连接,则此函数恢复处理来自连接的传入数据。 对于串行连接,在进程停止期间到达的数据可能会丢失。

Command: signal-process process signal ¶

该函数向进程发送信号。 参数信号指定发送哪个信号; 它应该是一个整数,或者一个名称为信号的符号。

process 参数可以是系统进程 ID(整数); 这允许您向不是 Emacs 子进程的进程发送信号。 请参阅访问其他进程。

有时,需要向非本地异步进程发送信号。 这可以通过编写自己的中断进程实现来实现。 然后必须将此函数添加到中断处理函数中。

Variable: interrupt-process-functions ¶

此变量是要为中断进程调用的函数列表。 函数的参数与中断进程相同。 这些函数按列表的顺序调用,直到其中一个返回非零。 默认函数是 internal-default-interrupt-process,它总是在这个列表中的最后一个。

这就是 Tramp 如何实现中断进程的机制。

39.9 接收进程的输出

异步子进程写入其标准输出流的输出被传递给称为过滤器函数的函数。 默认的过滤器函数只是简单地将输出插入一个缓冲区,该缓冲区称为进程的关联缓冲区(请参阅进程缓冲区)。 如果进程没有缓冲区,则默认过滤器会丢弃输出。

如果子进程写入其标准错误流,默认情况下,错误输出也会传递给进程过滤器函数。 如果 Emacs 使用伪 TTY (pty) 与子进程通信,那么就不可能将子进程的标准输出流和标准错误流分开,因为伪 TTY 只有一个输出通道。 在这种情况下,如果您想将这些流的输出分开,您应该将其中一个重定向到一个文件,例如,通过 start-process-shell-command 或类似函数使用适当的 shell 命令。

或者,您可以在调用 make-process(参见 make-process)时使用带有非 nil 值的 :stderr 参数,以使错误输出的目标与标准输出分开; 在这种情况下,Emacs 将使用管道与子进程进行通信。

当一个子进程终止时,Emacs 读取任何挂起的输出,然后停止从该子进程读取输出。 因此,如果子进程的子进程仍然存在并且仍在产生输出,那么 Emacs 将不会收到该输出。

子进程的输出只能在 Emacs 等待时到达:在读取终端输入时(参见函数 waiting-for-user-input-p),在sit-for 和 sleep-for 中(参见等待经过的时间或输入),在accept-process-output(请参阅接受进程的输出),以及向进程发送数据的函数(请参阅向进程发送输入)。 这最大限度地减少了通常困扰并行编程的时序错误问题。 例如,您可以安全地创建一个进程,然后才指定它的缓冲区或过滤器功能; 如果中间的代码没有调用任何等待的原语,则在完成之前没有输出可以到达。

Variable: process-adaptive-read-buffering ¶

在某些系统上,当 Emacs 从子进程读取输出时,输出数据以非常小的块读取,可能会导致性能非常差。 通过将变量 process-adaptive-read-buffering 设置为非 nil 值(默认值)可以在一定程度上纠正这种行为,因为它会自动延迟从此类进程中读取,从而允许它们在 Emacs 尝试之前产生更多输出阅读它。

39.9.1 进程缓冲区

一个进程可以(并且通常确实)有一个关联的缓冲区,这是一个普通的 Emacs 缓冲区,用于两个目的:存储进程的输出,以及决定何时终止进程。 您还可以使用缓冲区来标识要操作的进程,因为在正常实践中,只有一个进程与任何给定的缓冲区相关联。 许多进程应用程序也使用缓冲区来编辑要发送到进程的输入,但这不是内置在 Emacs Lisp 中的。

默认情况下,进程输出被插入到相关的缓冲区中。 (您可以通过定义自定义过滤器函数来更改此设置,请参阅处理过滤器函数。)插入输出的位置由处理标记确定,然后将其更新为指向刚刚插入的文本的末尾。 通常,但并非总是如此,进程标记位于缓冲区的末尾。

终止进程的关联缓冲区也会终止该进程。 如果进程的 process-query-on-exit-flag 不为 nil,Emacs 会首先要求确认(请参阅 Querying Before Exit)。 此确认由函数 process-kill-buffer-query-function 完成,该函数从 kill-buffer-query-functions 运行(请参阅 Killing Buffers)。

Function: process-buffer process ¶

此函数返回指定进程的关联缓冲区。

  (process-buffer (get-process "shell"))
	   ⇒ #<buffer *shell*>
Function: process-mark process ¶

此函数返回进程的进程标记,该标记表示在何处插入进程的输出。

如果 process 没有缓冲区,则 process-mark 返回一个不指向任何地方的标记。

默认过滤器函数使用此标记来决定在哪里插入流程输出,并将其更新为指向插入的文本之后。 这就是连续插入连续批次输出的原因。

自定义过滤器函数通常应该以相同的方式使用此标记。 有关使用 process-mark 的过滤器函数的示例,请参阅处理过滤器示例。

当期望用户在进程缓冲区中输入输入以传输到进程时,进程标记将新输入与先前的输出分开。

Function: set-process-buffer process buffer ¶

此函数将与进程关联的缓冲区设置为缓冲区。 如果 buffer 为 nil,则进程与无缓冲区关联; 如果非零,进程标记将被设置为指向缓冲区的结尾。

Function: get-buffer-process buffer-or-name ¶

此函数返回与由 buffer-or-name 指定的缓冲区关联的未删除进程。 如果有多个与之关联的进程,则此函数会选择一个(当前是最近创建的一个,但不要指望它)。 删除进程(请参阅删除进程)使此函数无法返回。

将多个进程与同一个缓冲区关联通常是一个坏主意。

  (get-buffer-process "*shell*")
	   ⇒ #<process shell>

终止进程的缓冲区会删除该进程,该进程会使用 SIGHUP 信号终止子进程(请参阅向进程发送信号)。

如果进程的缓冲区显示在一个窗口中,您的 Lisp 程序可能希望告诉进程该窗口的尺寸,以便进程可以使其输出适应这些尺寸,就像它适应屏幕尺寸一样。 以下功能允许将此类信息传达给进程; 然而,并非所有系统都支持底层功能,因此最好提供回退,例如,通过命令行参数或环境变量。

Function: set-process-window-size process height width ¶

告诉进程它的逻辑窗口大小的尺寸是宽乘高,以字符为单位。 如果此函数成功将此信息传递给进程,则返回 t; 否则返回零。

当显示与进程关联的缓冲区的窗口更改其尺寸时,应告知受影响的进程这些更改。 默认情况下,当窗口配置发生变化时,Emacs 将自动代表每个在窗口中显示缓冲区的进程调用 set-process-window-size,并将显示该进程缓冲区的所有窗口中的最小尺寸传递给它。 这通过 window-configuration-change-hook 工作(参见 Hooks for Window Scrolling and Changes),它被告知调用函数,该函数是每个进程的变量 window-adjust-process-window-size-function 的值,其缓冲区显示在至少一个窗口中。 您可以通过设置该变量的值来自定义此行为。

User Option: window-adjust-process-window-size-function ¶

这个变量的值应该是两个参数的函数:一个进程和显示进程缓冲区的窗口列表。 当函数被调用时,进程的缓冲区就是当前缓冲区。 该函数应返回一个 cons 单元格 (width . height),该单元格描述要通过调用 set-process-window-size 传递的逻辑进程窗口的尺寸。 该函数也可以返回 nil,在这种情况下,Emacs 不会为此进程调用 set-process-window-size。

Emacs 为这个变量提供了两个预定义的值:window-adjust-process-window-size-smallest,它返回显示进程缓冲区的所有窗口尺寸中最小的一个; 和 window-adjust-process-window-size-largest,它返回最大的尺寸。 对于更复杂的策略,编写自己的函数。

此变量可以是缓冲区本地的。

如果进程具有 adjust-window-size-function 属性(请参阅进程信息),则其值将覆盖 window-adjust-process-window-size-function 的全局值和缓冲区本地值。

39.9.2 过程过滤器函数

进程过滤器函数是从相关进程接收标准输出的函数。 该过程的所有输出都传递给过滤器。 默认过滤器只是直接输出到进程缓冲区。

默认情况下,进程的错误输出(如果有)也会传递给过滤器函数,除非在创建进程时将进程的标准错误流的目标与标准输出分开。 Emacs 只会在某些函数调用期间调用过滤器函数。 请参阅接收进程的输出。 请注意,如果过滤器调用了这些函数中的任何一个,则过滤器可能会被递归调用。

过滤器函数必须接受两个参数:关联的进程和一个字符串,它是刚刚从它接收到的输出。 然后,该函数可以自由地对输出进行任何选择。

退出通常在过滤器函数中被禁止——否则,在命令级别键入 Cg 或退出用户命令的效果将是不可预测的。 如果您想允许在过滤器函数内退出,请将禁止退出绑定到 nil。 在大多数情况下,正确的方法是使用 with-local-quit 宏。 请参阅退出。

如果在过滤器函数执行期间发生错误,它会被自动捕获,因此它不会停止过滤器函数启动时正在运行的任何程序的执行。 但是,如果 debug-on-error 不为零,则不会捕获错误。 这使得使用 Lisp 调试器来调试过滤器函数成为可能。 请参阅 Lisp 调试器。

许多过滤器函数有时(或总是)将输出插入进程的缓冲区,模仿默认过滤器的操作。 此类过滤器函数需要确保它们保存当前缓冲区,在插入输出之前选择正确的缓冲区(如果不同),然后恢复原始缓冲区。 他们还应该检查缓冲区是否还活着,更新进程标记,在某些情况下更新点的值。 以下是如何执行这些操作:

 (defun ordinary-insertion-filter (proc string)
   (when (buffer-live-p (process-buffer proc))
     (with-current-buffer (process-buffer proc)
	(let ((moving (= (point) (process-mark proc))))

	  (save-excursion
	    ;; Insert the text, advancing the process marker.
	    (goto-char (process-mark proc))
	    (insert string)
	    (set-marker (process-mark proc) (point)))
	  (if moving (goto-char (process-mark proc)))))))

为了使过滤器在新文本到达时强制进程缓冲区可见,您可以在 with-current-buffer 构造之前插入如下行:

(display-buffer (process-buffer proc))

要强制指向新输出的末尾,无论它以前在哪里,消除从示例中移动的变量并无条件调用 goto-char。 请注意,这不一定会移动窗口点。 默认过滤器实际上使用 insert-before-markers 来移动所有标记,包括窗口点。 这可能会移动不相关的标记,因此通常最好显式移动窗口点,或者将其插入类型设置为 t(请参阅窗口和点)。

请注意,Emacs 在执行过滤器功能时会自动保存和恢复匹配数据。 请参阅匹配数据。

过滤器的输出可以是任何大小的块。 连续两次产生相同输出的程序可能一次发送一批 200 个字符,下一次发送五批 40 个字符。 如果过滤器在子流程输出中查找某些文本字符串,请确保处理其中一个字符串被拆分为两批或多批输出的情况; 一种方法是将接收到的文本插入到临时缓冲区中,然后可以对其进行搜索。

Function: set-process-filter process filter ¶

该函数给出处理过滤函数filter。 如果 filter 为 nil,它为进程提供默认过滤器,它将进程输出插入到进程缓冲区中。 如果 filter 为 t,Emacs 将停止接受来自该进程的输出,除非它是一个网络服务器进程来监听传入的连接。

Function: process-filter process ¶

该函数返回进程的过滤函数。

如果流程的输出需要传递给多个过滤器,您可以使用 add-function 将现有过滤器与新过滤器组合。 请参阅建议 Emacs Lisp 函数。

以下是使用过滤器功能的示例:



(defun keep-output (process output)
   (setq kept (cons output kept)))
     ⇒ keep-output

(setq kept nil)
     ⇒ nil

(set-process-filter (get-process "shell") 'keep-output)
     ⇒ keep-output

(process-send-string "shell" "ls ~/other\n")
     ⇒ nil
kept
     ⇒ ("lewis@slug:$ "

"FINAL-W87-SHORT.MSS    backup.otl              kolstad.mss~
address.txt             backup.psf              kolstad.psf
backup.bib~             david.mss               resume-Dec-86.mss~
backup.err              david.psf               resume-Dec.psf
backup.mss              dland                   syllabus.mss
"
"#backups.mss#          backup.mss~             kolstad.mss
")

39.9.3 解码过程输出

当 Emacs 将进程输出直接写入多字节缓冲区时,它会根据进程输出编码系统对输出进行解码。 如果编码系统是原始文本或无转换,Emacs 使用字符串到多字节将单字节输出转换为多字节,并插入生成的多字节文本。

您可以使用 set-process-coding-system 指定要使用的编码系统(请参阅流程信息)。 否则,编码系统来自coding-system-for-read,如果不是零的话; 或者来自默认机制(请参阅默认编码系统)。 如果进程输出的文本包含空字节,Emacs 默认使用 no-conversion ; 有关如何控制此行为的信息,请参见禁止空字节检测。

警告:编码系统,如 undecided,根据数据确定编码系统,不能完全可靠地处理异步子进程输出。 这是因为 Emacs 必须在异步子流程输出到达时分批处理它。 Emacs 必须尝试一次从一批中检测正确的编码系统,但这并不总是有效。 因此,如果可能,请指定一个编码系统,该系统同时确定字符代码转换和行尾转换——即类似于 latin-1-unix 的编码系统,而不是 undecided 或 latin-1。

当 Emacs 调用进程过滤器函数时,它会根据进程的过滤器编码系统将进程输出作为多字节字符串或单字节字符串提供。 Emacs 根据进程输出编码系统对输出进行解码,通常会产生多字节字符串,但二进制和原始文本等编码系统除外。

39.9.4 接受进程的输出

异步子进程的输出通常仅在 Emacs 等待某种外部事件(例如经过的时间或终端输入)时到达。 有时,在 Lisp 程序中显式允许输出到达特定点或什至等到进程的输出到达时很有用。

Function: accept-process-output &optional process seconds millisec just-this-one ¶

这个函数允许 Emacs 从进程中读取挂起的输出。 将输出提供给它们的过滤器功能。 如果 process 不是 nil,那么这个函数不会返回,直到从 process 接收到一些输出或 process 关闭了连接。

参数 seconds 和 millisec 允许您指定超时时间。 前者指定以秒为单位的周期,后者指定以毫秒为单位的周期。 如此指定的两个时间段相加,即使没有子进程输出,accept-process-output 也会在这段时间后返回。

参数毫秒已过时(不应使用),因为秒可以是浮点数来指定等待的小数秒数。 如果秒为 0,则该函数接受任何待处理的输出但不等待。

如果 process 是一个进程,并且参数 just-this-one 不是 nil,则只处理来自该进程的输出,暂停其他进程的输出,直到从该进程接收到一些输出或超时到期。 如果 just-this-one 是整数,也禁止运行计时器。 通常不建议使用此功能,但对于特定应用程序(例如语音合成)可能是必需的。

如果函数accept-process-output从进程获得输出,则返回非nil,如果进程为nil,则从任何进程获得输出; 如果相应的连接包含缓冲数据,即使在进程退出后也会发生这种情况。 如果超时过期或连接在输出到达之前关闭,则该函数返回 nil。

如果来自进程的连接包含缓冲数据,即使在进程退出后,accept-process-output 也可以返回非零。 因此,虽然下面的循环:

;; This loop contains a bug.
(while (process-live-p process)
  (accept-process-output process))

通常会从进程读取所有输出,它有一个竞争条件,如果 process-live-p 返回 nil 而连接仍然包含数据,它可能会丢失一些输出。 更好的是这样编写循环:

(while (accept-process-output process))

如果您已将非零标准错误传递给 make-process,它将有一个标准错误过程。 请参阅创建异步进程。 在这种情况下,等待主进程的进程输出不会等待标准错误进程的输出。 为确保您已收到来自进程的所有标准输出和所有标准错误,请使用以下代码:

(while (accept-process-output process))
(while (accept-process-output stderr-process))

如果您将缓冲区传递给 make-process 的 stderr 参数,您仍然需要等待标准错误过程,如下所示:

(let* ((stdout (generate-new-buffer "stdout"))
	 (stderr (generate-new-buffer "stderr"))
	 (process (make-process :name "test"
				:command '("my-program")
				:buffer stdout
				:stderr stderr))
	 (stderr-process (get-buffer-process stderr)))
  (unless (and process stderr-process)
    (error "Process unexpectedly nil"))
  (while (accept-process-output process))
  (while (accept-process-output stderr-process)))

只有当两个 accept-process-output 表单都返回 nil 时,你才能确定进程已经退出并且 Emacs 已经读取了它的所有输出。

以这种方式无法从远程主机上运行的进程中读取挂起的标准错误。

39.9.5 进程和线程

因为线程是 Emacs Lisp 中一个相对较晚的添加,并且由于动态绑定有时与 accept-process-output 结合使用的方式,默认情况下,进程被锁定到创建它的线程。 当一个进程被锁定到一个线程时,该进程的输出只能被该线程接受。

Lisp 程序可以指定将一个进程锁定到哪个线程,或者指示 Emacs 解锁一个进程,在这种情况下,它的输出可以由任何线程处理。 一次只有一个线程会等待给定进程的输出——一旦一个线程开始等待输出,该进程就会被暂时锁定,直到 accept-process-output 或 sit-for 返回。

如果线程退出,所有锁定到它的进程都被解锁。

Function: process-thread process ¶

返回进程被锁定的线程。 如果进程未锁定,则返回 nil。

Function: set-process-thread process thread ¶

将进程的锁定线程设置为线程。 thread 可能为 nil,在这种情况下进程被解锁。

39.10 Sentinels:检测进程状态变化

进程哨兵是一个函数,每当相关进程因任何原因改变状态时都会调用该函数,包括终止、停止或继续进程的信号(无论是由 Emacs 发送还是由进程自己的操作引起)。 如果进程退出,也会调用进程哨兵。 哨兵接收两个参数:事件发生的进程和描述事件类型的字符串。

如果没有为进程指定哨兵函数,它将使用默认哨兵函数,该函数会在进程的缓冲区中插入一条消息,其中包含进程名称和描述事件的字符串。

描述事件的字符串如下所示(但这不是事件字符串的详尽列表):


"finished\n".
"deleted\n".
"exited abnormally with code exitcode (core dumped)\n". The “core dumped” part is optional, and only appears if the process dumped core.
"failed with code fail-code\n".
"signal-description (core dumped)\n". The signal-description is a system-dependent textual description of a signal, e.g., "killed" for SIGKILL. The “core dumped” part is optional, and only appears if the process dumped core.
"open from host-name\n".
"open\n".
"run\n".
"connection broken by remote peer\n".

哨兵仅在 Emacs 等待时运行(例如,等待终端输入,或等待时间过去,或等待进程输出)。 这避免了在其他 Lisp 程序中间的随机位置运行哨兵可能导致的计时错误。 程序可以等待,以便哨兵运行,方法是调用sit-for 或sleep-for(请参阅等待经过的时间或输入)或accept-process-output(请参阅接受来自进程的输出)。 Emacs 还允许在命令循环读取输入时运行哨兵。 delete-process 在终止正在运行的进程时调用哨兵。

Emacs 不会保留多个原因的队列来调用一个进程的哨兵; 它只记录当前状态和发生变化的事实。 所以两次状态的变化,接二连三的来,只能召唤一次哨兵。 但是,进程终止将始终只运行一次哨兵。 这是因为进程状态在终止后不能再次改变。

Emacs 在运行进程哨兵之前显式检查进程的输出。 一旦哨兵由于进程终止而运行,就无法从进程中获得进一步的输出。

将输出写入进程缓冲区的哨兵应检查缓冲区是否还活着。 如果它试图插入一个死缓冲区,它会得到一个错误。 如果缓冲区死了, (buffer-name (process-buffer process)) 返回 nil。

退出通常在哨兵中被禁止——否则,在命令级别键入 Cg 或退出用户命令的效果将是不可预测的。 如果您想允许在哨兵内部退出,请将禁止退出绑定到 nil。 在大多数情况下,正确的方法是使用 with-local-quit 宏。 请参阅退出。

如果在哨兵的执行过程中发生错误,它会被自动捕获,这样它就不会停止哨兵启动时正在运行的任何程序的执行。 但是,如果 debug-on-error 不为零,则不会捕获错误。 这使得使用 Lisp 调试器来调试哨兵成为可能。 请参阅 Lisp 调试器。

当哨兵正在运行时,进程哨兵被临时设置为 nil,这样哨兵就不会递归运行。 由于这个原因,哨兵不可能指定新的哨兵。

请注意,Emacs 在执行哨兵时会自动保存和恢复匹配数据。 请参阅匹配数据。

Function: set-process-sentinel process sentinel ¶

该函数将哨兵与进程相关联。 如果 sentinel 为 nil,则进程将具有默认的 sentinel,当进程状态发生变化时,它将在进程的缓冲区中插入一条消息。

进程哨兵的更改立即生效——如果哨兵计划运行但尚未被调用,并且您指定了新哨兵,则对哨兵的最终调用将使用新哨兵。

    (defun msg-me (process event)
	 (princ
	   (format "Process: %s had the event '%s'" process event)))
    (set-process-sentinel (get-process "shell") 'msg-me)
	   ⇒ msg-me

    (kill-process (get-process "shell"))
	   -| Process: #<process shell> had the event 'killed'
	   ⇒ #<process shell>

Function: process-sentinel process ¶

该函数返回进程的哨兵。

如果需要将进程状态更改传递给多个哨兵,您可以使用 add-function 将现有哨兵与新哨兵相结合。 请参阅建议 Emacs Lisp 函数。

Function: waiting-for-user-input-p ¶

当哨兵或过滤器函数正在运行时,如果 Emacs 在调用哨兵或过滤器函数时正在等待用户的键盘输入,则此函数返回非 nil,否则返回 nil。

39.11 退出前查询

当 Emacs 退出时,它会终止它的所有子进程。 对于运行程序的子进程,它会向它们发送 SIGHUP 信号; 连接被简单地关闭。 因为子进程可能正在做有价值的工作,Emacs 通常会要求用户确认可以终止它们。 每个进程都有一个查询标志,如果不是 nil,则表示 Emacs 应该在退出之前要求确认,从而终止该进程。 查询标志的默认值为 t,表示执行查询。

Function: process-query-on-exit-flag process ¶

这将返回进程的查询标志。

Function: set-process-query-on-exit-flag process flag ¶

该函数将进程的查询标志设置为flag。 它返回标志。

下面是一个在 shell 进程上使用 set-process-query-on-exit-flag 来避免查询的示例:

  (set-process-query-on-exit-flag (get-process "shell") nil)
	   ⇒ nil
User Option: confirm-kill-processes ¶

如果此用户选项设置为 t(默认值),那么 Emacs 会在退出时终止进程之前要求确认。 如果为 nil,Emacs 会在不确认的情况下杀死进程,即忽略所有进程的查询标志。

39.12 访问其他进程

除了访问和操作作为当前 Emacs 会话的子进程的进程之外,Emacs Lisp 程序还可以访问在同一台机器上运行的其他进程。 我们将这些系统进程称为系统进程,以将它们与 Emacs 子进程区分开来。

Emacs 提供了几个用于访问系统进程的原语。 并非所有平台都支持这些原语; 在那些没有的情况下,这些原语返回 nil。

Function: list-system-processes ¶

此函数返回系统上运行的所有进程的列表。 每个进程都由其 PID 标识,PID 是一个由操作系统分配的数字进程 ID,用于将该进程与同时在同一台机器上运行的所有其他进程区分开来。

Function: process-attributes pid ¶

此函数返回由进程 ID pid 指定的进程的属性列表。 alist 中的每个关联都采用 (key . value) 形式,其中 key 指定属性,value 是该属性的值。 下面列出了该函数可以返回的各种属性键。 并非所有平台都支持所有这些属性; 如果某个属性不受支持,则其关联不会出现在返回的列表中。

euid

调用流程的用户的有效用户 ID。 对应的值是一个数字。 如果进程由运行当前 Emacs 会话的同一用户调用,则该值与 user-uid 返回的值相同(请参阅用户标识)。

user

进程有效用户ID对应的用户名,字符串。

egid

有效用户ID的组ID,一个数字。

group

有效用户组ID对应的组名,字符串。

comm

在进程中运行的命令的名称。 这是一个字符串,通常指定进程的可执行文件的名称,没有前导目录。 但是,一些特殊的系统进程可能会报告与程序的可执行文件不对应的字符串。

state

进程的状态码。 这是一个对进程的调度状态进行编码的短字符串。 以下是最常见的代码列表:

"D"

不间断睡眠(通常是 I/O)

"R"

跑步

"S"

可中断睡眠(等待某个事件)

"T"

停止,例如,通过作业控制信号

"Z"

僵尸:一个已终止但未被其父进程收割的进程

有关可能状态的完整列表,请参阅 ps 命令的手册页。

ppid

父进程的进程ID,一个数字。

pgrp

进程的进程组ID,一个数字。

sess

进程的会话 ID。 这是一个数字,它是进程会话负责人的进程 ID。

ttname

一个字符串,它是进程的控制终端的名称。 在 Unix 和 GNU 系统上,这通常是相应终端设备的文件名,例如 /dev/pts65。

tpgid

使用进程终端的前台进程组的数字进程组 ID。

minflt

进程自开始以来导致的次要页面错误数。 (次要页面错误是那些不涉及从磁盘读取的错误。)

majflt

进程自开始以来导致的主要页面错误数。 (主要页面错误需要读取磁盘,因此比次要页面错误更昂贵。)

cminflt
cmajflt

与 minflt 和 majflt 类似,但包括给定进程的所有子进程的页面错误数。

utime

进程在用户上下文中花费的时间,用于运行应用程序的代码。 相应的值是 Lisp 时间戳(请参阅时间)。

cutime

进程在系统(内核)上下文中用于处理系统调用的时间。 对应的值是 Lisp 时间戳。

cstime

utime 和 stime 的总和。 对应的值是 Lisp 时间戳。

cstime
ctime

与 utime、stime 和 time 类似,但包括给定进程的所有子进程的时间。

pri

进程的数字优先级。

nice

进程的nice值,一个数字。 (nice 值较小的进程会得到更有利的调度。)

thcount

进程中的线程数。

start

进程启动的时间,作为 Lisp 时间戳。

etime

自进程启动以来经过的时间,作为 Lisp 时间戳。

vsize

进程的虚拟内存大小,以千字节为单位。

rss

进程驻留集的大小,进程在机器物理内存中占用的千字节数。

pcpu

进程自启动以来使用的 CPU 时间百分比。 对应的值是 0 到 100 之间的浮点数。

pmem

进程驻留集使用的机器上安装的总物理内存的百分比。 该值是 0 到 100 之间的浮点数。

args

调用进程的命令行。 这是一个字符串,其中各个命令行参数由空格分隔; 嵌入参数中的空白字符被引用为适合系统的 shell:在 GNU 和 Unix 上用反斜杠字符转义,在 Windows 上用双引号字符括起来。 因此,这个命令行字符串可以直接用在原语中,比如 shell-command。

39.13 事务队列

您可以使用事务队列与使用事务的子进程进行通信。 首先使用 tq-create 创建一个与指定进程通信的事务队列。 然后你可以调用 tq-enqueue 发送一个事务。

Function: tq-create process ¶

该函数创建并返回一个与进程通信的事务队列。 参数进程应该是一个能够发送和接收字节流的子进程。 它可能是子进程,也可能是与服务器的 TCP 连接,可能在另一台机器上。

Function: tq-enqueue queue question regexp closure fn &optional delay-question ¶

此函数将事务发送到队列队列。 指定队列具有指定要与之交谈的子进程的效果。

参数 question 是启动事务的传出消息。 参数 fn 是返回相应答案时调用的函数; 它用两个参数调用:闭包和收到的答案。

参数 regexp 是一个正则表达式,应该匹配整个答案末尾的文本,但之前没有; 这就是 tq-enqueue 确定答案在哪里结束的方式。

如果参数 delay-question 不为零,则延迟发送此问题,直到该过程完成对任何先前问题的答复。 这会在某些过程中产生更可靠的结果。

Function: tq-close queue ¶

关闭事务队列队列,等待所有未决事务完成,然后终止连接或子进程。

事务队列是通过过滤功能实现的。 请参阅处理过滤器函数。

39.14 网络连接

Emacs Lisp 程序可以打开流 (TCP) 和数据报 (UDP) 网络连接(请参阅数据报)到同一机器或其他机器上的其他进程。 网络连接由 Lisp 处理,就像子进程一样,由进程对象表示。 但是,您正在与之通信的进程不是 Emacs 进程的子进程,没有进程 ID,您无法杀死它或向它发送信号。 您所能做的就是发送和接收数据。 delete-process 关闭连接,但不会杀死另一端的程序; 该程序必须决定如何关闭连接。

Lisp 程序可以通过创建网络服务器来监听连接。 网络服务器也由一种进程对象表示,但与网络连接不同,网络服务器本身从不传输数据。 当它收到一个连接请求时,它会创建一个新的网络连接来代表刚刚建立的连接。 (网络连接从服务器继承某些信息,包括进程 plist。)然后网络服务器返回侦听更多连接请求。

网络连接和服务器是通过使用由关键字/参数对组成的参数列表调用 make-network-process 创建的,例如 :server t 创建服务器进程,或 :type ‘datagram 创建数据报连接。 有关详细信息,请参阅低级网络访问。 您还可以使用下面描述的 open-network-stream 功能。

为了区分不同类型的进程,process-type 函数返回符号 network 表示网络连接或服务器,serial 表示串行端口连接,pipe 表示管道连接,或 real 表示真正的子进程。

进程状态函数返回网络连接的打开、关闭、连接、停止或失败。 对于网络服务器,状态始终是监听。 除了 stop 之外,对于真正的子进程,这些值都不可能。 请参阅过程信息。

您可以通过调用 stop-process 和 continue-process 来停止和恢复网络进程的操作。 对于服务器进程,停止意味着不接受新连接。 (当您恢复服务器时,最多将有 5 个连接请求排队;您可以增加此限制,除非它是由操作系统强加的——请参阅 make-network-process、make-network-process 的 :server 关键字。)对于网络流连接,停止意味着不处理输入(任何到达的输入都会等待,直到您恢复连接)。 对于数据报连接,一些数据包可能会排队,但输入可能会丢失。 您可以使用函数 process-command 来确定是否停止了网络连接或服务器; 非零值表示是。

Emacs 可以使用对 GnuTLS 传输层安全库的内置支持创建加密网络连接; 请参阅 GnuTLS 项目页面。 如果你的 Emacs 是用 GnuTLS 支持编译的,函数 gnutls-available-p 被定义并返回非零。 有关更多详细信息,请参阅 Emacs-GnuTLS 手册中的概述。 open-network-stream 功能可以使用任何可用的支持透明地处理为您创建加密连接的细节。

Function: open-network-stream name buffer host service &rest parameters ¶

此函数打开一个 TCP 连接,带有可选的加密,并返回一个表示该连接的进程对象。

name 参数指定进程对象的名称。 根据需要对其进行修改以使其唯一。

buffer 参数是与连接关联的缓冲区。 连接的输出被插入缓冲区,除非您指定自己的过滤器函数来处理输出。 如果 buffer 为 nil,则表示该连接未与任何缓冲区关联。

参数 host 和 service 指定连接到哪里; host 是主机名(字符串),service 是定义的网络服务的名称(字符串)或端口号(整数,如 80 或整数字符串,如“80”)。

其余参数参数是主要与加密连接相关的关键字/参数对:

:nowait boolean

如果非零,尝试建立一个异步连接。

:coding coding

使用它来设置网络进程使用的编码系统,而不是绑定coding-system-for-read或coding-system-for-write。 有关详细信息,请参阅 make-network-process。

:type type

连接的类型。 选项是:

plain

一个普通的、未加密的连接。

tls
ssl

TLS(传输层安全)连接。

nil
network

从普通连接开始,如果提供参数 ‘:success’ 和 ‘:capability-command’,尝试通过 STARTTLS 升级到加密连接。 如果失败,请保留未加密的连接。

starttls

至于 nil,但如果 STARTTLS 失败则断开连接。

shell

外壳连接。

:always-query-capabilities boolean

如果非零,请始终询问服务器的功能,即使在进行“普通”连接时也是如此。

:capability-command capability-command

查询主机能力的命令。 这可以是一个字符串(然后将逐字发送到服务器),也可以是一个函数(使用单个参数调用;连接时来自服务器的“问候”),并且应该返回一个字符串。

:end-of-command regexp
:end-of-capability regexp

正则表达式匹配命令的结尾,或命令的结尾capability-command。 后者默认为前者。

:starttls-function function

一个参数的函数(对能力命令的响应),它返回 nil,或者如果支持,则返回激活 STARTTLS 的命令。

:success regexp

匹配成功的 STARTTLS 协商的正则表达式。

:use-starttls-if-possible boolean

如果非 nil,即使 Emacs 没有内置的 TLS 支持,也要进行机会性 STARTTLS 升级。

:warn-unless-encrypted boolean

如果非 nil 并且 :return-value 也非 nil,如果连接未加密,Emacs 将发出警告。 这对于 IMAP 等协议很有用,大多数用户都希望网络流量被加密。

:client-certificate list-or-t

要么是形式列表(key-file cert-file),命名证书密钥文件和证书文件本身,要么是 t,意思是查询 auth-source 以获取此信息(请参阅 Emacs auth-source 库中的 auth-source)。 仅用于 TLS 或 STARTTLS。 要在未指定 :client-certificate 时启用 auth-source 的自动查询,请将 network-stream-use-client-certificates 自定义为 t。

:return-list cons-or-nil

此函数的返回值。 如果省略或为零,则返回一个进程对象。 否则,形式的缺点 (process-object . plist),其中 plist 有关键字:

:greeting string-or-nil

如果非零,则主机返回的问候字符串。

:capabilities string-or-nil

如果非零,主机的能力字符串。

:type symbol

连接类型:“普通”或“tls”。

:shell-command string-or-nil

如果连接类型是 shell,则此参数将被解释为将执行以建立连接的格式规范字符串。 可用的规范是主机名的“%s”和端口号的“%p”。 例如,如果您想在建立普通连接之前先 ssh 到“网关”,那么此参数可能类似于“ssh gateway nc %s %p”。

39.15 网络服务器

您可以通过使用 :server t 调用 make-network-process(请参阅 make-network-process)来创建服务器。 服务器将侦听来自客户端的连接请求。 当它接受客户端连接请求时,它会创建一个新的网络连接,它本身就是一个进程对象,具有以下参数:

连接的进程名称是通过连接服务器进程的名称和客户端标识字符串来构造的。 IPv4 连接的客户端标识字符串类似于“abcd:p”,它表示地址和端口号。 否则,它是括号中的唯一编号,如“<nnn>”。 对于 Emacs 会话中的每个连接,该编号都是唯一的。 如果服务器有非默认过滤器,则连接进程不会获得单独的进程缓冲区; 否则,Emacs 会为此创建一个新缓冲区。 缓冲区名称是服务器的缓冲区名称或进程名称,与客户端标识字符串连接。

服务器的进程缓冲区值从不直接使用,但日志函数可以检索它并通过在其中插入文本来使用它来记录连接。 通信类型和进程过滤器和哨兵是从服务器继承的。 服务器从不直接使用它的过滤器和哨兵; 它们的唯一目的是初始化与服务器的连接。 连接的进程联系信息是根据客户端的寻址信息(通常是IP地址和端口号)设置的。 此信息与进程联系关键字:host、:service、:remote 相关联。 连接的本地地址是根据用于连接的端口号设置的。 客户端进程的 plist 是从服务器的 plist 初始化的。

39.16 数据报

数据报连接与单个数据包而不是数据流进行通信。 对 process-send 的每次调用都会发送一个数据报包(请参阅向进程发送输入),并且收到的每个数据报都会导致对过滤器函数的一次调用。

数据报连接不必一直与同一个远程对等方通信。 它有一个远程对等地址,它指定将数据报发送到哪里。 每次传入的数据报被传递给过滤函数时,对端地址被设置为数据报来自的地址; 这样,如果过滤器函数发送了一个数据报,它就会回到那个地方。 您可以在使用 :remote 关键字创建数据报连接时指定远程对等地址。 您可以稍后通过调用 set-process-datagram-address 来更改它。

Function: process-datagram-address process ¶

如果进程是数据报连接或服务器,则此函数返回其远程对等地址。

Function: set-process-datagram-address process address ¶

如果进程是数据报连接或服务器,则此函数将其远程对等地址设置为地址。

39.17 低级网络访问

您还可以使用 make-network-process 在比 open-network-stream 更低的级别操作来创建网络连接。

39.17.1 make-network-process

创建网络连接和网络服务器的基本功能是make-network-process。 它可以完成其中任何一项工作,具体取决于您提供的参数。

Function: make-network-process &rest args ¶

该函数创建一个网络连接或服务器,并返回代表它的进程对象。 参数 args 是关键字/参数对的列表。 除了 :coding、:filter-multibyte 和 :reuseaddr 之外,省略关键字总是等同于指定它的值为 nil。 以下是有意义的关键字(与网络选项相对应的关键字在下一节中列出):

:name name

使用字符串名称作为进程名称。 必要时对其进行修改以使其唯一。

:type type

指定通信类型。 nil 值指定流连接(默认); datagram 指定一个数据报连接; seqpacket 指定一个有序的数据包流连接。 连接和服务器都可以是这些类型。

:server server-flag

如果 server-flag 不为零,则创建一个服务器。 否则,创建一个连接。 对于流式服务器,server-flag 可以是一个整数,然后指定到服务器的未决连接队列的长度。 默认队列长度为 5。

:host host

指定要连接的主机。 host 应该是主机名或 Internet 地址,作为字符串,或符号 local 来指定本地主机。 如果您为服务器指定主机,它必须为本地主机指定一个有效地址,并且只接受连接到该地址的客户端。 使用本地时,默认情况下将使用 IPv4,指定一个 ipv6 系列来覆盖它。 要侦听所有接口,请为 IPv4 指定地址“0.0.0.0”或为 IPv6 指定地址“::”。 请注意,在某些操作系统上,监听 ‘”::”’ 也会监听 IPv4,因此尝试在 IPv4 上单独监听会导致 EADDRINUSE 错误(‘“Address already in use”’)。

:service service

service 指定要连接的端口号; 或者,对于服务器,要监听的端口号。 它应该是一个服务名称,如“https”,转换为端口号,或者一个整数,如“443”,或一个整数字符串,如“443”,直接指定端口号。 对于服务器,也可以是t,意思是让系统选择一个未使用的端口号。

:family family

family 指定用于通信的地址(和协议)族。 nil 表示为给定的主机和服务自动确定正确的地址族。 local 指定一个 Unix 套接字,在这种情况下 host 被忽略。 ipv4 和 ipv6 分别指定使用 IPv4 和 IPv6。

:use-external-socket use-external-socket

如果 use-external-socket 不为零,则使用在调用时传递给 Emacs 的任何套接字,而不是分配一个。 Emacs 服务器代码使用它来允许按需激活套接字。 如果 Emacs 没有传递一个套接字,这个选项会被忽略。

:local local-address

对于服务器进程,local-address 是要监听的地址。 它会覆盖家庭、主机和服务,因此您最好不要指定它们。

:remote remote-address

对于连接,remote-address 是要连接的地址。 它会覆盖家庭、主机和服务,因此您最好不要指定它们。

对于数据报服务器,remote-address 指定远程数据报地址的初始设置。

本地地址或远程地址的格式取决于地址族:

  • IPv4 地址表示为一个由四个 8 位整数和一个 16 位整数 [abcdp] 组成的五元素向量,对应于数字 IPv4 地址 abcd 和端口号 p。
  • IPv6 地址表示为 16 位整数 [abcdefghp] 的九元素向量,对应于数字 IPv6 地址 a:b:c:d:e:f:g:h 和端口号 p。
  • 本地地址表示为一个字符串,它指定本地地址空间中的地址。
  • 不支持的系列地址由 cons (f . av) 表示,其中 f 是系列号,av 是一个向量,它使用每个地址数据字节一个元素来指定套接字地址。 不要在可移植代码中依赖这种格式,因为它可能依赖于实现定义的常量、数据大小和数据结构对齐。
:nowait bool

如果流连接的 bool 不为零,则返回而不等待连接完成。 当连接成功或失败时,Emacs 将调用 sentinel 函数,第二个参数匹配“open”(如果成功)或“failed”。 默认是阻塞的,因此 make-network-process 在连接成功或失败之前不会返回。

如果您要设置异步 TLS 连接,则还必须提供 :tls-parameters 参数(见下文)。

根据 Emacs 的功能,异步 :nowait 的方式可能会有所不同。 可能(或不可能)异步完成的三个元素是域名解析、套接字设置和(对于 TLS 连接)TLS 协商。

许多与进程对象交互的函数(例如,进程数据报地址)依赖于它们至少有一个套接字才能返回有用的值。 这些函数将阻塞,直到套接字达到所需的状态。 与异步套接字交互的推荐方式是在进程上放置一个哨兵,在它将状态更改为“运行”之前不要尝试与它交互。 这样,这些功能都不会阻塞。

:tls-parameters

打开 TLS 连接时,第一个元素应该是 TLS 类型(应该是 gnutls-x509pki 或 gnutls-anon,其余元素应该形成 gnutls-boot 可接受的关键字列表。(这个关键字列表可以从 gnutls-boot-parameters 函数中获取。)完成与主机的连接后,将协商 TLS 连接。

:stop stopped

如果stopped 不为零,则在stopped 状态下启动网络连接或服务器。

:buffer buffer

使用缓冲区作为进程缓冲区。

:coding coding

使用编码作为此过程的编码系统。 要指定不同的编码系统来解码来自连接的数据和编码发送给它的数据,请指定 (decoding . encoding) 进行编码。

如果您根本不指定此关键字,则默认是根据数据确定编码系统。

:noquery query-flag

将进程查询标志初始化为query-flag。 请参阅退出前查询。

:filter filter

初始化流程过滤器进行过滤。

:filter-multibyte multibyte

如果 multibyte 不是 nil,则给进程过滤器的字符串是多字节的,否则它们是单字节的。 默认值为 t。

:sentinel sentinel

初始化进程哨兵到哨兵。

:log log

初始化一个服务器进程的日志函数来记录。 每次服务器接受来自客户端的网络连接时,都会调用 log 函数。 传递给日志函数的参数是服务器、连接和消息; 其中 server 是服务器进程,connection 是连接的新进程,message 是描述发生了什么的字符串。

:plist plist

将进程 plist 初始化为 plist。

使用实际连接信息修改的原始参数列表可通过过程联系功能获得。

39.17.2 网络选项

创建网络进程时可以指定以下网络选项。 除了 :reuseaddr,您还可以稍后使用 set-network-process-option 设置或修改这些选项。

对于服务器进程,使用 make-network-process 指定的选项不会被客户端连接继承,因此您需要在创建每个子连接时为其设置必要的选项。

:bindtodevice device-name

如果 device-name 是标识网络接口名称的非空字符串(请参阅 network-interface-list),则仅处理在该接口上接收到的数据包。 如果 device-name 为 nil(默认值),则处理在任何接口上接收到的数据包。

在某些系统上使用此选项可能需要特殊权限。

:broadcast broadcast-flag

如果一个数据报进程的broadcast-flag 为非nil,该进程将接收发送到广播地址的数据报包,并且能够将数据包发送到广播地址。 对于流连接,这将被忽略。

:dontroute dontroute-flag

如果 dontroute-flag 为非 nil,则进程只能发送到与本地主机位于同一网络的主机。

:keepalive keepalive-flag

如果流连接的 keepalive-flag 为非 nil,则启用低级 keep-alive 消息的交换。

:linger linger-arg

如果 linger-arg 不为零,则在删除之前等待连接上所有排队数据包的成功传输(请参阅delete-process)。 如果 linger-arg 是一个整数,它指定在关闭连接之前等待发送排队数据包的最长时间(以秒为单位)。 默认为 nil,表示进程被删除时丢弃未发送的排队数据包。

:oobinline oobinline-flag

如果 oobinline-flag 对于流连接不为 nil,则在正常数据流中接收带外数据。 否则,忽略带外数据。

:priority priority

将此连接上发送的数据包的优先级设置为整数优先级。 这个数字的解释是协议特定的; 例如在此连接上发送的 IP 数据包上设置 TOS(服务类型)字段。 它还可能具有系统相关的影响,例如在网络接口上选择特定的输出队列。

:reuseaddr reuseaddr-flag

如果对于流服务器进程,reuseaddr-flag 为非 nil(默认值),则允许此服务器重用特定端口号(请参阅 :service),除非此主机上的另一个进程已经在侦听该端口。 如果reuseaddr-flag 为nil,则在最后一次使用该端口(主机上的任何进程)之后的一段时间内,可能无法在该端口上创建新服务器。

Function: set-network-process-option process option value &optional no-error ¶

该函数设置或修改网络进程进程的网络选项。 接受的选项和值与 make-network-process 相同。 如果 no-error 为非 nil,则如果 option 不是受支持的选项,则此函数返回 nil 而不是发出错误信号。 如果函数成功完成,则返回 t。

选项的当前设置可通过过程联系功能获得。

39.17.3 测试网络功能的可用性

要测试给定网络功能的可用性,请像这样使用 featurep:

(featurep 'make-network-process '(keyword value))

如果在 make-network-process 中使用 value value 指定关键字,则此形式的结果为 t。 以下是您可以通过这种方式测试的一些关键字值对。

(:nowait t)

如果支持非阻塞连接,则为非零。

(:type datagram)

如果支持数据报,则为非零。

(:family local)

如果支持本地(又称“UNIX 域”)套接字,则为非零。

(:family ipv6)

如果支持 IPv6,则为非零。

(:service t)

如果系统可以为服务器选择端口,则非 nil。

要测试给定网络选项的可用性,请像这样使用 featurep:

(featurep 'make-network-process 'keyword)

接受的关键字值为 :bindtodevice 等。有关完整列表,请参阅网络选项。 如果 make-network-process(或 set-network-process-option)支持该特定网络选项,则此表单返回非零。

39.18 其他网络设施

这些附加功能对于创建和操作网络连接很有用。 请注意,它们仅在某些系统上受支持。

Function: network-interface-list &optional full family ¶

此函数返回一个描述您正在使用的机器的网络接口的列表。 该值是一个 alist,其元素的格式为 (ifname .address)。 ifname 是一个命名接口的字符串,address 与 make-network-process 的 local-address 和 remote-address 参数形式相同,即整数向量。 默认情况下,如果可能,会返回 IPv4 和 IPv6 地址。

可选参数 full non-nil 表示改为返回表单中一个或多个元素的列表(ifname addr bcast netmask)。 ifname 是一个命名接口的非唯一字符串。 addr、bcast 和 netmask 是详细说明 IP 地址、广播地址和网络掩码的整数向量。

指定为符号 ipv4 或 ipv6 的可选参数系列将返回的信息分别限制为 IPv4 和 IPv6 地址,与 full 的值无关。 当 IPv6 支持不可用时指定 ipv6 将导致发出错误信号。

一些例子:



     (network-interface-list) ⇒
     (("vmnet8" .
	[172 16 76 1 0])
      ("vmnet1" .
	[172 16 206 1 0])
      ("lo0" .
	[65152 0 0 0 0 0 0 1 0])
      ("lo0" .
	[0 0 0 0 0 0 0 1 0])
      ("lo0" .
	[127 0 0 1 0]))
     (network-interface-list t) ⇒
     (("vmnet8"
	[172 16 76 1 0]
	[172 16 76 255 0]
	[255 255 255 0 0])
      ("vmnet1"
	[172 16 206 1 0]
	[172 16 206 255 0]
	[255 255 255 0 0])
      ("lo0"
	[65152 0 0 0 0 0 0 1 0]
	[65152 0 0 0 65535 65535 65535 65535 0]
	[65535 65535 65535 65535 0 0 0 0 0])
      ("lo0"
	[0 0 0 0 0 0 0 1 0]
	[0 0 0 0 0 0 0 1 0]
	[65535 65535 65535 65535 65535 65535 65535 65535 0])
      ("lo0"
	[127 0 0 1 0]
	[127 255 255 255 0]
	[255 0 0 0 0]))
Function: network-interface-info ifname ¶

此函数返回有关名为 ifname 的网络接口的信息。 该值是一个形式的列表(addr bcast netmask hwaddr flags)。

addr

Internet 协议地址。

bcast

广播地址。

netmask

网络掩码。

hwaddr

第 2 层地址(例如以太网 MAC 地址)。

flags

接口的当前标志。

请注意,此函数仅返回 IPv4 信息。

Function: format-network-address address &optional omit-port ¶

此函数将网络地址的 Lisp 表示形式转换为字符串。

五元素向量 [abcdp] 表示 IPv4 地址 abcd 和端口号 p。 format-network-address 将其转换为字符串“abcd:p”。

九元素向量 [abcdefghp] 表示 IPv6 地址和端口号。 format-network-address 将其转换为字符串“[a:b:c:d:e:f:g:h]:p”。

如果向量不包括端口号 p,或者如果 omit-port 不为零,则结果不包括 :p 后缀。

Function: network-lookup-address-info name &optional family ¶

此函数用于对名称执行主机名查找,该名称应为纯 ASCII 字符串,否则会发出错误信号。 如果您希望查找国际化主机名,请先在名称上调用 puny-encode-domain。

如果成功,则返回网络地址的 Lisp 表示列表,否则返回 nil。 在后一种情况下,它还会显示错误消息,希望能解释出了什么问题。

默认情况下,会尝试 IPv4 和 IPv6 查找。 可选参数系列控制此行为,指定符号 ipv4 或 ipv6 将查找分别限制为 IPv4 和 IPv6。

39.19 与串口通信

Emacs 可以与串口通信。 为了交互使用,Mx serial-term 打开一个终端窗口。 在 Lisp 程序中,make-serial-process 创建一个进程对象。

串行端口可以在运行时进行配置,而无需关闭并重新打开它。 函数 serial-process-configure 允许您更改速度、字节大小和其他参数。 在 serial-term 创建的终端窗口中,您可以单击模式行进行配置。

串行连接由一个进程对象表示,它可以以类似于子进程或网络进程的方式使用。 您可以发送和接收数据,并配置串口。 然而,串行进程对象没有进程ID,您不能向它发送信号,并且状态代码与其他类型的进程不同。 进程对象上的 delete-process 或进程缓冲区上的 kill-buffer 关闭连接,但这不会影响连接到串口的设备。

函数 process-type 为表示串行端口连接的进程对象返回符号 serial。

串行端口可用于 GNU/Linux、Unix 和 MS Windows 系统。

Command: serial-term port speed &optional line-mode ¶

为新缓冲区中的串行端口启动终端仿真器。 port 是要连接的串行端口的名称。 例如,这可能是 Unix 上的 /dev/ttyS0。 在 MS Windows 上,这可能是 COM1 或 \.\COM10(Lisp 字符串中的双反斜杠)。

speed 是串行端口的速度,以比特/秒为单位。 9600 是一个常见的值。 缓冲区处于 Term 模式; 请参阅 The GNU Emacs Manual 中的 Term Mode,了解在该缓冲区中使用的命令。 您可以在模式行菜单中更改速度和配置。 如果 line-mode 不是 nil,则使用 term-line-mode; 否则使用术语原始模式。

Function: make-serial-process &rest args ¶

该函数创建一个进程和一个缓冲区。 参数被指定为关键字/参数对。 这是有意义的关键字列表,前两个(端口和速度)是强制性的:

:port port

这是串行端口的名称。 在 Unix 和 GNU 系统上,这是一个文件名,例如 /dev/ttyS0。 在 Windows 上,这可能是 COM1,或者 \.\COM10 表示高于 COM9 的端口(Lisp 字符串中的反斜杠的两倍)。

:speed speed

串行端口的速度,以比特/秒为单位。 该函数调用serial-process-configure来处理速度; 有关更多详细信息,请参阅该函数的以下文档。

:name name

进程的名称。 如果没有给出名称,端口也将作为进程名称。

:buffer buffer

与进程关联的缓冲区。 该值可以是缓冲区或命名缓冲区的字符串。 除非您指定输出流或过滤器函数来处理输出,否则进程输出将位于该缓冲区的末尾。 如果没有给出缓冲区,则进程缓冲区的名称取自 :name 关键字的值。

:coding coding

如果coding 是一个符号,它指定了用于该过程的读写的编码系统。 如果coding是一个cons(decoding.encoding),decode用于读取,encoding用于写入。 如果未指定,则默认是根据数据本身确定编码系统。

:noquery query-flag

将进程查询标志初始化为query-flag。 请参阅退出前查询。 如果未指定,标志默认为 nil。

:stop bool

如果 bool 为非零,则以停止状态启动进程。 在停止状态下,串行进程不接受传入数据,但可以发送传出数据。 停止状态由 continue-process 清除并由 stop-process 设置。

:filter filter

安装过滤器作为过程过滤器。

:sentinel sentinel

安装哨兵作为进程哨兵。

:plist plist

安装 plist 作为进程的初始 plist。

:bytesize
:parity
:stopbits
:flowcontrol

这些由 make-serial-process 调用的 serial-process-configure 处理。

原始参数列表(可能由以后的配置修改)可通过函数 process-contact 获得。

这是一个例子:

(make-serial-process :port "/dev/ttyS0" :speed 9600)
Function: serial-process-configure &rest args ¶

此函数配置串行端口连接。 参数被指定为关键字/参数对。 未给出的属性从进程的当前配置(可通过函数 process-contact 获得)重新初始化,或设置为合理的默认值。 定义了以下参数:

:process process
:name name
:buffer buffer
:port port

可以给出这些参数中的任何一个来标识要配置的进程。 如果没有给出这些参数,则使用当前缓冲区的进程。

:speed speed

串行端口的速度(以每秒位数为单位),即波特率。 该值可以是任何数字,但大多数串行端口仅在 1200 和 115200 之间的几个定义值下工作,其中 9600 是最常见的值。 如果 speed 为 nil,该函数将忽略所有其他参数并且不配置端口。 这可能对特殊的串行端口有用,例如蓝牙到串行转换器,只能通过通过连接发送的“AT”命令进行配置。 速度的 nil 值仅对先前调用 make-serial-process 或 serial-term 已打开的连接有效。

:bytesize bytesize

每个字节的位数,可以是 7 或 8。如果未给定字节大小或为 nil,则默认为 8。

:parity parity

该值可以是 nil(不使用奇偶校验)、符号奇数(使用奇校验)或偶数符号(使用偶校验)。 如果没有给出奇偶校验,则默认为无奇偶校验。

:stopbits stopbits

用于终止每个字节传输的停止位的数量。 stopbits 可以是 1 或 2。如果 stopbits 没有给出或为零,则默认为 1。

:flowcontrol flowcontrol

用于此连接的流控类型,可以是 nil(不使用流控)、符号 hw(使用 RTS/CTS 硬件流控)或符号 sw(使用 XON/XOFF 软件流控) . 如果没有给出流量控制,则默认为无流量控制。

在内部,make-serial-process 调用 serial-process-configure 进行串口的初始配置。

39.20 打包和解包字节数组

本节描述如何打包和解包字节数组,通常用于二进制网络协议。 这些函数将字节数组转换为 alist,反之亦然。 字节数组可以表示为单字节字符串或整数向量,而 alist 将符号与固定大小的对象或递归子列表相关联。 要使用本节中提到的函数,请加载 bindat 库。

从字节数组到嵌套列表的转换也称为反序列化或解包,而相反方向的转换也称为序列化或打包。

39.20.1 描述数据布局

要控制解包和打包,您需要编写数据布局规范,也称为 Bindat 类型表达式。 这可以是基本类型或由多个字段组成的复合类型,其中规范控制要处理的每个字段的长度,以及如何打包或解包。 我们通常将 bindat 类型值保存在名称以 -bindat-spec 结尾的变量中; 这种名称会被自动识别为有风险的(请参阅文件局部变量)。

Macro: bindat-type &rest type ¶

根据 Bindat 类型表达式类型创建一个 Bindat 类型值对象。

字段的类型描述了该字段表示的对象的大小(以字节为单位),在多字节字段的情况下,还描述了字节在字段中的排序方式。 两种可能的排序是大端(也称为“网络字节排序”)和小端。 例如,大端序中的数字#x23cd(十进制9165)将是两个字节#x23 #xcd; 在小端,#xcd #x23。 以下是可能的类型值:

u8
byte

无符号字节,长度为 1。

uint bitlen

网络字节顺序的无符号整数,带有 bitlen 位。 bitlen 必须是 8 的倍数。

uintr bitlen

小端序的无符号整数,带有 bitlen 位。 bitlen 必须是 8 的倍数。

str len

长度为 len 的字节字符串。

strz &optional len

以零结尾的字节串,可以是任意长度,也可以是长度为 len 的固定大小字段。

vec len [type]

len 元素的向量。 元素的类型由类型给出,默认为字节。 类型可以是任何 Bindat 类型表达式。

repeat len [type]

与 vec 类似,但它解包到列表并从列表中打包,而 vec 解包到向量。

bits len

设置为 1 的位列表(以 len 个字节为单位)。 字节按大端顺序获取,位编号从 8 * len - 1 开始,以零结尾。 例如:位 2 将 #x28 #x1c 解包到 (2 3 4 11 13) 和 #x1c #x28 到 (3 5 10 11 12)。

fill len

len 个字节仅用作填充符。 在打包时,这些字节保持不变,这通常意味着它们保持为零。 解包时,这只是返回 nil。

align len

除了字节数是跳到下一个 len 字节的倍数之外,与 fill 相同。

type exp

这让你可以间接引用一个类型:exp 是一个 Lisp 表达式,它应该返回一个 Bindat 类型值。

unit exp

这是一个使用 0 位空间的普通类型。 exp 描述了当我们尝试“解包”这样一个字段时返回的值。

struct fields...

由多个字段组成的复合类型。 每个字段都采用(名称类型)形式,其中类型可以是任何 Bindat 类型表达式。 当字段的值不值得命名时,name 可以是 _,对齐和填充字段通常是这种情况。 当上下文明确表明这是一个 Bindat 类型表达式时,可以省略符号结构。

在上述类型中,len 和 bitlen 以整数形式给出,指定字段中的字节数(或位数)。 当字段的长度不固定时,通常取决于前面字段的值。 出于这个原因,长度 len 不必是一个常数,而是可以是任何 Lisp 表达式,它可以通过名称引用先前字段的值。

例如,前导字节给出后续 16 位整数向量的大小的数据布局规范可以是:

(bindat-type
  (len      u8)
  (payload  vec (1+ len) uint 16))

39.20.2 解包和打包字节的函数

在以下文档中,type 指的是从 bindat-type 返回的 Bindat 类型值,raw 指的是字节数组,struct 指的是表示解包字段数据的 alist。

Function: bindat-unpack type raw &optional idx ¶

此函数根据类型从单字节字符串或字节数组 raw 中解包数据。 通常,这会在字节数组的开头开始解包,但如果 idx 不为零,则它指定要使用的从零开始的起始位置。

该值是一个 alist 或嵌套 alist,其中每个元素描述一个未打包的字段。

Function: bindat-get-field struct &rest name ¶

此函数从嵌套的 alist 结构中选择字段的数据。 通常 struct 由 bindat-unpack 返回。 如果 name 只对应一个参数,这意味着提取顶级字段值。 多个名称参数指定重复查找子结构。 整数名称充当数组索引。

例如,(bindat-get-field struct ab 2 c) 表示在字段a的子字段b的第三个元素中查找字段c。 (这对应于 C 编程语言语法中的 struct.ab[2].c。)

尽管打包和解包操作改变了数据的组织(在内存中),但它们保留了数据的总长度,即所有字段长度的总和,以字节为单位。 该值通常不是规范或 alist 中固有的; 相反,这两条信息都有助于其计算。 同样,被解包的字符串或数组的长度可能比规范中描述的数据的总长度长。

Function: bindat-length type struct ¶

此函数根据类型返回 struct 中数据的总长度。

Function: bindat-pack type struct &optional raw idx ¶

此函数从 alist 结构中的数据返回一个根据类型打包的字节数组。 它通常从头开始创建并填充一个新的字节数组。 但是,如果 raw 不为 nil,则它指定要打包的预分配单字节字符串或向量。 如果 idx 不是 nil,它指定打包成 raw 的起始偏移量。

预分配时,应确保 (length raw) 满足或超过总长度以避免超出范围的错误。

Function: bindat-ip-to-string ip ¶

将 Internet 地址向量 ip 转换为通常点分表示法的字符串。

  (bindat-ip-to-string [127 0 0 1])
	   ⇒ "127.0.0.1"

39.20.3 高级数据布局规范

Bindat 类型表达式不限于前面描述的类型。 它们也可以是返回 Bindat 类型表达式的任意 Lisp 形式。 例如,下面的类型描述了可以包含 24 位错误代码或字节向量的数据:

(bindat-type
  (len      u8)
  (payload  . (if (zerop len) (uint 24) (vec (1- len)))))

此外,虽然复合类型通常解包到关联列表(并从关联列表中打包),但可以通过使用以下特殊关键字参数来更改:

:unpack-val exp

当字段列表以此关键字参数结尾时,解包时返回的值是 exp 的值,而不是标准的 alist。 exp 可以通过名称引用所有先前的字段。 :pack-val exp

如果字段的类型后跟此关键字参数,则打包到此字段中的值由 exp 返回,而不是从 alist 中提取。 :pack-var 名称

如果字段列表前面有这个关键字参数,那么所有后续的 :pack-val 参数都可以通过名为 name 的变量引用要打包到此复合类型中的整体值。

例如,可以如下描述一个 16 位有符号整数:

(defconst sint16-bindat-spec
  (let* ((max (ash 1 15))
	   (wrap (+ max max)))
    (bindat-type :pack-var v
		   (n uint 16 :pack-val (if (< v 0) (+ v wrap) v))
		   :unpack-val (if (>= n max) (- n wrap) n))))

然后其行为如下:

(bindat-pack sint16-bindat-spec -8)
     ⇒ "\377\370"

(bindat-unpack sint16-bindat-spec "\300\100")
     ⇒ -16320

最后,您可以使用 bindat-defmacro 定义新的 Bindat 类型表单以在 Bindat 类型表达式中使用:

Macro: bindat-defmacro name args &rest body ¶

定义一个名为 name 并采用参数 args 的新 Bindat 类型表达式。 它的行为遵循 defmacro 的行为,重要的区别是新形式只能在 Bindat 类型表达式中使用。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/advanceflow/elisp.git
git@gitee.com:advanceflow/elisp.git
advanceflow
elisp
Elisp
main

搜索帮助