Linux网络编程:网络基础概念(下)

目录

前言:

一、网络传输基本流程

1.1、认识MAC地址

1.2、认识IP地址

二、socket编程预备 

2.1、端口号

2.2、传输层的代表

2.3、网络字节序 

2.4、sockaddr 结构

总结:


前言:

大家好,上一篇文章,我们说到了协议。

我们说,协议,就是一种约定!!协议是分层的,本质原因是因为问题是分层的。

在代码层面上,协议就是通信双方都认识的数据结构。

那么我们今天继续来学习网络基础的概念。

一、网络传输基本流程

两台独立的主机用一根网线连接起来,他们之间是否能相互通信?

是的。

几个独立的主机通过网线与交换机连接起来,就形成了局域网。

每台主机在局域网上,要有唯一的标识来保证主机的唯一性:mac 地址。

1.1、认识MAC地址

MAC 地址是 网络设备的物理硬件地址,用于在 局域网(LAN) 内唯一标识一台设备(如电脑、手机、路由器等)。它工作在 OSI 模型的第 2 层(数据链路层),主要用于 以太网(Ethernet)、Wi-Fi(802.11)等本地网络通信。

MAC地址是用来识别数据链路层中相连的节点,一般来说长度为48位,及6个字节。

一般用16进制数字加上冒号的形式来表示(例如:08:00:27:03:fb:19)

MAC地址在网卡出厂时就确定了(每个物理网卡在制造时会被分配一个唯一的 MAC 地址),不能进行修改,MAC地址通常是唯一的。

后面我们详细谈论数据链路层的时候,会谈 mac 帧协议,此处我们做一个了解即可。
以以太网为例,在以太网中的任意时刻,都只允许一台机器向网络中发送数据。如果有多台机器同时发送,就会发生数据干扰。我们称之为:数据碰撞。


而所有发送数据的主机都需要进行碰撞检测与碰撞避免。
所谓碰撞避免,就是发现此时有主机发送数据了,你就进行休眠,等一会再发送数据。
在没有交换机的情况下,一个以太网就是一个碰撞域。
在我们的局域网通信的过程中,主机对收到的报文确认是否是发送给自己的,是由目标的MAC地址来判定的。
目标MAC地址一样,就说明这个报文是发给自己的。
以太网就是一个临界资源

主机abcd发送数据时执行的代码是临界区

我们采用碰撞避免碰撞检测来保证临界资源使用时数据的原子性

这个碰撞也是为什么人一多,我们的网就卡起来了,因为碰撞的次数增多了,休眠的时间就多了。


再来看同一个网段内的两台主机进行发送消息的过程:

而其中每层都有协议,所以当我进行进行上述传输流程的时候,要进行封装和解包。

当应用程序发送网络数据时,数据必须经过操作系统的网络协议栈处理,最终由网卡(硬件)发送出去。这个过程是 “从上到下” 的,即:

  1. 应用层(用户态) →

  2. 传输层(TCP/UDP) →

  3. 网络层(IP) →

  4. 数据链路层(MAC/ARP) →

  5. 物理层(网卡驱动) →

  6. 网卡硬件(NIC)

为什么是这样从上到下的贯穿?

这是因为网卡是硬件,管理工作由操作系统来进行,今天要访问的网络功能网络层传输层是属于内核的。

所以你发数据一定会使用系统调用,使用系统调用就一定会贯穿操作系统的,所以一定会走这条路 

让我们来解释一下,为什么同层之间,都认为自己在和对方同层协议在直接通信。

在我们用户发送一个“你好”的信息后,用户层会给这个信息添加一个用户层的报头。

报头(Header)本质上是一种数据结构。它在计算机网络中用于存储协议控制信息,通常以固定或可变格式的二进制数据块的形式存在,由协议规范明确定义其字段和排列方式。

以快递为例,报头类似于快递单。

