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.mzph.cn/news/637960.shtml

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

相关文章

二叉树 - 堆 | 数据结构中的小技巧大作用

📷 江池俊: 个人主页 🔥个人专栏: ✅数据结构冒险记 ✅C语言进阶之路 🌅 有航道的人,再渺小也不会迷途。 文章目录 一、堆的概念及介绍二、结构图示三、堆的代码实现(图解)3.1 创…

HackTheBox - Medium - Linux - Noter

Noter Noter 是一种中型 Linux 机器,其特点是利用了 Python Flask 应用程序,该应用程序使用易受远程代码执行影响的“节点”模块。由于“MySQL”守护进程以用户“root”身份运行,因此可以通过利用“MySQL”的用户定义函数来利用它来获得RCE并…

快速上手的AI工具-文心3.5vs文心4.0

前言 大家好晚上好,现在AI技术的发展,它已经渗透到我们生活的各个层面。对于普通人来说,理解并有效利用AI技术不仅能增强个人竞争力,还能在日常生活中带来便利。无论是提高工作效率,还是优化日常任务,AI工…

一篇文章看懂云渲染,云渲染是什么?云渲染如何计费?云渲染怎么选择

云渲染是近年兴起的新行业,很多初学者对它不是很了解,云渲染是什么?为什么要选择云渲染?它是如何计费的又怎么选择?这篇文章我们就带大家了解下吧。 云渲染是什么 云渲染简单来说就是把本地的渲染工作迁移到云端进行的…

以超市数据微案例-fineBI可视化分析

一、入门案例: 2.分析思路: 数据清晰界面中添加毛利额计算 **所以在新增步骤之后,必须点击保存并更新,否则可视化界面中无法使用最新的数据 4、数据可视化分析 1)销售额最高的十大商品种类 为1-8月超市数据&#xff…

响应式编程

Reactive-Stream Reactive Streams是JVM面向流的库的标准和规范 1、处理可能无限数量的元素 2、有序 3、在组件之间异步传递元素 4、强制性非阻塞,背压模式 在Java中,常用的背压机制是响应式流编程中的反压(Reactive Streams Backpressure)。反压是一种生产者-消费者模型,…

【刷题笔记4】

动态规划题目汇总 斐波那契数列:1,1,2,3,5,8,13…… 递归一把解决三类问题:1.数据定义是按照递归的(斐波那契数列)。2.问题解法是按递归算法实现的。 3.数据…

JUC-Java内存模型JMM

JMM概述 Java Meory Model java内存模型。在不同的硬件和不同的操作系统上,对内存的访问方式是不一样的。这就造成了同一套java代码运行在不同的操作系统上会出问题。JMM就屏蔽掉硬件和操作系统的差异,增加java代码的可移植性。这是一方面。 另一方面JM…

ios适配虚拟home键

在H5开发过程中遇到一个兼容性问题。iphone手机的虚拟home键会对屏幕底部的内容造成遮挡。要处理此问题,需要清楚安全区域这个概念。 安全区域 根据刘海和虚拟Home键,Apple为其设备提供了屏幕安全区域的视觉规范 竖屏:竖屏的时候&#xff…

UE5 C++学习笔记 常用宏的再次理解

1.随意创建一个类,他都有UCLASS()。GENERATED_BODY()这样的默认的宏。 UCLASS() 告知虚幻引擎生成类的反射数据。类必须派生自UObject. (告诉引擎我是从远古大帝UObject中,继承而来,我们是一家人,只是我进化了其他功能…

矩阵和矩阵如何相乘?

矩阵与矩阵相乘遵循特定的数学规则。为了相乘,第一个矩阵的列数必须等于第二个矩阵的行数。矩阵乘法的结果是一个新矩阵,其行数等于第一个矩阵的行数,列数等于第二个矩阵的列数。矩阵乘法不满足交换律,即 AB≠BA。 例子&#xff…

131. 分割回文串 - 力扣(LeetCode)

问题描述 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。 回文串 是正着读和反着读都一样的字符串。 输入示例 s "aab"输出示例 [["a","a","b"],["…

Unity 面试篇|(九)操作系统与网络篇 【全面总结 | 持续更新】

目录 1. 客户端与服务器交互方式有几种?2. OSI七层模型有哪些,每一层的作用3. UDP/TCP含义,区别4. TCP/IP协议栈各个层次及分别的功能?5. 写出WWW的几个方法?6. Socket粘包7. Socket的封包、拆包8. Socket 客户端 队列…

数学建模常见算法的通俗理解(2)

目录 6 K-Means(K-均值)聚类算法(无需分割数据即可分类) 6.1 粗浅理解 6.2 算法过程 6.2.1 选定质心 6.2.2 分配点 6.2.3 评价 7 KNN算法(K近邻算法)(K个最近的决定方案) 7.…

【每日一题】按分隔符拆分字符串

文章目录 Tag题目来源解题思路方法一:遍历方法二:getline 写在最后 Tag 【遍历】【getline】【字符串】【2024-01-20】 题目来源 2788. 按分隔符拆分字符串 解题思路 方法一:遍历 思路 分隔符在字符串开始和结束位置时不需要处理。 分隔…

Crow:实现点击下载功能

Crow:设置网站的index.html-CSDN博客 讲述了如何完成一个最简单的网页的路由 很多网页提供了下载功能,怎么实现呢,其实也很简单。 假设网页的目录结构如图 $ tree static static ├── img │ └── goodday.jpg └── index.html //index.html <html> <body&…

专业137总分439东南大学920专业基础综合考研经验电子信息与通信电路系统芯片

我本科是南京信息工程大学&#xff0c;今年报考东南大学信息学院&#xff0c;成功逆袭&#xff0c;专业137&#xff0c;政治69&#xff0c;英语86&#xff0c;数一147&#xff0c;总分439。以下总结了自己的复习心得和经验&#xff0c;希望对大家复习有一点帮助。啰嗦一句&…

C++ :命名空间域

目录 冲突与命名&#xff1a; 举个例子&#xff1a; 全局与局部&#xff1a; 域作用限定符&#xff1a; 命名空间域&#xff1a; 冲突与命名&#xff1a; 在C语言中&#xff0c;我们通常会使用stdlib.h 而stdlib.h 本质上是一个函数的库&#xff0c;在程序中使用的大多数…

Java学习笔记(八)——Lambda表达式

文章目录 Lambda表达式Lambda表达式的省略写法Lambda练习练习1练习2 算法题算法题1 斐波那契数列算法题2 猴子吃桃子算法题3 爬楼梯 Lambda表达式 Lambda表达式是JDK8开始的一种新语法形式。 基本作用&#xff1a;简化函数式接口的匿名内部类的写法。 注意&#xff1a; Lam…

2023年总结我所经历的技术大变革

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…