TCP是一个面向连接的、可靠的、基于字节流的传输层协议。文次我们会通过介绍TCP的报头并通过分析各字段的用途来进一步解释其核心特性:

  • 可靠传输: 有确认应答、超时重传、确保有序。

  • 流量控制和拥塞控制: 动态调节发送速率,防止丢包与拥塞。

  • 面向连接: 通过“三次握手”建立连接,“四次挥手”断开连接。

TCP报头

源/目的端口号:

标识唯一的发送方和接收方进程 

32 位序号Seq:

体现基于字节流特性和可靠性:表示本报文段所携带数据的第一个字节在整个字节流中的编号。TCP将应用层交付的数据视为连续的字节流,并为每个字节编号。接收端TCP利用序列号将可能乱序到达的报文段重新排序,确保数据按正确的顺序交付给应用层。

32 位确认号Ack: 

(1)体现可靠性 (确认机制): 当ACK标志位为1时,该字段才有效。它表示接收方期望收到的下一个字节的序列号。

(2)也用于流量控制

4 位 TCP 报头长度:

表示该 TCP 头部有多少个 4 字节,最小值为5(对应20字节标准报头),最大值为15(对应60字节报头)

4位保留未用:必须置为0

6 位标志位:

○ URG: 紧急指针是否有效,一般没用

ACK: 确认号是否有效,绝大多数报文段都携带ACK

○ PSH: 提示接收端应用程序立刻从 TCP 缓冲区把数据读走

○ RST: 对方要求重新建立连接,通常在发生严重错误或拒绝连接请求时使用; 我们把携带 RST 标识的称为复位报文段

SYN: 请求建立连接,在三次握手中用于同步序列号; 我们把携带 SYN 标识的称为同步报文段

FIN: 表示发送方数据已发送完毕,请求终止连接,用于四次挥手关闭连接; 我们称携带 FIN 标识的为结束报文段

16 位窗口大小:

用于流量控制: 表示接收方当前愿意接收的数据量(以字节为单位),即接收窗口的大小。发送方根据这个值动态调整自己发送数据的速率,确保不会淹没接收方,防止接收缓冲区溢出。这是TCP实现端到端流量控制的关键机制。

16 位校验和:

发送端填充, CRC 校验.

接收端校验不通过, 则认为数据有问题

此处的检验和不光包含 TCP 首部, 也包含 TCP 数据部分.

16 位紧急指针:

标识哪部分数据是紧急数据;

40 字节头部选项: 暂时忽略;

可靠传输

确认应答

在进行tcp通信时,发送方每发送一个报文,接收方就必须给接收方发一个ACK应答报文,表示“Ack序号之前的数据我收到了”(ACK是一个标识位;Ack就是确认序号,可以认为是缓冲区上的一个指针,而序号Seq同样是一个指针),这保证了发送方能掌握接收方的接收状态,避免盲目发送(如接收方网络中断,发送方还在发送),保证了通信时的可靠性,同时还与下文的滑动窗口、超时重传、快速重传有密切关系(通过Ack来判断哪一部分数据丢包)

快速重传

如果连续三次接收方都给发送方应答同一个ACK,那么接收方就会认为该ACK对应的报文丢失,会进行重发

超时重传

如果发送方发送报文后一段时间内接收方没有应答,接收方就会认为发送的报文丢失,会进行重复发送。那么如何确定该过多长时间没收到应答才认为是丢包呢,这个时间如果太短会导致网络环境较差时频繁发送重复数据,太长又降低了整体的重传效率,因此一般会动态计算超时时间:

Linux 中(BSD Unix 和 Windows 也是如此), 超时以 500ms 为一个单位进行控制, 每次判定超时重发的超时时间都是 500ms 的整数倍.

如果重发一次之后, 仍然得不到应答, 等待 2*500ms 后再进行重传.

如果仍然得不到应答, 等待 4*500ms 进行重传. 依次类推, 以指数形式递增.

累计到一定的重传次数, TCP 认为网络或者对端主机出现异常, 强制关闭连接

滑动窗口

一方面,由于发送方每发送一个报文都需要接收方进行应答,这使得高延迟网络下双方通信效率较低;另一方面,如果发送方发送数据过快,接收方来不及将新报文放入接收缓冲区,只能将其丢弃。为此就需要一种机制动态地调节传输速率——滑动窗口