这个应用层汇报自己的报头加有效载荷(这里是“你好”),传递给下一次传输层,由传输层再添加一个传输层报头。随后让传输层报头把自己的报头加有效载荷(这里的有效载荷指的是应用层报头+“你好”),传递给下一层网络层。

一段报文,就等于自己的报头+有效载荷。

不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据报 (datagram),在链路层叫做帧(frame)。
应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),这个层层加封的过程,我们就称为:封装
首部信息中包含了一些类似于首部有多长, 载荷(payload)有多长, 上层协议是什么等信息。
数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,根据首部中的 "上层协议字段" 将数据交给对应的上层协议处理。
这个把自己的报头剥离,随后把有效载荷传递给上层的过程,我们叫做解包。
在网络传输的过程中,数据不是直接发送给对方主机的,而是先要自定向下将数据交付给下层协议,最后由底层发送,然后由对方主机的底层来进行接受,随后再自底向上进行向上交付。
那么用户b怎么知道网卡此时已经有数据了,要一步一步往上传:硬件中断!!!
在通信的双方的同一层看来,报文都具有一样的报头,一样的地址。所以就像同层之间都在和对方协议在直接通信一样。
在Linux系统中,我们可以通过ifconfig命令来查看自己机器的MAC地址:

ether后面就是mac地址。

 在Windows上可以通过命令:ipconfig /all来看到自己的物理地址(即mac地址)。


1.2、认识IP地址

IP 协议有两个版本, IPv4 IPv6。本文及后面的文章,如果没有进行特殊说明,谈到IP,都是指的是IPv4.
IP地址是在IP协议中,用来标识网络中不同主机的地址。
对于IPv4来说,IP地址是一个4字节,32位的整数。
我们通常也用“点分十进制”的字符串来表示IP地址,这样做的好处是可读性高,例如:192.168.0.1;用点分隔的每一个数字表示一个字节,范围是0-255。
跨网段的主机的数据传输. 数据从一台计算机到另一台计算机传输过程中要经过一个或多个路由器。
我们如何理解IP地址呢?
我们从一台主机跨网段向另外一台主机发送信息,就跟你人生的发展阶段一样。
我们的出生的婴儿就是源IP地址,我们的死亡的时候的老人就是目标IP地址。
在我们从婴儿到死老人的过程中,不是一蹴而就的,而是划分了许多个阶段。我们从婴儿会长到小孩,又会从小孩长到青少年,最后是青年,壮年,中年,中老年,老年。
IP地址就像人生的起点与终点——从出生的婴儿(源IP)到离世的老人(目标IP),代表通信双方永恒不变的身份标识
MAC地址则是人生每个阶段的临时身份,随着成长不断变化。
就像从婴儿到老人需要经历童年、青年、中年等阶段,数据包跨越不同网络时,它的源MAC和目标MAC会在每一跳动态更新(如从家庭路由器到运营商网关,再到目标服务器),但IP地址始终如一
这正体现了:IP负责全局寻址(人生方向),MAC负责局部传输(阶段性的脚步)
也许这里你还不能对IP地址有一个深刻的理解。没关系,我们先继续往下面看。

这张图的通信双方并不处于同一个局域网中,甚至于二者的局域网类型都不一样。

他们之间如何进行通信呢?

这就要通过我们的路由器了,所谓路由,就是进行路径的选择。

左侧的FTP客户,通过我们说的封装过程,将IP层的IP报文封装成以太网帧,添加源MAC(客户端网卡)和目标MAC(默认网关,如路由器)。

这样就将信息传给了路由器,随后路由器的以太网接口收到帧,校验MAC地址(目标MAC是否匹配自身接口)。剥离以太网头,解析IP包,查询路由表决定下一跳去向哪里。

由于另外一侧是令牌网,路由器就重新封装IP包为令牌环帧(格式与以太网不同,如使用令牌控制访问)。随后更新源MAC(路由器令牌环接口MAC)和目标MAC(下一跳设备MAC)。

