当前位置: 首页 > news >正文

Linux网络编程IO管理

网络 IO 涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段:

  1. 等待内核协议栈的数据准备就绪;
  2. 将内核中的数据拷贝到用户态的进程或线程中。
    由于在以上两个阶段产出的不同情况,就出现了多种网络 IO 管理方法,即网络 IO 模型。

五种网络 IO 模型

阻塞 IO(blocking IO)

在 Linux 中,默认情况下所有 socket 都是 blocking,一个典型的读操作流程如下:

image.png

当用户进程调用了 read 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候的数据是没有就绪的(比如很多时候还没有收到一个完整的数据包),那么整个进程就会被阻塞;当内核将数据准备好了,才会将数据从内核空间拷贝到用户态内存,然后 kernel 返回结果,用户态进程才会解除阻塞继续运行。
所以,block io 在 io 执行的两个阶段都被 block 了(数据准备和数据拷贝)。所有程序员解除网络编程都是从 listen recv send,开始的,这些都是阻塞型接口。可以很方便地构建一个服务器-客户机模型,下面是一个简单的模型结构:

image.png

//接受缓冲区大小
#define BUFFER_LENGTH       1024int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s\n", strerror(errno));return -1;}listen(sockfd, 10);struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept\n");while(1){char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);}
}

大部分的 socket 接口都是阻塞型的。所谓的阻塞型接口是指系统调用不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或超时出错时才返回。
这些阻塞的接口给网络编程带来了很大的问题,如在调用 send() 的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或相应任何网络请求。
一个简单的改进方案就是在服务器端使用多线程(或多进程)。让每个连接都有独立的线程/进程,这样任何一个链接的阻塞都不会影响他的连接。具体使用多进程还是多线程没有一个特定的模式。传统意义上,进程的开销要远大于线程,所以要同时为较多的客户机提供服务,则不推荐多进程;如果单个服务执行体需要消耗较多的 CPU 资源,比如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。通常,使用 pthread_create() 创建新线程,fork() 创建新进程。
我们假设对上述服务器/客户机模型提出更高的要求,即让服务器同时为多个客户机提供服务,就有了以下模型。

image.png

#define BUFFER_LENGTH       1024//线程函数
void *client_thread(void *arg)
{int clientfd = *(int*)arg;while(1){char buffer[BUFFER_LENGTH] = {0};int ret = recv(clientfd, buffer, BUFFER_LENGTH, 0);if(ret == 0){close(clientfd);break;}printf("ret: %d, buffer: %s\n", ret, buffer);send(clientfd, buffer, ret, 0);}
}int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s\n", strerror(errno));return -1;}listen(sockfd, 10);struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);while(1){int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);pthread_t threadid;//将clientfd昨晚参数传入线程pthread_create(&threadid, NULL, client_thread, &clientfd);}
}

在上面的模型中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供和前面相同的服务。
很多初学者可能不明白为何一个 socket 可以 accept 多次。实际上 socket 的设计者
可能特意为多客户机的情况留下了伏笔,让 accept () 能够返回一个新的 socket。下面是
Accept 接口的原型:

int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

输入参数 s 是从 socket(),bind() 和 listen() 中沿用下来的 socket() 句柄值。执行完 bind() 和 listen() 后,操作系统会在指定的端口处监听所有连接请求,如果有请求,则将该连接请求加入请求队列。调用 accept() 接口正是从 socket s 的请求队列抽取第一个连接信息,创建一个与 s 同类的新 socket 返回句柄。新的 socket 句柄即后续 read() 和 recv() 的输入参数。如果请求队列当前没有请求,则 accept() 将进入阻塞状态直到有请求进入队列。
上述多线程服务器模型几乎完美解决了多个客户机提供问答服务的要求,但其实并不尽然。如果要同时相应成百上千的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界相应效率,而线程与进程本省也容易进入假死状态。
对于可能面临的同时出现的上千次次甚至上万次的客户端请求,“线程池”和“连接池”等池化组件或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模服务请求,但是对面大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来解决这个问题。

非阻塞 IO(non-blocking IO)

Linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时,流程如下:

image.png