滑动窗口首先允许发送方连续发送多个报文(无需逐个等待ACK),显著提高了传输效率。

这个过程中即使应答报文丢失了也没关系,因为还会有后续的应答报文。

随后又将发送/接收缓冲区进行划分:

发送方:

已确认发送|发送窗口|未发送

  • SND.WND: 表示发送窗口的大小, 上图虚线框的格子数是 10 个,即发送窗口大小是 10。

  • SND.NXT:下一个发送的位置,它指向未发送但可以发送的第一个字节的序列号。

  • SND.UNA: 一个绝对指针,它指向的是已发送但未确认的第一个字节的序列号。

发送方在收到应答报文时,需要将确认号Ack与自己的窗口范围进行对比:

  • 若 Ack 不在 [SND.UNA,SND.NXT] 范围内→ 无效ACK,直接忽略。

  • 若 Ack > SND.UNA → 新数据被确认,更新 SND.UNA = Ack。

  • 若 Ack == SND.UNA → 重复ACK(可能数据丢失或乱序),

  • 累计重复次数:若重复ACK ≥ 3次 → 触发快速重传(重传 SND.UNA 对应的数据包)。

  • 若 Ack < SND.UNA→ 过期的ACK(确认已确认的数据),直接忽略。

那么接收方的Ack又是如何更新的呢:

接收方:

已确认收到|接收窗口|未收到

  • REV.WND: 表示接收窗口的大小, 上图虚线框的格子就是 9 个。

  • REV.NXT: 下一个接收的位置,它指向未收到但可以接收的第一个字节的序列号。

当Seq<RCV.NXT时(重复数据),直接丢弃报文,窗口不变,Ack不变

当Seq==RCV.NXT时(按序到达),Ack=RCV.NXT=RCV.NXT+len,窗口缩小

当Seq>RCV.NXT时(乱序数据),RCV.NXt和Ack不变,将数据缓存,窗口缩小,REV.WND-=len

而当应用层将数据从缓冲区取出时,窗口则会变大

流量控制

有了滑动窗口机制,我们就可以通过控制窗口大小来限制传输速率

接收方通过 TCP 报头的窗口大小字段,动态告知发送方其剩余接收缓冲区容量;

窗口大小字段越大, 说明网络的吞吐量越高;

接收端一旦发现自己的缓冲区快满了, 就会将窗口大小设置成一个更小的值;

发送端接收到这个窗口之后, 就会减慢自己的发送速度;

如果接收端缓冲区满了, 就会将窗口置为 0;

这时发送方不再发送数据, 但是需要定期发送一个窗口探测数据段, 强制接收方应答并把接窗口大小告诉发送端

拥塞控制

由于可能同时有大量的计算机在网络上进行通信,大家同时发送大量的数据, 很有可能导致甚至加重网络拥堵;为此,TCP引入慢启动机制, 先发少量的数据,摸清当前的网络拥堵状态, 再决定按照多大的速度传输数据;

此处引入一个概念称为拥塞窗口

发送开始的时候, 定义拥塞窗口大小为 1;

每次收到一个 ACK 应答, 拥塞窗口大小乘2;

每次发送数据包的时候, 将拥塞窗口和接收端主机反馈的窗口大小做比较, 取较小的值作为实际发送的窗口

像上面这样的拥塞窗口增长速度, 是指数级别的

"慢启动" 只是指初使时慢, 但是增长速度非常快.

为了不增长的那么快, 因此不能使拥塞窗口单纯的加倍.

此处引入一个叫做慢启动的阈值

当拥塞窗口超过这个阈值的时候, 不再按照指数方式增长, 而是按照线性方式增长

当 TCP 开始启动的时候, 慢启动阈值等于窗口最大值;

在每次超时重发的时候, 慢启动阈值会变成原来的一半, 同时拥塞窗口置回1;

少量的丢包, 我们仅仅是触发超时重传;

大量的丢包, 我们就认为网络拥塞;

当 TCP 通信开始后, 网络吞吐量会逐渐上升;

随着网络发生拥堵, 吞吐量会立刻下降;

拥塞控制, 归根结底是 TCP 协议想尽可能快的把数据传输给对方, 但是又要避免给网络造成太大压力的折中方案.