若目标服务器在令牌环网中,服务器网卡就接收令牌环帧,校验MAC是否正确后剥离帧头,将IP包上传至内核协议栈。IP层检查目标IP是否为本机,TCP层按端口号交付给FTP服务进程。

( 路由器通过多个接口连接不同子网,每个接口的IP充当该子网的网关。网关是路由器接口的IP地址,主机需配置网关才能访问外部子网。)


抽象点的图看看:

这个过程更详细的来说,就是封装到了网络层,此时进行IP的封装,确定好目标的IP地址与源IP地址,发现此时的IP地址前面的跟我们的局域网的IP地址不一样(一般来说同一个局域网中的IP地址,点分十进制的话,前面三个数都是一样的,所以我们就能判断是否目标地址与我在同一个局域网,如果不是,就将目标MAC设为默认网关(路由器)的地址),随后传递给链路层,进行mac地址的封装。
封装好之后,就要开始传递信息了。
在以太网中(以太网是一个局域网)有另外的其他许多主机,但是他们的mac地址都匹配不上,最后我们就匹配上了路由器的对应以太网接口的mac地址。
此时路由器的网卡收到了消息,开始网上解包,到了网络层解包出了源IP地址与目标IP地址,查询
路由表:若目标IP在直连网络中,就重新封装帧(更新源/目标MAC)并从对应接口转发;否则交给下一跳路由器。
找到目标局域网后,这个路由器此时就继续往下封装成mac帧,由负责该局域网的网卡发出信
息,在这个局域网下的主机都会收到消息。
随后通过目标mac地址是否匹配来判断是否接收。
最后目标主机就匹配成功了,随后逐渐网上解包,把消息给了用户。

IP 网络层存在的意义:提供网络虚拟层,让世界的所有网络都是 IP 网络,屏蔽最底层网络的差异
最初通信依赖MAC地址,但MAC只能在局域网生效,且无法分层管理。IP层的引入屏蔽了底层网络差异(如以太网、Wi-Fi、光纤),通过逻辑化的IP地址实现全球路由,让不同技术的网络能无缝互联。

二、socket编程预备 

IP在网络中,用来标识一个主机的唯一性。

但是这里我们要思考一个问题:数据传输到主机是目的吗?

不是的!!因为数据是给人使用的。比如:聊天是人在聊天,下载是人在下载,浏览网页是人在浏览。

但是人怎么看到聊天记录的呢?怎么执行下载?浏览任务的呢?

是通过启动的qq微信,网盘,浏览器。

而启动的这些都是进程。换句话说,进程是人在系统中的代表,只要把数据传给进程,就相当于人拿到了数据。

所以,数据传输到主机不是目的,而是手段。到达主机内部后,交给主机里的进程,才是目的。

但是系统中,同时会存在许多个进程,当数据到达主机之后,怎么转发给目标进程?

如何把数据交付给进程?这就依赖于传输层的两大协议:TCP与UDP,以及它们的“地址簿”——端口号(Port)。


2.1、端口号

端口号(port)是传输层协议的内容,通常是一个2字节十六位的整数。
端口号用来标识一个进程,告诉操作系统,当前的这个数据要交给哪一个进程来处理。
所以我们的IP地址+端口好能够表示网络的某一台主机的某一个进程。
而一个进程可以占用多个端口号,可是一个端口号只能被一个进程占用。
0 - 1023: 知名端口号, HTTP, FTP, SSH 等这些广为使用的应用层协议, 他们的端口号都是固定的.
而1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的。
我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这两者之间是怎样的关系?

我们可以用“10086客服热线”来进行类比

  • PID:相当于客服工号(如“工号1024”),用于内部管理,客户无需知道。

  • 端口号:相当于客服热线号码(如“10086”),客户通过拨打号码找到服务。

  • 进程 ID 属于系统概念,技术上也具有唯一性,确实可以用来标识唯一的一个进程,但是这样做,会让系统进程管理和网络强耦合,实际设计的时候,并没有选择这样做。