int main()
{int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;memset(&servaddr, 0, sizeof(struct sockaddr_in));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(9999);if(-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))){printf("bind failed: %s", strerror(errno));return -1;}listen(sockfd, 10);// sleep(10);printf("sleep\n");int flags = fcntl(sockfd, F_GETFL, 0);flags |= O_NONBLOCK;fcntl(sockfd, F_SETFL, flags);struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);while(1){int clientfd = accept(sockfd, (struct sockaddr*)&clientaddr, &len);printf("accept\n");}
}

从图中看出,当用户发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户的角度来讲,它发起一个 read 操作后,并不需要等待,而是马上得到一个结果。用户进程判断结果是一个 error 时,他就知道数据还没准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且再次收到用户进程的 system call,那么它马上就将数据拷贝到用户内存,然后返回,所以,在非阻塞 IO 中,用户进程其实是不需要主动询问 kernel 数据准备好了没有。
在非阻塞状态下,recv() 接口在被调用后立刻返回,返回值代表了不同的含义,如在上面的例子中:

  • recv()返回值大于 0 ,表示接受数据完毕,返回值即是接受到的字节数;
  • recv()返回 0 ,表示连接已经正常断开;
  • recv()返回 1 ,且 errno 等于 EAGAIN ,表示 recv 操作还没执行完成;
  • recv()返回 1 ,且 errno 不等于 EAGAIN ,表示 recv 操作遇到系统错误 errno 。

非阻塞的接口相比阻塞接口的显著差异在于,在被调用之后立刻返回。使用如下的函数可以将某句柄 fd 设为非阻塞状态。

fcntl(fd, F_SETFL, O_NONBLOCK);

多路复用 IO(IO multiplexing)

这种模型的好处在于,单个 process 可以同时处理多个网络连接的 IO。他的基本原理就是 select/epoll 这个 function 会不断轮询所负责的所有 socket,当某个 socket 有数据到达,就通知用户进程。流程如下:

image.png

当用户进程调用了 select,那么整个进程就会被 block,而同时,kernel 会“监视”所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。
在多路复用模型中,对于每一个 socket,一般都会设置成 non-blocking,但是,如上图所示,其实整个用户的 process 都是 block 的。只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。因此 select() 与非阻塞 IO 类似。
大部分 Unix/Linux 都支持 select 函数,该函数用于探测多个文件句柄的状态变化。下面给出 select 接口的原型:

FD_ZERO(int fd, fd_set* fds)
FD_SET(int fd, fd_set* fds)
FD_ISSET(int fd, fd_set* fds)
FD_CLR(int fd, fd_set* fds)
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

这里,fd_set 类型可以简单的理解为按 bit 位标记的句柄队列,例如要在某 fd_set 中标记一个值为 16 的句柄,则该 fd_set 的第 16 个 bit 位被标记为 1。具体的位置、验证可使用 FD_SET、FD_ISSET 等宏实现。在 select() 函数中,readfd、writefds 和 exceptfds 同时作为输入参数和输出参数。如果输入的 readfds 标记了 16 号句柄,则 select() 将检测 16 号句柄是否可读。在 select() 返回后,可以通过检查 readfds 是否标记 16 号句柄来判断“可读”事件是否发生。另外,用户可以设置 timeout 时间。
这里需要指出的是,客户端的一个 connect() 操作,将在服务器端激发一个“可读事件”,所以 select() 可能探测来自客户端的 connect() 行为。
上述模型中,最关键的地方是如何动态维护 select() 的三个参数,readfds、writefds 和 exceptfds。作为输入参数,readfds 应该标记所有的需要探测的“可读事件”的句柄,其中永远包括那个探测 connect() 的那个“母”句柄(使用 FD_SET() 标记)。
作为输出参数,readfds、writefds 和 exceptfds 中中保存了所有 select 捕捉到的所有事件的句柄值。程序员需要检查的所有的标记位(使用 FD_ISSET() 检查),以确定到底那些句柄发生了事件。
在上面的一问一答模式中,如果 select() 发现某句柄捕捉到可“可读事件”,服务器程序应及时做 recv() 操作,并且根据接收到的数据准备好发送数据,并将对应的句柄值加入 writefds,准备下一次“可写事件”的 select() 探测。探测。同样,如果 select() 发现某句柄捕捉到“可写事件”,则程序应及时做 send() 操作,并准备好下一次的可读事件探测准备。
这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型叫做“事件驱动模型”。
但这个模型依旧有着很多问题。首先 Select () 接口并不是实现事件驱动的最好选择。因为当需要探测的句柄值较大时, select () 接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如 linux 提供了 epoll BSD 提供了 kqueue Solaris 提供了 /dev/poll 。如果需要实现更高效的服务器程序,类似 epoll 这样的接口更被推荐。遗憾的是不同的操作系统特供的 epoll 接口有很大差异,所以使用类似于 epoll 的接口实现具有较好跨平台能力的服务器会比较困难。