应答的优化策略

延迟应答

由于接收方收到消息时,窗口会变小,此时如果立刻应答,会使得发送方发过来的报文变小(进行流量控制),可是很多时候,接收方的处理速度很快,可能窗口虽然在接收报文时变小了,但很快就会恢复。因此,实际上发送方可能会低估接收方的接收能力,进而降低传输效率。

所以,为了提高传输效率,采用延迟应答机制:减少应答次数和接收到消息后过一定时间才应答

那么这个延迟应答时间应该是多少呢:显然不能超过500ms,因为这会与超时重传产生冲突,导致接收方明明收到消息了却被发送方认为是丢包了。一般来说,这个时间是200ms。

捎带应答

很多时候tcp通信不是一方发送一方接收,而是双方都在发送都在接受,这时候双方不仅要发自己的消息,还要频繁应答对方(而发送的信息仅仅是一个ACK和确认号和窗口大小,这显然有些浪费),因此,为了提高通信效率,当进行双向通信时,发送方(同时也是接收方)会把这次要发的报文和上次接收数据的应答报文合二为一发送给对方,这也是为什么大多数报文都会携带ACK应答

面向连接

三次握手

tcp通信在连接时需要先进行“三次握手”,其目的为:

1.确保双方的接收能力和发送能力正常

2.同步双方序列号seq

第一次握手前,双方均处于CLOSED状态,表示断开;服务端调用listen()后进入LISTEN状态,表示等待连接

第一次握手:

客户端调用connect()向指定服务端发起连接请求并向服务端发送SYN报文请求连接并发送Seq序(假设值为x,因为并没有发送任何数据,所以这个是随机生成的)

客户端进入SYNC-SENT状态

此时服务端已知晓自己的接收功能正常,对方的发送功能正常,服务端进入SYNC-RCVD状态

第二次握手:

服务端向客户端发送SYN报文表示同意连接,并捎带应答ACK报文;发送Seq序号(假设值为y,由于没有发送任何数据,也是随机生成的)和Ack确认序号(值为x+1,你可能疑惑不是没有发送数据吗,为什么确认序号还要加1,这是因为TCP 协议规定SYN报文虽然不携带数据, 但是也要消耗1个序列号)

此时,客户端已知晓自己的发送和接收功能正常(因为收到了服务端应答,说明自己的消息成功发出,也说明自己能收到服务端的消息),也知晓服务端的发送和接收功能正常(因为服务端收到了自己的连接请求并成功应答);

但服务端尚不知道自己的发送功能和对方的接收功能是否正常(因为对方还没有应答)因此需要第三次握手:

第三次握手:

客户端发送ACK报文应答,并发送Seq序号(值为x+1)和Ack确认序号(值为y+1)

至此服务端收到消息,确认了自己的发送功能和对方的接收功能,双方都进入ESTABLISHED状态,表示已连接可以正常通信

四次挥手

tcp在断开连接时,要进行四次挥手,其目的为:确保双方数据完整传输并安全释放资源

其中FIN信号表示不再发送数据,但仍可以接收数据

由于更多情况下是客户端主动断开连接(如关闭浏览器),所以这里我们认为客户端是主动断开连接的一方,服务端是被动断开连接的一方,当然服务端也有可能是主动都断开的连接的一方,下文会提到

第一次挥手:

客户端调用close()函数,向服务端发送FIN报文表示准备断开连接,并发送Seq序号(由于没有数据,因此是随机生成的,假设值为u)

客户端进入FIN_WAIT-1状态,关闭应用层动作(不再发送应用层的数据)

第二次挥手:

服务端发送ACK应答报文,并发送Ack确认序号(值为u+1,你可能疑惑不是没有发送数据吗,为什么确认序号还要加1,这是因为TCP 协议规定FIN报文虽然不携带数据, 但是也要消耗1个序列号)和Seq序号(由于没有数据,因此是随机生成的,假设值为v)

服务端进入CLOSE_WAIT状态,关闭内核动作(无法读取缓冲区中的数据)

客户端进入FIN_WAIT_2状态(这是一个半关闭状态,不能发送,但可以接收)

第三次挥手:

服务端调用close()函数,向服务端发送FIN报文,并发送Ack确认序号(值为u+1)和Seq序号(假设值为w,若第二次挥手后到第三次挥手前,服务端向客户端发送了数据,则w>v;否则w=v)

服务端进入LAST_ACK状态,关闭应用层动作(不再发送应用层的数据)

第四次挥手:

客户端发送ACK应答报文并发送确认序号Ack(值为w+1)和序号Seq(值为u+1)

客户端进入TIME_WAIT状态,关闭内核动作(无法读取缓冲区中的数据),并等待一段时间确保对方收到应答报文

服务端收到报文后进入CLOSE状态,连接断开

TIME_WAIT

为什么客户端在第四次挥手后,还要等待一段时间呢:这是因为,如果该报文丢失,服务端会超时重发第三次挥手的报文,客户端收到后又会发送第四次挥手的报文;这样确保了服务端能收到第四次报文

这里还存在一种情况:大量TIME_WAIT状态堆积

服务器需要处理非常大量的客户端的连接(每个连接的生存时间可能很短, 但是每秒都有很大数量的客户端来请求)

这个时候如果由服务器端主动关闭连接(比如某些客户端不活跃, 就需要被服务端主动清理掉), 就会产生大量 TIME_WAIT 连接

由于我们的请求量很大, 就可能导致 TIME_WAIT 的连接数很多, 每个连接都会占用一个通信五元组(源 ip, 源端口, 目的 ip, 目的端口, 协议). 其中服务器的 ip 和端口和协议是固定的

如果新来的客户端连接的 ip 和端口号和 TIME_WAIT 占用的重复了, 就会出现问题,造成服务端bind失败,一个解决方法使用 setsockopt()设置 socket 描述符的 选项 SO_REUSEADDR 为 1, 表示允许创建端口号相同但 IP 地址不同的多个 socket 描述符

CLOSE_WAIT

