Traceroute 详解

前言

如果您是网络管理员,系统管理员或任何系统操作团队的一员,那么您可能已经听说过名为TRACEROUTE的工具。默认情况下,它是大多数操作系统中都提供的非常方便的工具。

网络管理员和系统管理员在日常活动中最常使用此工具。它基本上是一个非常方便的网络诊断工具。跟踪路由工具的三个主要主要目标。通过traceroute实现的这些目标可以洞悉您的网络问题。

  1. 数据包通过的整个路径
  2. 路径中路由器和设备的名称和标识
  3. 网络延迟,或更具体地说是在路径上向每个设备发送和接收数据所花费的时间

它是一种工具,可用于确定数据到达目的地的路径,而无需实际发送数据。

就像我在文章中经常说的那样,了解特定工具的工作原理总是很有益的。因为它的用法不能帮助您理解问题并排除故障。但是其背后的工具概念始终可以使您深入了解问题。始终可以在网上甚至在Linux手册和信息页面中找到如何使用命令。

在本文中,我将解释traceroute的工作方式和traceroute工具的类型及其区别。我们还将研究Linux中可用于traceroute命令的细节。

敲黑板讲基础

您在互联网上发送的每个IP数据包都有一个称为TTL的字段。TTL表示生存时间。尽管将其称为“生存时间”,但实际上并非以秒为单位的时间,而是其他时间。

TTL不是用秒数来衡量的,而是用跳数来衡量的。它是数据包在丢弃之前可以通过互联网传播的最大跳数。

跃点只是源与目标之间所经过的计算机,路由器或其他网络设备。

如果根本没有TTL怎么办?如果IP数据包中没有TTL,则该数据包将不断地从一台路由器流向另一台路由器,并不断地搜索目的地。TTL值由发送者在其IP数据包内设置(使用系统或发送数据包的人不知道这些事情在幕后进行,而是由操作系统自动处理)。

如果在(跳)之间经过太多路由器后找不到目的地,并且TTL值变为0(表示不再需要被传输),则接收路由器将丢弃该数据包并通知原始发送者。

通知原始发送方TTL值已超过,它无法进一步转发数据包。

假设我需要达到10.1.136.23 IP地址,而我的默认TTL值为30跳。这意味着我最多可以经过30跳才能到达目的地,然后丢弃数据包。

但是介于两者之间的路由器将如何确定已达到TTL值限制。在发送到下一个路由器之前,位于源和目标之间的每个路由器将继续减小TTL值。这意味着如果我的默认TTL值为30,则我的第一个路由器会将其减小为29,然后将其发送到路径中的下一个路由器。

接收路由器将其设置为28,然后发送到下一个,依此类推。如果路由器接收到TT1为1的数据包(这意味着不再继续传输,也没有转发),则该数据包将被丢弃。但是丢弃该数据包的路由器将通知原始发送者TTL值已超过。

由接收到TTL为1的数据包的路由器发送回原始发送者的信息称为“ ICMP TTL超出消息”。当然,在互联网上,当您向接收方发送邮件时,接收方会知道发送方的地址。

因此,当路由器发送超过ICMP TTL的消息时,原始发送者将知道路由器的地址。

Traceroute利用此TTL超出的消息来查找穿过您的目的地路径的路由器(因为路由器发送的这些超出的消息将包含其地址)。

但是Traceroute如何使用TTL超时消息来找出两者之间的路由器/跳数?

您可能在想,TTL超时消息仅由接收到TTL为1的数据包的路由器发送。是的,您和接收方之间的每个路由器都不会发送TTL超时消息。然后,您将如何找到您与目的地之间的所有路由器/跳的地址。因为Traceroute的主要目的是识别您与目的地之间的跃点。

但是,您可以通过故意发送TTL值为1的IP数据包来利用路由器/跳之间发送TTL超出消息的行为。

在下图中,请参见整个过程的示例图,其中,发送方向远程服务器之一进行跟踪路由。

Traceroute工作说明