IP 地址用来标识互联网中唯一的一台主机,port 用来标识该主机上唯一的一个网络进程
IP+Port 就能表示互联网中唯一的一个进程
所以,通信的时候,本质是两个互联网进程代表人来进行通信,{srcIp,srcPort,dstIpdstPort}这样的 4 元组就能标识互联网中唯二的两个进程
所以,网络通信的本质,也是进程间通信
这两个进程具有十足的独立性,运行在不同的主机上,但是他们之间也存在共享的资源:网络!!两个进程间通过共享的“网络”这一公共资源交换数据。
另外,一个主机不断从外部获取数据到内核缓冲区,进程不断的从内核缓冲区读取数据。这不就是消费者生产者模型吗?
我们把 ip+port 叫做套接字 socket。

2.2、传输层的代表

如果我们了解了系统,也了解了网络协议栈,我们就会清楚,传输层是属于内核的,那么我们要通过网络协议栈进行通信,必定调用的是传输层提供的系统调用,来进行的网络通信。

传输层主要有两种核心协议:TCP(传输控制协议) 和 UDP(用户数据报协议)。它们为应用层提供不同的数据传输服务,是互联网通信的基础。 


我们现在先来简单的了解一下这两种协议。

TCP(Transmission Control Protocol)是一种面向连接的、可靠的传输层协议,它为应用程序提供有序、无差错的数据流传输服务

主要特点就是以下四个:

传输层协议
有连接
可靠传输
面向字节流
我们只需要知道就行了,后面会详细解释。

UDP(User Datagram Protocol)是一种无连接的、不可靠的传输层协议,专注于低延迟和简单性

传输层协议
无连接
不可靠传输

面向数据报


这里的面向字节流与面向数据报我们简单的说一下,面向字节流就是我们读写文件那样的,数据像“水流”一样无边界连续传输,发送方写入的字节数和接收方读取的字节数无需一一对应。就是我们写入的时候是十个字节的写入的,读取的时候不要求以十字节的方式读出来。

而面向数据报是什么?就是你一次发送信息,就得对应一次接收一次。数据以独立报文为单位传输,且报文长度固定,不会拆分或合并。

2.3、网络字节序 

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢?
发送主机通常将发送缓冲区中的数据按照内存地址从低到高的顺序发出。接收主机把从网络上接
到的字节依次保存到接收缓冲区中,也是按照内存地址从低到高的顺序保存。
因此,网络数据流的地址应该这样规定:先发出的数据是低地址,后发出的数据是高地址。
所以TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节。
不管这台主机是大端机还是小端机,都会按照这个协议规定的网络字节序来发送和接收数据。如果当前发送主机是小段,就需要先把数据转化成大端。
为使网络程序具有可移植性,使同样的 C 代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。

这些函数名很好记,h 表示 host,n 表示 network,l 表示 32 位长整数,s 表示 16 位短整数。
例如 htonl 表示将 32 位的长整数从主机字节序转换为网络字节序,例如将 IP 地址转换后准备发送。
如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回。

2.4、sockaddr 结构

socket API 是一层抽象的网络编程接口,适用于各种底层网络协议,IPv4IPv6,以及后面要讲的 UNIX Domain Socket. 然而, 各种网络协议的地址格式并不相同。

IPv4 IPv6 的地址格式定义在 netinet/in.h ,IPv4 地址用 sockaddr_in 结构体表示,包括 16 位地址类型, 16 位端口号和 32 IP 地址.
IPv4IPv6 地址类型分别定义为常数 AF_INETAF_INET6. 这样,只要取得某种 sockaddr 结构体的首地址,不需要知道具体是哪种类型的 sockaddr 结构体,就可以根据地址类型字段确定结构体中的内容
这样有什么好处呢?
大家想一下,我们在使用socket的常见接口时,只需要把对应的结构体指针类型强制转化为const struct sockaddr *,这样我们不就能统一接口了吗?
所以就降低了耦合性,提升代码的通用性和可扩展性,无需为每种协议重复实现。
socket 常见 API接口:
// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,socklen_t address_len);// 开始监听 socket (TCP, 服务器)
int listen(int socket, int backlog);// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,socklen_t* address_len);// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