异步 IO(Asynchronous IO)

image.png

当用户进程发起 read 操作后,就立刻做其他的事情。另一方面,从 kernel 的角度,当他收到一个 asynchronous read 后,它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等数据准备好之后,将数据拷贝到用户空间内存,当一切都完成后,kernel 会给用户进程发送一个 signal 告诉他 read 操作完成了。

到目前为止,已经介绍了四个 IO 模型。现在来回答最初的几个问题:blocking 和 non-blocking 的区别在哪里?synchronous IO 和 asynchronous IO 的区别在哪里?

先回答简单的:blocking 和 non-blocking。调用 blocking IO 会一直 block 进程直到操作完成,而 non-blocking IO 在 kernel 还在准备数据的情况下会直接返回。
synchronous 和 asynchronous 的区别在于 synchronous 在进行 IO opration 的时候回将 process block 但是 asynchronous 不会。所以前面介绍的 blocking IO,non-blocking IO 和 IOmultiplexing 都是 synchronous。但是这时候就会有人问,non-blocking 不是不会 block process 吗。这里有一个需要注意的地方,non-blocking 只是在执行 read 这个系统调用的情况下 kernel 会直接返回,但是在 kernel 准备好数据拷贝到 application 的时候,依然会对 process block。所以她在 IO 操作上依然有阻塞的部分。而 asynchronous IO 不一样,当进程发起 IO 操作信号后直接返回不理睬,直到 kernel 发出 IO 操作完成的信号,中间没有任何阻塞。

信号驱动 IO(signal driven IO, SIGIO)

在我们安装信号函数之后,看进程继续运行并不阻塞。数据准备好之后,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 IO 操作函数处理数据。我们可以在信号处理函数中调用 read 读取数据,并通知主循环数据准备好;也可以立刻通知主循环让它读取数据。这种模型的优势在于等待数据包到达器件,可以继续执行,不被阻塞。免去了 select 的阻塞与轮询,当有 socket 活跃时,由 handler 处理。

推荐课程:https://xxetb.xetslk.com/s/3oyV5o

相关文章:

Linux网络编程IO管理

网络 IO 涉及到两个系统对象,一个是用户空间调用 IO 的进程或者线程,一个是内核空间的内核系统,比如发生 IO 操作 read 时,它会经历两个阶段: 等待内核协议栈的数据准备就绪;将内核中的数据拷贝到用户态的…...

SpringCloud集成ELK

1、添加依赖 <dependency><groupId>net.logstash.logback</groupId><artifactId>logstash-logback-encoder</artifactId><version>6.1</version> </dependency>2、在logback-spring.xml中添加配置信息&#xff08;logback-sp…...

【卷起来】VUE3.0教程-06-组件详解

各位看官&#xff0c;点波关注和赞吧 组件允许我们将 UI 划分为独立的、可重用的部分&#xff0c;并且可以对每个部分进行单独的思考。在实际应用中&#xff0c;组件常常被组织成层层嵌套的树状结构&#xff1a; 这和我们嵌套 HTML 元素的方式类似&#xff0c;Vue 实现了自己的…...

JS Web

Web API 元素通用属性 元素自身属性 事件处理...

【Linux】传输层协议——UDP

零、传输层的作用是负责数据能够从发送端传输到接收端 一、再来认识一下端口号 端口号&#xff08;Port&#xff09;标识了一个主机进行通信的不同的应用程序。在TCP/IP协议中&#xff0c;用“源IP”&#xff0c;“源端口号”&#xff0c;“目的IP”&#xff0c;“目的端口号”…...

算法学习攻略总结 : 入门至进阶,通关之路指南

❃博主首页 &#xff1a; <码到三十五> ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a; <搬的每块砖&#xff0c;皆为峰峦之基&#xff1b;公众号搜索(码到…...

《卷积神经网络 CNN 原理探秘》