因此,假设我想对Google的公用DNS服务器(8.8.8.8)进行跟踪路由。我的traceroute命令及其结果如下所示。

 root@workstation:~# traceroute -n 8.8.8.8traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets1  192.168.0.1  6.768 ms  6.462 ms  6.223 ms2  183.83.192.1  5.842 ms  5.543 ms  5.288 ms3  183.82.14.5  5.078 ms  6.755 ms  6.468 ms4  183.82.14.57  20.789 ms  27.609 ms  27.931 ms5  72.14.194.18  17.821 ms  17.652 ms  17.465 ms6  66.249.94.170  19.378 ms  15.975 ms  23.017 ms7  209.85.241.21  16.633 ms  16.607 ms  17.428 ms8  8.8.8.8  17.144 ms  17.662 ms  17.228 ms

让我们看看到底发生了什么。当我执行traceroute -n 8.8.8.8的命令时,我的计算机要做的是制作一个UDP数据包,该UDP数据包将包含以下内容。

  • 我的源地址(这是我的IP地址)
  • 目的地址(8.8.8.8)
  • 无特殊用途和功能的目的地UDP端口号。意味着traceroute实用程序会将数据包发送到范围为33434至33534的UDP端口,该端口通常未使用。

因此,让我们看看这东西是如何工作的。

步骤1:“ 我的源地址”将创建一个数据包,该数据包的目标ip地址为8.8.8.8,目标端口号介于33434至33534之间。重要的是,它使TTL值1

步骤2: 当然,我的数据包将到达网关服务器。看到接收到的数据包后,我的网关服务器将TTL减小1(中间的所有路由器/跳台都将TTL值减小1)。一旦将TTL减少1(1-1 = 0)的值,则TTL值将变为零。因此,我的网关服务器将发回TTL超时消息。请记住,当我的网关服务器向我发送TTL超出消息时,它将发送我发送的初始数据包的前28个字节的标头。

步骤3: 收到此TTL超时消息后,我的traceroute程序将知道源地址和有关第一跳的其他详细信息(这是我的网关服务器。)。

步骤4:现在,traceroute程序将再次发送相同的UDP数据包,其目标地址为8.8.8.8,并在33434至33534之间发送一个随机的UDP目标端口。但这一次,我将设置初始 TTL2  这是因为我的网关路由器会将其减少1,然后转发发送到下一跳/路由器的同一数据包(由我的网关发送到其下一跳的数据包的TTL值为1)。

步骤5:在接收到UDP数据包后,到我的网关服务器的下一跳将再次将其减小为1,这意味着现在TTL再次变为0。因此,它将向我发送回ICMP超时消息,并带有其源地址,并且也是我发送的数据包的第一个28字节标头。

步骤6: 在收到超过TTL时间的消息后,我的traceroute程序将了解该跃点/路由器的IP地址,并将其显示在我的屏幕上。

步骤7:现在,我的traceroute程序再次使用类似的UDP数据包,并使用一个随机udp端口,其目标地址为8.8.8.8。但是这次,ttl的值设为3,这样当ttl到达第三个跃点/路由器时,ttl将自动变为0(请记住,我的网关及其下一个跃点会将其减小1)。这样它就会以TTL超时消息答复我,并且我的traceroute程序将知道该跃点/路由器的IP地址。

步骤8:在收到该答复后,traceroute程序将再次生成TTL值为4的UDP数据包。如果我也超过了TTL时间,那么我的traceroute程序将发送TTL为5的UDP数据包,依此类推。

但是我的traceroute程序将如何知道8.8.8.8的最终目标已经到达。traceroute程序会知道这一点,因为,当数据包8.8.8.8的原始接收者(记住所有UDP数据包的目标地址为8.8.8.8)收到请求时,它将向我发送一条消息,该消息将完全不同从所有消息“ TTL时间已超出”

当原始接收者(8.8.8.8)获得我的UDP数据包时,它将向我发送“ ICMP目标/端口不可达”消息。这势必会发生,因为我们总是在33434至33534之间发送一个随机UDP端口。因此,我的Traceroute程序将知道我们已经到达了最终目的地,并且将停止发送任何其他数据包。