这也就是为什么这些接口里的相关参数结构体指针类型都是const struct sockaddr *的原因。

转化后,根据我们之前定义的常数 AF_INETAF_INET6(因为这两个结构体前16位被定义了常数,我们就可以通过if等判断出来原来是什么结构体)

这其实也是C语言实现多态的一种用法。

总结:

我们的网络基础概念就先讲到这里。

后面我们会继续深化学习,更加深刻的理解网络。

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

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

相关文章

亚马逊广告进阶指南:如何优化流量实现新品快速起量

“新品上架如何快速获取精准流量?”“如何降低ACOS同时提升转化率?”“竞品流量拦截有哪些高效方法?”“关键词广告和ASIN广告如何协同投放?”“人工投放效果不稳定,AI工具真的能解决问题吗?”如果你也在思…

路径平滑优化算法--Clothoid 路径平滑

路径平滑优化算法–Clothoid 路径平滑 文章目录路径平滑优化算法--Clothoid 路径平滑0 为什么选 Clothoid?1 数学基础:严谨推导(Mathematical Foundation)可视化解释1.1 曲率线性假设1.2 切向角(Heading Angle&#…

PCB学习笔记(一)

文章目录一、PCB的制作过程了解1.1 覆铜板一、核心作用:制作印制电路板(PCB)二、不同类型覆铜板的针对性用途三、延伸应用1.2 覆铜板和信号线的关系一、覆铜板不是“全是铜”,原始结构是“绝缘基材铜箔”二、信号线就是铜&#xf…

【19】C# 窗体应用WinForm ——【列表框ListBox、复选列表框CheckedListBox】属性、方法、实例应用

文章目录9 复选列表框CheckedListBox10. 列表框ListBox10.1 实例:买菜10.2 实例:购菜 应用二WinForm 是 Windows Form 的简称,是基于 .NET Framework 平台的客户端(PC软件)开发技术,是 C# 语言中的一个重要…

新注册企业信息查询“数据大集网”:驱动企业增长的源头活水

商贸繁荣的齐鲁大地上,“赶大集”曾是千年传承的民间智慧。而今天,一场以新注册企业信息为核心的“数据大集”正悄然重塑商业生态——数据大集网,以“聚天下好数,促万企互联”为使命,将分散的企业信息转化为精准商机&a…

项目目标如何拆解,才能提高执行效率和效果

项目目标要有效拆解以提高执行效率和效果,需要遵循以下关键步骤:明确整体目标、划分阶段性目标和里程碑、具体化任务细分并设定优先级、分配明确的责任人和时间节点、持续跟踪与反馈调整。其中,划分阶段性目标和里程碑尤为重要,通…

spring-ai-alibaba动态 Prompt 最佳实践

Spring AI Alibaba 使用 Nacos 的配置中心能力来动态管理 AI 应用的 Prompt。以此来实现动态更新 Prompt 的功能。 环境准备 Nacos: 具备配置中心能力的 Nacos,本例中使用 Nacos 3.0.2。Nacos 2.X 亦可, spring-ai版本1.0.0 ,spring-ai-al…

基于词频统计、关键词提取、情感分析与AI大模型自动生成系统的设计与实现

文章目录有需要本项目的代码或文档以及全部资源,或者部署调试可以私信博主一、研究背景与项目意义二、项目目标与研究内容三、系统架构与功能模块1. AI对话生成模块2. 分词与关键词提取模块3. 情感分析模块4. 行为预测模块5. 系统管理模块6. 可视化展示模块四、技术…

JVM工具

首先,JDK 自带了很多监控工具,都位于 JDK 的 bin 目录下,其中最常用的是 jconsole 和 jvisualvm 这两款视图监控工具。 一、jps(Java Process Status) 用于查看有权访问的虚拟机的进程,并显示他们的进程号 -v:列出虚拟…

VisionPro系列讲解 - 03 Simulator 模拟器使用

一、VisionOS Simulator 简介 VisionOS Simulator 模拟器是专为 VisionOS 操作系统开发的调试和测试工具。它允许开发者在没有实际硬件设备的情况下,在计算机上模拟 VisionOS 环境,进行应用的开发、调试和优化。该模拟器帮助开发者快速验证应用的功能和界…

huggingface是什么?2025-07-30

huggingface被我看做是ai模型的试用空间 体验了一下image edit的功能,去除背景的功能不错 models 模型库 dataset 目前对我来说没用 spaces huggingface的spaces是什么? 演示空间吧。 令人震惊的背景移除能力H200是什么?

mysql索引下推和索引失效

索引下推:ICP过滤的条件可以不限于用于索引查找(index lookup)的字段。只要存储引擎在扫描当前索引时能够访问到该字段的值,就可以用它来过滤。索引可以分为聚簇索引和非聚簇索引没有索引下推:当使用聚簇索引的时候&am…

【电赛学习笔记】MaixCAM 的OCR图片文字识别

前言 本文是对MaixPy官方文档 MaixCAM MaixPy 实现 OCR 图片文字识别 - MaixPy 的项目实践整理与拓展,侵权即删。 功能介绍 OCR是MaixCAM中功能强大的数字文字识别模块,可以做到轻松的识别各种数字与文字。 OCR官方例程解析 工程源码 from maix im…

如何在生成式引擎优化(GEO)中取得成功

如果你希望您的内容出现在 AI Overviews、ChatGPT 和 Gemini 中?以下是设置 GEO 广告系列的方法。 任何好的 GEO 活动的第一步是创造一些东西实际上想要链接到或引用。 GEO 策略组件 想象一些你合理预期不会直接在 ChatGPT 或类似系统中找到的体验: 例如…

WPFC#超市管理系统(3)商品管理

超市管理系统6. 商品管理6.1 添加商品6.1 商品管理主界面6.3 修改商品6. 商品管理 将前文中的GoodsView全部改成和数据库一致的ProductView新增枚举类型商品类型ProductType.cs namespace 超市管理系统.Enums {public enum ProductType{水果类,休闲食品类,粮油类,饮料类,日用…

openwrt中br-lan,eth0,eth0.1,eth0.2

CPU是QCA9558 有两个以太网接口 这个好像没有外接交换机直接印出来的 openwrt中br-lan,eth0,eth0.1,eth0.2 https://blog.csdn.net/f2157120/article/details/119460852 这个哥用的是 链接: DomyWifi DW33D 路由器 CPU是QCA9558 有两个以太网接口 因为CPU没集成千兆交换&…

RAG实战指南 Day 29:RAG系统成本控制与规模化

【RAG实战指南 Day 29】RAG系统成本控制与规模化 开篇 欢迎来到"RAG实战指南"系列的第29天!今天我们将深入探讨RAG系统的成本控制与规模化部署策略。当RAG系统从原型阶段进入生产环境时,如何经济高效地扩展系统规模、控制运营成本成为关键挑…

React 中获取当前路由信息

在 React 中获取当前路由信息,根据使用的路由库不同(如 React Router v5/v6 或 Next.js),方法也有所区别。以下是常见场景的解决方案:1. 使用 React Router v6 获取当前路径(pathname)、查询参数…

Sklearn 机器学习 随机森林 网格搜索获取最优参数

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Sklearn 机器学习:随机森林 + 网格搜索获取最优参数实战指南 在构建机器学习模型时,…

力扣-101.对称二叉树

题目链接 101.对称二叉树 class Solution {public boolean check(TreeNode l, TreeNode r) {if (l null && r null)return true;if ((l null && r ! null) || (r null && l ! null))return false;if (l.val ! r.val)return false;return check(l…