crontab 또는 cron 테이블은 반복적인 작업을 예약하여 일상 작업을 단순화하는 데 도움이 되는 Linux 시스템 프로세스/데몬입니다. 이 튜토리얼에서는 보안 연결을 사용하여 crontab을 조작할 수 있는 동적 PHP 클래스를 생성합니다. 배경: Crontab 개요 백그라운드에서 실행할 작업을 예약할 수 있다는 것은 정말 멋진 일입니다! SQL 데이터베이스를 백업하고, 이메일을 받거나 보내고, 정리 작업을 실행하고, 성능을 분석하고, 심지어 RSS 피드를 스크랩할 수도 있습니다. cron 작업은 정말 훌륭합니다! 새 과제를 예약하는 구문이 처음에는 어려워 보일 수 있지만 일단 분석해 보면 비교적 이해하기 쉽습니다. 크론 작업에는 항상 5개의 열이 있으며, 각 열은 시간순 연산자를 나타내며 그 뒤에는 전체 경로와 실행할 명령이 표시됩니다. 으아악 각 시간 열은 작업 일정과 특정한 관련성을 갖습니다. 그 내용은 다음과 같습니다: Minutes는 각각 0~59 범위의 특정 시간의 분 수를 나타냅니다. Hours는 특정 날짜의 시간(각각 0~23)을 나타냅니다. Days는 1~31 범위의 특정 월의 일 수를 나타냅니다. Month는 해당 연도의 월을 나타내며 각각 1~12로 번호가 매겨져 있습니다. 요일은 일요일부터 토요일까지의 요일을 나타내며 숫자는 각각 0-6입니다. 으아악 예를 들어, 매월 1일 오전 12시에 작업을 예약하려는 경우 다음과 같습니다. 으아악 반면 매주 토요일 오전 8시 30분에 작업이 실행되도록 예약하려면 다음과 같이 작성합니다. 으아악 일정을 더욱 맞춤화할 수 있는 다양한 연산자도 있습니다: 쉼표는 모든 cron 열에 대해 쉼표로 구분된 값 목록을 만드는 데 사용됩니다. 대시는 값 범위를 지정하는 데 사용됩니다. 별표는 all 또는 각 값을 지정하는 데 사용됩니다. 기본적으로 crontab은 예약된 작업이 실행될 때마다 이메일 알림을 보냅니다. 그러나 많은 경우에는 이것이 필요하지 않습니다. 이 명령의 표준 출력을 blackhole 또는 /dev/null设备来轻松抑制此功能。本质上,这是一个将丢弃写入其中的所有内容的文件。输出重定向是通过 > 연산자로 리디렉션하여 이를 수행할 수 있습니다. 매주 토요일 오전 8시 30분에 예약된 작업을 실행하는 샘플 cron 작업을 사용하여 표준 출력을 블랙홀로 리디렉션하는 방법을 살펴보겠습니다. 으아악 또한 표준 출력을 null 장치로 리디렉션하는 경우 표준 오류도 리디렉션해야 할 수도 있습니다. 표준 출력이 이미 리디렉션된 위치(빈 장치)로 표준 오류를 리디렉션하면 됩니다. 으아악 PHP로 crontab 관리: Blueprint PHP를 사용하여 crontab을 관리하려면 편집 중인 crontab의 사용자로 원격 서버에서 명령을 실행할 수 있어야 합니다. PHP에서 제공하는 SSH2 라이브러리를 사용할 수도 있지만, 원격 서버와 통신하기 위해 다양한 프로토콜을 지원하는 phpseclib 라이브러리를 사용하겠습니다. phpseclib 라이브러리 설치 및 사용 방법 phpseclib 라이브러리(PHP 보안 통신 라이브러리)는 SSH, SFTP, SSL/TLS와 같은 보안 통신 프로토콜을 위한 메서드 및 클래스 세트를 제공하는 PHP 라이브러리입니다. 이를 통해 외부 명령줄 도구나 확장에 의존하지 않고도 보안 통신 기능을 애플리케이션에 통합할 수 있습니다. PHP에서 제공하는 핵심 SSH2 기능 대신 phpseclib를 사용하는 것을 선호하는 이유가 궁금하다면 phpseclib 라이브러리의 이점을 살펴보겠습니다. SSH2 라이브러리는 제한된 기능을 제공하며 모든 SSH 기능을 지원하지 않을 수도 있습니다 다른 프로토콜 간에 쉽게 전환할 수 있는 추상화 제공 기본 프로토콜의 복잡성을 추상화 사용하기 쉽고 직관적입니다 외부 종속성이나 확장이 필요하지 않습니다 SSH, SFTP, SCP, FTPS, SSL/TLS 등을 포함한 여러 보안 통신 프로토콜을 지원합니다. 완벽한 문서화, 적극적인 개발 및 커뮤니티 지원 보시다시피 phpseclib 라이브러리는 보안 통신 요구 사항에 맞는 보다 포괄적이고 강력한 솔루션을 제공할 수 있습니다. 설치 방법을 살펴보겠습니다. 다음 코드 조각에 표시된 대로 Composer를 사용하여 phpseclib 라이브러리를 설치할 수 있습니다. 으아악 설치한 후에는 다음 코드 조각에 표시된 대로 PHP 스크립트 상단에 자동 로더 스크립트를 포함하여 PHP 코드에서 phpseclib 라이브러리 사용을 시작할 수 있습니다. 으아악 원격 SSH2 로그인 방법을 간단히 살펴보겠습니다. 으아악 phpseclib 라이브러리에 대한 간략한 소개입니다. Ssh2_crontab_manager 수업 템플릿 이 섹션에서는 클래스에서 어떤 메서드를 구현해야 하는지 논의합니다. 我们的类必须能够作为适当的用户进行连接和身份验证,以便执行命令并有权访问用户的 crontab。因此,我们将建立一个 SSH2 连接并在构造函数本身内对其进行身份验证。 建立经过身份验证的连接后,我们需要一种方法来处理我们将要运行的各种命令的执行。我们将使用 phpseclib 库提供的 exec() 方法。一般来说,当我们需要在远程服务器上执行命令时,我们会从类的其他方法中调用此方法。 接下来,我们需要能够将 crontab 写入文件,以便我们能够切实访问它。当我们完成这个文件时,我们还需要一种方法来删除它。我们将处理将 crontab 输出到文件的方法定义为 write_to_file(),并将删除该文件的方法定义为 remove_file()。 当然,我们还需要一种创建和删除 cron 作业的方法。因此,我们将分别定义 append_cronjob() 和 remove_cronjob() 方法。 如果我们删除了唯一的或最后一个 crontab 作业,我们应该可以选择删除整个 crontab,而不是尝试创建一个空的 crontab。为了处理这种情况,我们可以使用 remove_crontab() 方法。 最后,我们将为我们的类创建两个辅助方法。第一个方法将返回一个布尔值,将简单地检查临时 cron 文件是否存在。后者将用于在发生错误时显示错误消息。我们将这些方法分别命名为 crontab_file_exists() 和 error_message()。 我们的准系统 PHP 类应该如下所示: 로그인 후 복사 实现 Ssh2_crontab_manager 类 在本节中,我们将实现类的各种方法。 构造函数 类构造函数主要负责建立和验证 SSH2 连接。 让我们看一下__construct方法。 public function __construct($host = null, $port = null, $username = null, $password = null) { $path_length = strrpos(__FILE__, "/"); $this->path = substr(__FILE__, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}"; try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) { throw new Exception("Please specify the host, port, username, and password!"); } $this->connection = new SSH2($host, $port); if (!$this->connection->login($username, $password)) { throw new Exception("Could not authenticate '{$username}' using password: '{$password}'."); } } catch (Exception $e) { $this->error_message($e->getMessage()); } } 로그인 후 복사 首先,它需要四个参数,每个参数都有一个默认值NULL: $host:代表我们要连接的远程服务器的IP地址。 $port:用于 SSH2 连接的端口。 $username:代表服务器的用户登录名。 $password:代表服务器的用户密码。 首先,它计算当前文件的路径并将其设置为$this->path变量。它还设置要在 $this->handle 中使用的 cron 文件的名称,并在 $this->cron_file 中构造 cron 文件的完整路径。 p> 然后,我们检查是否缺少任何必需的参数,如果缺少,则会引发异常并附带相应的错误消息。 接下来,它通过传入 $host 和 $port 值来创建 SSH2 类的实例,以建立 SSH 连接。 最后,它尝试通过调用 SSH2 连接对象的 login() 方法,使用提供的 $username 和 $password 进行身份验证。如果身份验证成功,则建立连接。如果失败,则会抛出异常。 exec 方法 exec 方法负责在远程服务器上执行命令。它类似于手动将命令输入到 shell(例如 PuTTY)中。为了获得更大的灵活性,我们将公开此方法,以便用户可以实际执行他们可能需要运行的任何其他命令。 让我们看一下 exec 方法。 public function exec() { $argument_count = func_num_args(); try { if (!$argument_count) { throw new Exception("There is nothing to execute, no arguments specified."); } $arguments = func_get_args(); $command_string = ($argument_count > 1) ? implode(" && ", $arguments) : $arguments[0]; $stream = $this->connection->exec($command_string); if (!$stream) { throw new Exception("Unable to execute the specified commands: {$command_string}"); } } catch (Exception $e) { $this->error_message($e->getMessage()); } return $this; } 로그인 후 복사 我们的 exec() 方法使用 phpseclib 库的 exec 方法来执行命令。 我们在此方法中要做的第一件事是定义一个变量,表示传递的参数总数。我们将使用 PHP 的 func_num_args() 函数来获取此计数。接下来,在 try 块中,我们将检查是否有任何参数传递给此方法。如果参数计数为 0,我们将抛出一个带有适当消息的新异常。 接下来,使用 func_get_args() 函数,我们将创建一个包含传递给此方法的所有参数的数组。然后,我们将使用三元运算符定义一个新变量 $command_string,作为我们将执行的实际 Linux 命令的单行字符串表示形式。 如果我们确实要执行多个命令,我们将使用 PHP 的 implode() 函数将参数数组解析为字符串。我们将向 implode() 传递两个参数:粘合或分隔符,它基本上只是一个将插入到数组元素和数组本身之间的字符串。我们将使用 Linux 运算符 && 分隔每个元素,这允许我们在一行上顺序执行多个命令! 准备好命令并将其解析为字符串后,我们现在可以尝试使用 SSH2 类中的 exec() 方法通过已建立的 SSH 连接执行命令字符串。返回的值存储在 $stream 变量中。 write_to_file 方法 下一个方法,write_to_file(),将负责将现有的 crontab 写入临时文件,或者在不存在 cron 作业的情况下创建一个空白临时文件。它需要两个参数: 我们将创建的临时文件的路径 我们在创建它时应该使用的名称 write_to_file 方法应如下所示。 public function write_to_file($path=NULL, $handle=NULL) { if (! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path; $this->cron_file = "{$this->path}{$this->handle}"; $init_cron = "crontab -l > {$this->cron_file} && [ -f {$this->cron_file} ] || > {$this->cron_file}"; $this->exec($init_cron); } return $this; } 로그인 후 복사 首先,我们使用布尔型 crontab_file_exists() 方法检查 cron 文件是否已存在,我们很快就会创建该方法。如果该文件存在,则无需继续。如果不是,我们将使用三元运算符来检查 $path 和 $handle 属性以确定它们是否为 NULL代码>.如果它们中的任何一个为NULL,我们将使用构造函数中预定义的回退来定义它们。 然后,我们将这些属性连接在一起形成一个新属性,它表示临时 cron 文件的完整路径和文件名。 接下来,我们使用带有 -l 参数的 Linux 命令 crontab 将用户 crontab 显示为标准输出。然后,使用 Linux 的 > 运算符,我们将标准输出或 STDOUT 重定向到我们的临时 cron 文件,而不是连接 $this->cron_file进入命令字符串!使用 > 运算符将输出重定向到文件将始终创建该文件(如果该文件不存在)。 这非常有效,但前提是 crontab 中已经安排了作业。如果没有 cron 作业,则永远不会创建此文件!不过,使用 && 运算符,我们可以向该字符串附加其他命令/表达式。因此,让我们附加一个条件表达式来检查 cron 文件是否存在。如果文件不存在,则该表达式的计算结果为 false。因此,如果需要,我们可以在此条件后使用 || 或 or 运算符来创建新的空白文件。 如果条件评估为 false,则将执行位于 or 之后的命令。现在,通过再次使用 > 运算符,这次无需在其前面添加特定命令,我们可以创建一个新的空白文件。所以本质上,这串命令会将 crontab 输出到一个文件,然后检查该文件是否存在,这将表明 crontab 中有条目,然后创建一个新的空白文件(如果不存在)。 最后,我们调用了 exec() 方法并将命令字符串作为唯一的参数传递给它。然后,为了使该方法也可链接,我们将返回 $this。 remove_file 方法 让我们快速浏览一下 remove_file 方法。 public function remove_file() { if ($this->crontab_file_exists()) $this->exec("rm {$this->cron_file}"); return $this; } 로그인 후 복사 在此方法中,我们使用辅助方法 crontab_file_exists() 来检查临时 cron 文件是否存在,然后执行 Linux 命令 rm如果有则将其删除。像往常一样,我们还将返回 $this 以保持可链接性。 append_cronjob 方法 append_cronjob 方法通过向临时 cron 文件添加新作业/行,然后在该文件上执行 crontab 命令来创建新的 cron 作业,该命令将安装所有这些工作作为新的 crontab。 看起来像这样: public function append_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to append! Please specify a cron job or an array of cron jobs."); $append_cronfile = "echo '"; $append_cronfile .= (is_array($cron_jobs)) ? implode("n", $cron_jobs) : $cron_jobs; $append_cronfile .= "' >> {$this->cron_file}"; $install_cron = "crontab {$this->cron_file}"; $this->write_to_file()->exec($append_cronfile, $install_cron)->remove_file(); return $this; } 로그인 후 복사 append_cronjob() 方法采用一个参数 $cron_jobs,该参数可以是一个字符串,也可以是表示要附加的 cron 作业的字符串数组。 我们将通过确定 $cron_jobs 参数是否为 NULL 来启动此方法。如果是,我们将调用 error_message() 方法来停止任何进一步的执行并向用户显示错误消息。 接下来,我们将一个新变量 $append_cronfile 定义为字符串,文本 echo 后跟一个空格,末尾有一个单引号。我们将立即用我们添加的各种 cron 作业以及结束引号填充此字符串。我们将使用字符串连接运算符 .= 构建此字符串。 接下来,使用三元运算符,我们确定 $cron_jobs 是否是数组。如果是,我们将使用换行符 n 对该数组进行内爆,以便每个 cron 作业都在 cron 文件中写入自己的行。如果 $cron_jobs 参数不是数组,我们只需将该作业连接到 $append_cron 字符串,而不进行任何特殊处理。 本质上,我们可以通过再次将标准输出重定向到文件中来将我们的任务回显到 cron 文件中。因此,通过使用字符串连接运算符,我们将在命令字符串中附加右单引号,以及 Linux 运算符 >> ,后跟 cron 文件的完整路径和文件名。 >> 运算符与始终覆盖文件的 > 运算符不同,它将输出附加到文件末尾。因此,通过使用此运算符,我们不会覆盖任何现有的 cron 作业。 接下来,我们将变量定义为字符串,并使用我们将用来安装即将创建的新 cron 文件的命令。这就像调用 crontab 命令一样简单,后跟 cron 文件的路径和文件名。 最后,在通过 exec() 方法执行这些命令之前,我们首先调用 write_to_file() 方法来创建临时 cron 文件。然后,在链中,我们将执行这些命令并调用remove_file()方法来删除临时文件。最后,我们将返回 $this ,以便 append_cronjob() 方法可链接。 remove_cronjob 方法 既然我们可以创建新的 cron 作业,那么我们也有能力删除它们,这是合乎逻辑的!这就是 remove_cronjob 方法的目的。 让我们来看看。 public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs."); $this->write_to_file(); $cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES); if (empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty."); $original_count = count($cron_array); if (is_array($cron_jobs)) { foreach ($cron_jobs as $cron_regex) $cron_array = preg_grep($cron_regex, $cron_array, PREG_GREP_INVERT); } else { $cron_array = preg_grep($cron_jobs, $cron_array, PREG_GREP_INVERT); } return ($original_count === count($cron_array)) ? $this->remove_file() : $this->remove_crontab()->append_cronjob($cron_array); } 로그인 후 복사 它需要一个参数,它是一个(简单的)正则表达式。它将用于在 crontab 中查找匹配的作业并相应地删除它们。 创建 cron 文件后,我们现在将使用 PHP 的 file() 函数将其读入数组。该函数将给定的文件解析为一个数组,每一行作为一个数组元素。我们将 cron 文件作为第一个参数传递给此函数,然后设置一个特殊标志 FILE_IGNORE_NEW_LINES,这将强制 file() 忽略所有新行。因此,我们有一个仅包含 cron 作业本身的数组。如果没有安排 cron 作业,该数组将为空。以后就没有继续下去的理由了。因此,我们检查了 $cron_array 是否为空,如果是则停止执行。 现在,我们确定 $cron_jobs 参数是否是一个数组。如果它是一个数组,我们使用 foreach 循环对其进行迭代。在该循环中,我们执行函数 preg_grep()。这个漂亮的函数与 preg_match() 不同,将返回与指定的正则表达式匹配的所有数组元素的数组。然而,在这种情况下,我们想要不匹配的数组元素。换句话说,我们需要一个包含要保留的所有 cron 作业的数组,以便我们可以仅使用这些作业来初始化 crontab。因此,我们将在此处设置一个特殊标志 PREG_GREP_INVERT,这将导致 preg_grep() 返回一个包含所有不匹配元素的数组。 正则表达式。因此,我们将拥有一个包含我们想要保留的所有 cron 作业的数组。 如果 $cron_jobs 参数不是数组,我们将以相同的方式继续,但不进行任何迭代。同样,我们将 $cron_array 重新定义为设置了 PREG_GREP_INVERT 标志的 preg_grep() 函数的结果数组。 通过我们的 $cron_array 设置,现在我们将该数组的当前长度与其原始长度进行比较,原始长度缓存在变量 $original_count 中。如果长度相同,我们将简单地返回 remove_file() 方法来删除临时 cron 文件。如果它们不匹配,我们将删除现有的 crontab,然后安装新的。 remove_crontab 方法 删除整个 crontab 相对容易实现,如以下代码片段所示。 public function remove_crontab() { $this->exec("crontab -r")->remove_file(); return $this; } 로그인 후 복사 其他辅助方法 随着 cron 管理类的编写,我们现在来看看我们在整个类中使用的两个小但有用的方法,crontab_file_exists() 和 error_message (). crontab_file_exists 方法 此方法返回 PHP 的 file_exists() 方法的结果,true 或 false,具体取决于临时 cron 文件是否存在. private function crontab_file_exists() { return file_exists($this->cron_file); } 로그인 후 복사 error_message 方法 此方法采用单个参数(一个字符串),表示我们要显示的错误消息。然后,我们将调用 PHP 的 die() 方法来停止执行并显示此消息。字符串本身将连接到 元素中,并应用简单的样式。 private function error_message($error) { die("ERROR: {$error}"); } 로그인 후 복사 完整的类如下所示: path = substr(__FILE__, 0, $path_length) . '/'; $this->handle = 'crontab.txt'; $this->cron_file = "{$this->path}{$this->handle}"; try { if (is_null($host) || is_null($port) || is_null($username) || is_null($password)) { throw new Exception("Please specify the host, port, username, and password!"); } $this->connection = new SSH2($host, $port); if (!$this->connection->login($username, $password)) { throw new Exception("Could not authenticate '{$username}' using password: '{$password}'."); } } catch (Exception $e) { $this->error_message($e->getMessage()); } } public function exec() { $argument_count = func_num_args(); try { if (!$argument_count) { throw new Exception("There is nothing to execute, no arguments specified."); } $arguments = func_get_args(); $command_string = ($argument_count > 1) ? implode(" && ", $arguments) : $arguments[0]; $stream = $this->connection->exec($command_string); if (!$stream) { throw new Exception("Unable to execute the specified commands: {$command_string}"); } } catch (Exception $e) { $this->error_message($e->getMessage()); } return $this; } public function write_to_file($path=NULL, $handle=NULL) { if (! $this->crontab_file_exists()) { $this->handle = (is_null($handle)) ? $this->handle : $handle; $this->path = (is_null($path)) ? $this->path : $path; $this->cron_file = "{$this->path}{$this->handle}"; $init_cron = "crontab -l > {$this->cron_file} && [ -f {$this->cron_file} ] || > {$this->cron_file}"; $this->exec($init_cron); } return $this; } public function remove_file() { if ($this->crontab_file_exists()) $this->exec("rm {$this->cron_file}"); return $this; } public function append_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to append! Please specify a cron job or an array of cron jobs."); $append_cronfile = "echo '"; $append_cronfile .= (is_array($cron_jobs)) ? implode("n", $cron_jobs) : $cron_jobs; $append_cronfile .= "' >> {$this->cron_file}"; $install_cron = "crontab {$this->cron_file}"; $this->write_to_file()->exec($append_cronfile, $install_cron)->remove_file(); return $this; } public function remove_cronjob($cron_jobs=NULL) { if (is_null($cron_jobs)) $this->error_message("Nothing to remove! Please specify a cron job or an array of cron jobs."); $this->write_to_file(); $cron_array = file($this->cron_file, FILE_IGNORE_NEW_LINES); if (empty($cron_array)) $this->error_message("Nothing to remove! The cronTab is already empty."); $original_count = count($cron_array); if (is_array($cron_jobs)) { foreach ($cron_jobs as $cron_regex) $cron_array = preg_grep($cron_regex, $cron_array, PREG_GREP_INVERT); } else { $cron_array = preg_grep($cron_jobs, $cron_array, PREG_GREP_INVERT); } return ($original_count === count($cron_array)) ? $this->remove_file() : $this->remove_crontab()->append_cronjob($cron_array); } public function remove_crontab() { $this->exec("crontab -r")->remove_file(); return $this; } private function crontab_file_exists() { return file_exists($this->cron_file); } private function error_message($error) { die("ERROR: {$error}"); } } ?> 로그인 후 복사 将它们放在一起 现在我们已经完成了 cron 管理类,让我们看一些如何使用它的示例。 实例化类并建立经过身份验证的连接 首先,让我们创建一个新的类实例。请记住,我们需要将 IP 地址、端口、用户名和密码传递给类构造函数。 로그인 후 복사 附加单个 Cron 作业 建立经过身份验证的连接后,让我们看看如何创建一个新的单一 cron 作业。 append_cronjob('30 8 * * 6 home/path/to/command/the_command.sh >/dev/null 2>&1'); 로그인 후 복사 附加 Cron 作业数组 附加多个 cron 作业与附加单个 cron 作业一样简单。我们只需将一个数组传递给 append_cronjob() 方法即可。 /dev/null 2>&1' ); $crontab->append_cronjob($new_cronjobs); 로그인 후 복사 删除单个 Cron 作业 与创建单个 cron 作业的方式类似,我们现在将删除一个。不过,这一次,我们将使用正则表达式来查找适当的任务。该正则表达式可以根据您的需要简单或复杂。事实上,有多种方法可以通过正则表达式来执行您正在寻找的任务。例如,如果您需要删除的任务是唯一的,因为正在运行的命令未在 crontab 中的其他任何地方使用,则您可以简单地将命令名称指定为正则表达式。此外,如果您想删除某个月份的所有任务,您可以简单地编写一个正则表达式来查找给定月份的所有作业的匹配项。 remove_cronjob($cron_regex); 로그인 후 복사 删除 Cron 作业数组 删除多个 cronjob 的处理方式与删除单个 cronjob 的方式相同,但有一个例外:我们将向 remove_cronjob() 方法传递一个 cron job 正则表达式数组。 p> remove_cronjob($cron_regex); 로그인 후 복사 结论 就这些了,伙计们!我希望您喜欢阅读这篇文章,就像我喜欢写这篇文章一样,并且您对 crontab 以及使用 PHP 管理它有了新的见解。非常感谢您的阅读。