现在,用言语描述的任何东西都称为理论。我们需要通过在执行traceroute时执行tcpdump来确认这一点。让我们看一下tcpdump的输出。请注意,我不会向您显示tcpdump的整个输出,因为它太长了。

在Linux机器的一个终端上运行traceroute。在另一个终端上,运行以下tcpdump命令以查看发生了什么。

 root@workstation:~# tcpdump -n '(icmp or udp)' -vvv12:13:06.585187 IP (tos 0x0, ttl 1, id 37285, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.43143 > 8.8.8.8.33434: [bad udp cksum 0xd157 -> 0x0e59!] UDP, length 3212:13:06.585218 IP (tos 0x0, ttl 1, id 37286, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.38682 > 8.8.8.8.33435: [bad udp cksum 0xd157 -> 0x1fc5!] UDP, length 3212:13:06.585228 IP (tos 0x0, ttl 1, id 37287, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.48381 > 8.8.8.8.33436: [bad udp cksum 0xd157 -> 0xf9e0!] UDP, length 3212:13:06.585237 IP (tos 0x0, ttl 2, id 37288, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.57602 > 8.8.8.8.33437: [bad udp cksum 0xd157 -> 0xd5da!] UDP, length 3212:13:06.585247 IP (tos 0x0, ttl 2, id 37289, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.39195 > 8.8.8.8.33438: [bad udp cksum 0xd157 -> 0x1dc1!] UDP, length 3212:13:06.585256 IP (tos 0x0, ttl 2, id 37290, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.47823 > 8.8.8.8.33439: [bad udp cksum 0xd157 -> 0xfc0b!] UDP, length 3212:13:06.585264 IP (tos 0x0, ttl 3, id 37291, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.52815 > 8.8.8.8.33440: [bad udp cksum 0xd157 -> 0xe88a!] UDP, length 3212:13:06.585273 IP (tos 0x0, ttl 3, id 37292, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.51780 > 8.8.8.8.33441: [bad udp cksum 0xd157 -> 0xec94!] UDP, length 3212:13:06.585281 IP (tos 0x0, ttl 3, id 37293, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.34782 > 8.8.8.8.33442: [bad udp cksum 0xd157 -> 0x2efa!] UDP, length 3212:13:06.585290 IP (tos 0x0, ttl 4, id 37294, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.53015 > 8.8.8.8.33443: [bad udp cksum 0xd157 -> 0xe7bf!] UDP, length 3212:13:06.585299 IP (tos 0x0, ttl 4, id 37295, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.58417 > 8.8.8.8.33444: [bad udp cksum 0xd157 -> 0xd2a4!] UDP, length 3212:13:06.585308 IP (tos 0x0, ttl 4, id 37296, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.55943 > 8.8.8.8.33445: [bad udp cksum 0xd157 -> 0xdc4d!] UDP, length 3212:13:06.585318 IP (tos 0x0, ttl 5, id 37297, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.33265 > 8.8.8.8.33446: [bad udp cksum 0xd157 -> 0x34e3!] UDP, length 3212:13:06.585327 IP (tos 0x0, ttl 5, id 37298, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.53485 > 8.8.8.8.33447: [bad udp cksum 0xd157 -> 0xe5e5!] UDP, length 3212:13:06.585335 IP (tos 0x0, ttl 5, id 37299, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.40992 > 8.8.8.8.33448: [bad udp cksum 0xd157 -> 0x16b2!] UDP, length 3212:13:06.585344 IP (tos 0x0, ttl 6, id 37300, offset 0, flags [none], proto UDP (17), length 60)192.168.0.102.41538 > 8.8.8.8.33449: [bad udp cksum 0xd157 -> 0x148f!] UDP, length 32

上面的输出仅显示了我的机器发送的UDP数据包。我将分别显示答复消息,以使其更加清楚。

注意每行的TTL值。它从1的TTL开始,然后从2开始,然后从3直到TTL6。但是您可能想知道为什么我的服务器发送3 UDP消息,其TTL值为1,然后是2然后是3。