CLOSE_WAIT表示: 本地(已经收到了对端发来的 FIN 包(关闭请求),并且已经回复了 ACK(确认收到)。本地已经知道对端没有数据要发送了。

它在等待: 本地应用程序(通常是服务器端的服务进程)执行 close() 系统调用,关闭自己的套接字。只有应用程序调用了 close(),操作系统内核才会发送 FIN 包给对端。

这里可能存在一个问题:大量CLOSE_WAIT状态堆积

如果服务端无法正常调用close函数关闭连接,可能会导致大量CLOSE_WAIT状态堆积,占用文件描述符和内存,这时就需要调试寻找没有正常关闭连接的原因。

为什么是四次挥手

你可能会疑惑,为什么断开连接时,第二次挥手(服务端应答)和第三次挥手(服务端发送FIN报文)为什么不能合二为一(像三次握手中的第二次握手一样,发送SYN报文的同时捎带应答ACK报文)。这是因为客户端发送FIN报文是表示自己不想再发送数据了,但此时服务端可能还有数据没有发完,需要一些时间。因此,第二次挥手和第三次挥手有一定时间差,不能合二为一;同时,如果在这期间服务端又向客户端发送了数据的话,两次的确认序号也可能不同。

面向字节流

TCP的面向字节流是通过发送缓冲区和接收缓冲区来实现的,这使得其相较于UDP具有以下优势:

  1. 有序性:尽管字节流在传输过程中会被分割成多个报文段,并且这些报文段可能乱序到达、丢失、重传,TCP 协议保证了接收缓冲区中的字节流顺序与发送缓冲区中的字节流顺序完全一致。接收方应用读取到的字节顺序就是发送方写入的字节顺序。

  2. 可靠性:TCP 保证,只要连接没有异常中断,发送方写入发送缓冲区的每一个字节最终都会按顺序出现在接收方的接收缓冲区中,不会丢失、不会重复、不会出错(通过校验和、确认、重传、序列号等机制实现)。

  3. 缓冲区机制平滑了应用层读写速度与网络传输速度之间的差异(可以进行流量控制)。

但这也导致其没有消息边界(UDP是面向报文发送,有天然的消息边界),进而催生了粘包和拆包两个问题:

粘包问题:发送方多次写入的较小数据块,可能被 TCP 合并成一个较大的报文段发送

拆包问题:发送方写入的一个较大数据块,可能被 TCP 拆分成多个较小的报文段发送

这里举一个例子:

发送方分别发送"Hello"和"World"

接收方读取缓冲区中的内容, 返回 “HelloWorld”。

又或者是发送方分别发送 “HelloWorld

接收方读取缓冲区中的内容, 返回”"Hello"和"World"。

为了解决这一问题,需要明确两个包之间的边界:

对于定长的包, 保证每次都按固定大小读取即可;

对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置;

对于变长的包, 还可以在包和包之间使用明确的分隔符(应用层协议来定, 只要保证分隔符不和正文冲突即可);

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

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

相关文章

uniapp使用map打包app后自定义气泡不显示解决方法customCallout

前言&#xff1a;使用uniapp开发后在小程序可以正常显示&#xff0c;但是运行打包成App后就不显示了&#xff0c;其实这一块对于uniapp框架开发来说&#xff0c;是有系统性的bug&#xff0c;如果你再开发时使用的是vue文件进行&#xff0c;就会出现这个问题。解决方法&#xff…

【typenum】 22 类型级别二进制对数运算(Logarithm2)

一、源码 这段代码实现了一个类型级别的二进制对数运算系统 定义&#xff08;type_operators.rs&#xff09; /// A **type operator** for taking the integer binary logarithm of Self. /// /// The integer binary logarighm of n is the largest integer m such /// that …

golang 非error错误分类

1.应用级别&#xff0c;可recover这些 panic 一般是 逻辑或使用不当导致的运行时错误&#xff0c;Go 程序可以用 recover 捕获并继续运行&#xff1a;类型示例描述类型不一致atomic.Value 存不同类型 v.Store(100); v.Store("abc")panic: store of inconsistently ty…

【Ansible】变量与敏感数据管理:Vault加密与Facts采集详解

1. 变量Ansible利用变量存储可重复使用的值&#xff0c;可以简化项目的创建和维护&#xff0c;减少错误数量。1.1 变量名称由字符串组成&#xff0c;必须以字母开头&#xff0c;并且只能含有字母、数字和下划线&#xff0c;和其它编程语言很类似。1.2 常见变量要创建的用户要安…

ROS2下YOLO+Moveit+PCL机械臂自主避障抓取方案

整体运行架构 1.运行相机取像节点 . ./install/setup.bash ros2 launch orbbec_camera gemini_330_series.launch.py depth_registration:true 2.运行根据图像x,y获取z的service 基本操作记录&#xff1a; 创建python包,在src目录下 ros2 pkg create test_python_topic --bu…

快速入门Vue3——初体验

目录 前言 一、搭建环境 1.1、安装Node.js 1.2、安装Vite 二、项目创建 三、运行项目 四、集成Pinia 4.1、Pinia介绍 4.2、Pinia安装 五、集成VueUse 5.1、vueuse简介 5.2、vueuse安装 六、集成Vant 6.1、Vant简介 6.2、Vant安装 前言 本专栏主要介绍如何使用…

深入理解Kubernetes核心:标签与标签选择器实战解析

在管理 Kubernetes 集群时&#xff0c;随着 Pods、Services 等资源数量的增长&#xff0c;如何有效地组织和筛选它们&#xff0c;成为了一个核心问题。Kubernetes 为此提供了一个简单却极其强大的机制&#xff1a;标签&#xff08;Labels&#xff09;和标签选择器&#xff08;L…

哈希和字符串哈希

哈希&#xff08;Hash&#xff09; Hash 表 Hash 表又称为散列表&#xff0c;一般由 Hash 函数&#xff08;散列函数&#xff09;与链表结构共同实现。与离散化思想类似&#xff0c;当我们要对若干复杂信息进行统计时&#xff0c;可以用 Hash 函数把这些复杂信息映射到一个容…

【Docker基础】Docker-Compose核心配置文件深度解析:从YAML语法到高级配置

目录 前言 1 YAML基础语法解析 1.1 YAML格式简介 1.2 Docker-compose中的YAML语法规则 1.3 YAML数据类型在Compose中的应用 2 docker-compose.yml文件结构剖析 2.1 基本文件结构 2.2 版本声明详解 3 services配置深度解析 3.1 服务定义基础 3.2 镜像与构建配置 3.3…

如何判断是否应该为了一个小功能而引入一个大体积的库

在软件开发中&#xff0c;判断是否应该为了一个看似微小的功能&#xff0c;而引入一个大体积的第三方库&#xff0c;是一项极其重要的、需要进行审慎的“投入产出比”分析的技术决策。这个决策&#xff0c;绝不能&#xff0c;仅仅基于“实现功能的便利性”&#xff0c;而必须&a…

相机定屏问题分析五:【跳帧异常】照片模式1x以上的焦段拍照之后定屏

【关注我&#xff0c;后续持续新增专题博文&#xff0c;谢谢&#xff01;&#xff01;&#xff01;】 上一篇我们讲了&#xff1a; 这一篇我们开始讲&#xff1a; 相机定屏问题分析五&#xff1a;【跳帧异常】照片模式1x以上的焦段拍照之后定屏9573412 目录 一、问题背景 二…

Non-stationary Diffusion For Probabilistic Time Series Forecasting论文阅读笔记

Non-stationary Diffusion For Probabilistic Time Series Forecasting 摘要 时间序列数据受到潜在的物理动力学和外部影响&#xff0c;其不确定性通常随时间而变化。现有的去噪扩散概率模型&#xff08;DDPMs&#xff09;受到加性噪声模型&#xff08;ANM&#xff09;的恒定方…

解决Docker 无法连接到官方镜像仓库

这个错误&#xff1a; Error response from daemon: Get "https://registry-1.docker.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)表示 Docker 无法连接到官方镜像仓库 registry-1.docker…

解决RAGFlow启动时Elasticsearch容器权限错误的技术指南

文章目录 问题现象 根本原因分析 解决方案步骤 1. 定位宿主机数据目录 2. 修复目录权限 3. 验证权限状态 4. 重启服务 5. 检查启动状态 永久解决方案:优化Docker Compose配置 高级故障排除 技术原理 问题现象 在启动RAGFlow项目时,执行 docker logs ragflow-es-01 发现Elast…

【C++高阶六】哈希与哈希表

【C高阶六】哈希与哈希表1.什么是哈希&#xff1f;2.unordered系列容器3.哈希表3.1将key与存储位置建立映射关系3.1.1直接定址法3.1.2除留余数法&#xff08;产生哈希冲突&#xff09;3.2解决哈希冲突的方法3.2.1闭散列&#xff08;开放定址法&#xff09;3.3.2开散列&#xff…

Vue 3 +Ant Design Vue 父容器样式不影响子级,隔离

公共样式文件 common.scss.zz-ant-status-bar {div {font-size: 12px;padding: 0 8px;} }页面代码<div class"zz-ant-status-bar"><a-row><a-col :span"6" ><a-progress :percent"progress.percent" size"small"…

k8s 简介及部署方法以及各方面应用

Kubernetes 简介及部署方法Kubernetes&#xff08;简称 K8s&#xff09;是一个开源的容器编排平台&#xff0c;用于自动化容器化应用的部署、扩展、管理和运维。它由 Google 基于内部的 Borg 系统经验开发&#xff0c;2014 年开源后由云原生计算基金会&#xff08;CNCF&#xf…

Class A 包含字段 x Class B 也包含字段 x,如果判断List<A> lista 和 List<B> listb 有相同的 x?

要判断两个不同类型的对象列表 List<A> 和 List<B> 是否包含相同的 x字段值&#xff08;即两个列表中至少有一个 x是相同的&#xff09;&#xff0c;你可以使用 Java 8 的 Stream API 来实现。import java.util.List; import java.util.Set; import java.util.stre…

SpringBoot整合Camunda工作流

什么是工作流&#xff1f;概述 工作流是将一组任务组织起来以完成某个经营过程&#xff1a;定义了任务的触发顺序和触发条件&#xff0c;每个任务可以由一个或多个软件系统完成&#xff0c;也可以由一个或一组人完成&#xff0c;还可以由一个或多个人与软件系统协作完成&#x…

2025年09月计算机二级Java选择题每日一练——第四期

计算机二级中选择题是非常重要的&#xff0c;所以开始写一个每日一题的专栏。 答案及解析将在末尾公布&#xff01; 今日主题&#xff1a;面向对象特性 1、有两个类 A 和 B 的定义如下&#xff1a; class A{final int x10;public void show(){System.out.print(x " &quo…