CNN基本原理详解 卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;简称CNN&#xff09;&#xff0c;是一种前馈神经网络&#xff0c;人工神经元可以响应周围单元&#xff0c;可以进行大型图像处理。卷积神经网络包括卷积层和池化层。 卷积神经网络是受…...

C#获取计算机信息

目录 效果 项目 代码 下载 效果 项目 代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Management; n…...

派遣函数 - 通过设备链接打开设备

利用文件IO相关的Wn32API对设备进行“打开”和“关闭”操作。要打开设备&#xff0c;必须通过设备的名字才能得到该设备的柄。前面介绍过&#xff0c;每个设备都有设备名称&#xff0c;如HelloDDK驱动程序的设备名为“Device\\MyDDKDevice”&#xff0c;但是设备名无法被用户模…...

Vue 2 中的 `$set` 方法详解

在 Vue 2 中&#xff0c;响应式数据的更新非常重要&#xff0c;因为它确保了当数据改变时&#xff0c;视图能够自动更新。Vue 使用一套高效的机制来追踪依赖并在数据变化时更新视图。然而&#xff0c;在某些情况下&#xff0c;直接修改对象的属性可能不会触发视图更新。这时&am…...

掌握Hive函数[2]:从基础到高级应用

目录 高级聚合函数 多进一出 1. 普通聚合 count/sum... 2. collect_list 收集并形成list集合&#xff0c;结果不去重 3. collect_set 收集并形成set集合&#xff0c;结果去重 案例演示 1. 每个月的入职人数以及姓名 炸裂函数 概述 案例演示 1. 数据准备 1&#xff09;表…...

水壶问题记录

https://leetcode.cn/problems/water-and-jug-problem/description/?envTypestudy-plan-v2&envId2024-spring-sprint-100...

spring综合性利用工具-SpringBootVul-GUI(五)

项目地址 https://github.com/wh1t3zer/SpringBootVul-GUI 0x01简介 本着简单到极致的原则&#xff0c;开发了这么一款半自动化工具&#xff08;PS&#xff1a;这个工具所包含了20个漏洞&#xff0c;开发不易&#xff0c;有任何问题可提issue&#xff09; 尽管是一个为懒人量…...

2024年9月12日(k8s环境及测试 常用命令)

一、环境准备及测试 1、报错处理&#xff1a; kube-system calico-node-5wvln 0/1 Init:0/3 0 16h kube-system calico-node-d7xfb 0/1 Init:0/3 0 16h ku…...

卫生间漏水原因很多,切莫病急乱投医

有位业主说他家卫生间背面的墙湿了&#xff0c;邻居家正好在装修&#xff0c;把家具拆掉以后发现墙面上有一片已经湿了。      和业主相约去现场看看&#xff0c;去楼下业主家看了看&#xff0c;顶面是干燥的&#xff0c;这就说明不往楼下漏水。      这就奇怪了&#…...

IEEE 802.11a OFDM系统的仿真(续)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第九章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all %%%%%%%参数设计部分%%%%%%%Nsp52;%系统子载波数&#xff08;不包括直流载波&#xff09; Nfft64;%FFT长度 Ncp16;…...

Linux cut命令详解使用:掌握高效文本切割

cut 是 Linux 中一个用于从文本文件或标准输入中提取指定字段的命令。它根据分隔符或者字符位置来裁剪文本&#xff0c;是处理文本文件中的字段、列和子字符串的常用工具。 基本语法 cut [选项] 文件或 命令 | cut [选项]常用选项 -b&#xff1a;按字节位置切割&#xff08…...

c++11新特性——endable_shared_from_this

文章目录 一.解决场景代码示例原因 二.解决办法代码 三.底层原理 一.解决场景 一个share_ptr管理的类&#xff0c;如果从类的函数里返回类对象&#xff08;this指针&#xff09;&#xff0c;导致share_ptr引用计数错误&#xff0c;析构时异常问题 代码示例 #include <mem…...

小程序的右侧抽屉开关动画手写效果

<template><view><button click"openDrawer">打开抽屉</button><view v-if"showDrawer" class"drawer" :style"{ backgroundColor: bgColor }" click"closeDrawer"><view class"draw…...

vue3中el-table中点击图片放大时,被表格覆盖

问题&#xff1a;vue3中el-table中点击图片放大时&#xff0c;被表格覆盖。 解决方法&#xff1a;el-image 添加preview-teleported <el-table-column label"封面图" prop"coverUrl"><template #default"scope"><el-imagestyle&q…...