其背后的原因是要计算平均往返时间。Traceroute程序向每个跃点发送三个UDP数据包,以测量确切的平均往返时间。往返时间不过是发送和接收回复所花费的时间(以毫秒为单位)。为了避免混淆,我一开始就没有提到这一点。

因此,最重要的是我的traceroute程序向每个跃点发送了三个UDP数据包,以简单地计算往返平均值。因为traceroute在您的输出中显示了这三个值。请更仔细地查看traceroute输出。它显示每个跃点三个毫秒的值。

现在,让我们看看通过TCPDUMP从所有跃点获得的回复。请注意,下面显示的回复消息是我上面所做的同一tcpdump的一部分,但为了让您更清楚地单独显示了消息。

需要注意的另一件事是,我的traceroute程序每次发送一个不同的随机UDP端口号。这是为了识别答复属于哪个数据包。如前所述,跃点和目的地发送的回复消息包含我们发送的原始数据包的标头,因此traceroute程序可以准确计算往返时间(对于发送到每个跃点的每三个UDP数据包),因为它可以轻松识别答复并相互关联。随机端口号是用于标识回复的标识符。

回复消息如下所示。

 192.168.0.1 > 192.168.0.102: ICMP time exceeded in-transit, length 68IP (tos 0x0, ttl 1, id 37285, offset 0, flags [none], proto UDP (17), le                                                                                        ngth 60)192.168.0.1 > 192.168.0.102: ICMP time exceeded in-transit, length 68IP (tos 0x0, ttl 1, id 37286, offset 0, flags [none], proto UDP (17), le                                                                                        ngth 60)183.83.192.1 > 192.168.0.102: ICMP time exceeded in-transit, length 60IP (tos 0x0, id 37288, offset 0, flags [none], proto UDP (17), length 60                                                                                        )192.168.0.1 > 192.168.0.102: ICMP time exceeded in-transit, length 68IP (tos 0x0, ttl 1, id 37287, offset 0, flags [none], proto UDP (17), le    

请注意上面显示的回复中的ICMP time over消息(我没有显示所有回复消息)。

现在,让我显示与“ ICMP超时”消息不同的最终消息。如前所述,此消息是无法访问的目标端口。我的traceroute程序将知道我们已经到达目的地了。

 8.8.8.8 > 192.168.0.102: ICMP 8.8.8.8 udp port 33458 unreachable, length 68IP (tos 0x80, ttl 2, id 37309, offset 0, flags [none], proto UDP (17), l                                                                                        ength 60)8.8.8.8 > 192.168.0.102: ICMP 8.8.8.8 udp port 33457 unreachable, length 68IP (tos 0x80, ttl 1, id 37308, offset 0, flags [none], proto UDP (17), l                                                                                        ength 60)8.8.8.8 > 192.168.0.102: ICMP 8.8.8.8 udp port 33459 unreachable, length 68IP (tos 0x80, ttl 2, id 37310, offset 0, flags [none], proto UDP (17), l   

请注意,从8.8.8.8到我的traceroute程序有3条回复。如前所述,traceroute使用不同的端口发送三个类似的UDP数据包,以简单地计算往返时间。最终目的地没有什么不同。

不同类型的Traceroute程序

有不同类型的traceroute程序。他们每个人的工作略有不同。但是它们背后的总体概念是相同的。它们全部使用TTL值。

为什么要使用不同的实现?那是因为您可以使用适合您的环境的一种。如果假设防火墙阻止了UDP流量,则可以为此使用另一个跟踪路由。 不同类型将在下面提到。

  • UDP跟踪路由
  • ICMP跟踪路由
  • TCP跟踪路由

我们之前使用的是UDP traceroute。它是linux traceroute程序使用的默认协议。但是,您可以通过以下命令要求Linux中的traceroute实用程序使用ICMP代替UDP。

 root@workstation:~# traceroute -I -n 8.8.8.8

跟踪路由的ICMP与UDP跟踪路由的工作方式相同。Traceroute程序将发送ICMP Echo Request消息,并且之间的跃点将以ICMP Time beyond消息进行回复。但是最终目的地将以ICMP Echo答复进行答复。

Windows操作系统中可用的Tracert命令默认情况下使用ICMP traceroute方法。

现在,最后一个是最有趣的一个。它称为TCP traceroute。之所以使用它,是因为几乎所有的防火墙和路由器之间都允许您发送TCP通信。如果数据包是通过端口80发送的,该端口是Web流量,则大多数路由器都允许该数据包。默认情况下,TCPTRACEROUTE将TCP SYN请求发送到端口80。

源和目标之间的所有路由器都将发送TTL超时消息,并且如果端口80关闭,则目标将发送RST数据包,或者将发送SYN / ACK数据包(但是tcptraceroute不会在接收到以下消息后建立tcp连接) SYN / ACK数据包,traceroute程序将发送一个RST数据包以关闭连接)。因此,traceroute程序知道目的地已经到达。

请注意,我在先前显示的traceroute命令中使用的-n选项不会进行DNS名称解析。否则,traceroute将发送dns查询途中发现的所有跃点。

现在主要的问题是我应该从icmp,udp和tcp traceroutes中使用哪一个?

一切都取决于当时的情况。假设源和目的地之间的路由器阻塞了特定协议,源就必须尝试使用其他协议。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/242751.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pandas操作excel

目录 一:创建excel 二:修改excel 三:查找excel 四:删除数据 五:合并excel数据 一:创建excel import pandas as pd # 创建DataFrame对象 data { Name: [Alice, Bob, Charlie], Age: [25, 30, 35], S…

Microsoft Visual C++ RunTime怎么下载?

64位下载链接 下载好程序后双击,勾选“我同意许可条款和条件”,然后点击“安装” 安装完成后点击“关闭”即可 感谢您的阅读与关注,服务器大本营助您成为更专业的服务器管理员!

32 登录页组件

效果演示 实现了一个登录页面的样式,包括一个容器、左侧和右侧部分。左侧部分是一个背景图片,右侧部分是一个表单,包括输入框、复选框、按钮和忘记密码链接。整个页面的背景色为白色,容器为一个圆角矩形,表单为一个半透…

华为机考入门python3--(0)模拟题2-vowel元音字母翻译

分类:字符串 知识点: 字符串转list,每个字符成为list中的一个元素 list(string) 字符串变大小写 str.upper(), str.lower() 题目来自【华为招聘模拟考试】 # If you need to import additional packages or classes, please import …

UE5 独立程序的网络TCP/UDP服务器与客户端基础流程

引擎源码版,复制\Engine\Source\Programs\路径下的BlankProgram空项目示例。 重命名BlankProgram,例如CustomTcpProgram,并修改项目名称。 修改.Build.cs内容 修改Target.cs内容 修改Private文件夹内.h.cpp文件名并修改.cpp内容 刷新引擎 …

C++入门学习(七)整型

整型就是整数类型的数据(-1,0,1等等) 数据类型占用空间取值范围short(短整型)2字节 (-2^15 ~ 2^15-1) 32768~32767 int(整型)4字节(-2^31 ~ 2^31-1)long(长整形) Windows为4字节, Linux为4字节(32位), 8字节(64位) (-2^31 ~ 2^31…

为什么需要放行回源IP

为什么需要放行回源IP 网站以“独享模式”成功接入WAF后,所有网站访问请求将先经过独享引擎配置的ELB然后流转到独享引擎实例进行监控,经独享引擎实例过滤后再返回到源站服务器,流量经独享引擎实例返回源站的过程称为回源。在服务器看来&…

16.5 参考文献——深度学习定位

16.5 一种高效鲁棒的多楼层室内环境指纹定位方法 同济大学 Zhao Y, Gong W, Li L, et al. An Efficient and Robust Fingerprint Based Localization Method for Multi Floor Indoor Environment[J]. IEEEa Internet of Things Journal, 2023. 2.相关工作 B.基于深度学习的…

ChatGPT时代对大数据应用的展望

前言: 2022年底,科技圈有个爆炸性新闻,ChatGPT的诞生,引发了世界范围内的震惊;人工智能在与人交流上有了划时代的技术突破,可以和人深入的理解交流,让许多公司和领域对这项技术有了更多遐想。对…

HarmonyOS之sqlite数据库的使用

从API Version 9开始,鸿蒙开发中sqlite使用新接口ohos.data.relationalStore 但是 relationalStore在 getRdbStore操作时,在预览模式运行或者远程模拟器运行都会报错,导致无法使用。查了一圈说只有在真机上可以正常使用,因此这里…

Docker进阶篇-安装MySQL主从复制

一、MySQL主服务器 1、新建主服务器容器实例3307 docker run -p 3307:3306 \--name mysql-master \--privilegedtrue \-v /mydata/mysql-master/log:/var/log/mysql \-v /mydata/mysql-master/data:/var/lib/mysql \-v /mydata/mysql-master/conf:/etc/mysql \-e MYSQL_ROOT_…

String在VS与Linux下的区别

目录 一、string的成员 1.VS 2.Linux 二、string的扩容机制 1. VS 2.Linux 一、string的成员 string是C标准库中的一个类模板,用于表示和操作字符串 string在 Windows 与 Linux 中的成员不是相同的 1.VS 4个成员:_str , _size , _capacity 和…

RHEL8_安装软件的方法和系统初始化

参考: 资料来自下面链接: 第1章 动手部署一台Linux操作系统 | 《Linux就该这么学》 (linuxprobe.com)https://www.linuxprobe.com/basic-learning-01.html 一、安装软件的方法 RPM、Yum、DNF 常用的RPM软件包命令 命令 作用 rpm -ivh filename.rpm安装…

proteus8.15安装教程

proteus8.15安装教程 1.管理员运行 2.一直NEXT到这一步,需要注意,一定要选这一个 3.选中后出现 4.一直下一步到更新 这边结束后准备激活: 1.安装激活插件,先关闭防火墙 2.下一步 3.最后,将数据库放在根目录下 …

【总结】Linux命令中文帮助手册

1. 为什么要总结Linux命令中文帮助手册 Linux 官方并不提供中文的 help、man 帮助手册。网络上已有的前人翻译过的中文手册版本比较老,且翻译存在误差。从记忆角度来看,Linux 很多命令都不一定记得住详细的用法,易遗忘,缺少经验总…

PIG框架学习3——Redisson 实现业务接口幂等

零、前言 ​ 业务接口幂等问题是在开发中遇到的,如果对业务接口代码不进行幂等控制,并且在前端没有对请求进行限制的情况下,可能会出现多次对接口调用,导致错误异常的发生。就上述情况,对PIGX自带的业务接口幂等实现进…

渐进式哈希和SpringBoot集成redis

一)渐进式遍历 1)keys可以一次性的把整个redis中的所有的key都获取到,keys *,这个操作比较危险可能会一下子得到过多的key,从而会造成redis阻塞,因为redis是一个单线程的服务器,通过渐进式遍历,可以做到既能…

uni-app使用HBuilderX打包Web项目

非常简单,就是容易忘记 一、找到manifest.json配置Web配置 二、源码视图配置 "h5" : {"template" : "","domain" : "xxx.xx.xx.xxx","publicPath" : "./","devServer" : {&quo…

数据库防水坝是什么?有什么作用?有哪些优势?

数据库是公司重要IT资产,是公司数据存储、数据整合、数据备份等重要载体。所以保障数据库安全至关重要。目前保障数据库安全产品较多,例如堡垒机、防火墙、数据库防水坝等等。今天我们就先来简单了解一下数据库防水坝是什么?有什么作用&#…

C语言从入门到实战——编译和链接

编译和链接 前言一、 翻译环境和运行环境二、 翻译环境2.1 预处理(预编译)2.2 编译2.2.1 词法分析2.2.2 语法分析2.2.3 语义分析 2.3 汇编2.4 链接 三、 运行环境 前言 在C语言中,编译和链接是将源代码转换为可执行文件的两个主要步骤。 编…