GO学习笔记(4) strconv/time

目录 strconv包1、string与bool之间的转换2、string与int之间的转换 time包1、常用常量定义2、Now&#xff08;&#xff09;获取当前年月日时分秒3、Format&#xff08;&#xff09;时间格式化4、Parse&#xff08;&#xff09;/ ParseInLocation&#xff08;&#xff09;解析时…...

课程管理系统-数据库-基于MySQL的数据库课程设计

目录 前言一、需求分析二、设计数据库模型1.实体关系图(ERD)2.表结构设计三、创建数据库和表四、插入数据五、查询数据六、更新和维护七、安全性与性能优化总结前言 设计一个数据库课程(或任何课程管理系统)时,我们首先需要明确系统的需求和目标。以下是一个基于MySQL的数…...

降维打击 华为赢麻了

文&#xff5c;琥珀食酒社 作者 | 积溪 真是赢麻了 华为估计都懵了 这辈子还能打这么富裕的仗&#xff1f; 其实在苹果和华为的发布会召开之前 我就知道华为肯定会赢 但我没想到 苹果会这么拉胯 华为这是妥妥的降维打击啊 就说这苹果iPhone 16吧 屏幕是变大了、颜色…...

[数据集][目标检测]汽车头部尾部检测数据集VOC+YOLO格式5319张3类别

数据集制作单位&#xff1a;未来自主研究中心(FIRC) 版权单位&#xff1a;未来自主研究中心(FIRC) 版权声明&#xff1a;数据集仅仅供个人使用&#xff0c;不得在未授权情况下挂淘宝、咸鱼等交易网站公开售卖,由此引发的法律责任需自行承担 数据集格式&#xff1a;Pascal VOC格…...

python 生成的代码,需要帮我生成一个直接在一台没有依赖的电脑上运行的 包

要创建一个可以在没有依赖的电脑上运行的包&#xff0c;你需要将你的代码和所有依赖项打包成一个可执行文件。对于Python项目&#xff0c;这通常意味着使用一些工具来打包你的代码和所有必要的库。以下是一些常用的工具和步骤&#xff1a; 确定依赖&#xff1a;首先&#xff0c…...

【Linux】操作系统与进程

&#x1f984;个人主页:修修修也 &#x1f38f;所属专栏:Linux ⚙️操作环境:Xshell (操作系统:CentOS 7.9 64位) 目录 &#x1f4cc;操作系统 &#x1f38f;操作系统的概念 &#x1f38f;设计操作系统的目的 &#x1f38f;操作系统对进程的管理 &#x1f579;️操作系统为什么…...

【Linux】 LTG:移动硬盘部署Ubuntu24.04

Ubuntu To Go 是一种便携式的 Ubuntu 操作系统解决方案&#xff0c;允许用户将 Ubuntu 系统安装在 USB 驱动器或其他可移动存储设备上。这样&#xff0c;用户可以在任何支持 USB 启动的计算机上运行 Ubuntu&#xff0c;而无需在本地硬盘上进行安装。 准备工作 移动硬盘&#x…...

Android的logcat日志详解

Android log系统 logcat介绍 logcat是android中的一个命令行工具&#xff0c;可以用于得到程序的log信息。下面介绍 adb logcat中的详细参数命令以及如何才能高效的打印日志&#xff0c;或把日志保存到我们指定的位置。 可以输入 adb logcat --help&#xff0c;查看一下一些简…...

【Linux】:信号的保存和信号处理

朋友们、伙计们&#xff0c;我们又见面了&#xff0c;本期来给大家带来信号的保存和信号处理相关代码和知识点&#xff0c;如果看完之后对你有一定的启发&#xff0c;那么请留下你的三连&#xff0c;祝大家心想事成&#xff01; C 语 言 专 栏&#xff1a;C语言&#xff1a;从入…...

深入理解Java虚拟机:Jvm总结-Java内存区域与内存溢出异常

第二章 Java内存区域与内存溢出异常 2.1 意义 对于C、C程序开发来说&#xff0c;程序员需要维护每一个对象从开始到终结。Java的虚拟自动内存管理机制&#xff0c;让java程序员不需要手写delete或者free代码&#xff0c;不容易出现内存泄漏和内存溢出问题&#xff0c;但是如果…...