Linux进程通信——匿名管道

目录

1、进程间通信基础概念

2、管道的工作原理

 2.1 什么是管道文件

3、匿名管道的创建与使用

3.1、pipe 系统调用

3.2  父进程调用 fork() 创建子进程

3.3. 父子进程的文件描述符共享

3.4. 关闭不必要的文件描述符

3.5 父子进程通过管道进行通信

父子进程通信的具体例子

 4.管道的四种场景 

4.1 场景一:父进程不写,子进程尝试读取

4.2 场景二:父进程不断写入,直到管道写满,子进程不读取

4.3 场景三:关闭写端,子进程读取数据

4.4 场景四:关闭读端,父进程写入数据

5、匿名管道实操-进程控制

5.1 逻辑设计


🌇前言

在操作系统中,进程间通信(Interprocess Communication,简称IPC)是两个不同进程之间进行协同工作和信息交换的基础。IPC 允许不同的进程相互协调,协作完成任务。进程间通信的方式有很多种,而管道则是一种非常经典且常用的方式。本文将详细探讨 匿名管道,它在进程间通信中扮演着重要的角色。

🏙️正文

1、进程间通信基础概念

在深入探讨匿名管道之前,我们先来了解一些基本概念。进程间通信的目的是为了使多个独立的进程能够协同工作,进行信息交换。主要有四个目的:

  • 数据传输:不同进程之间需要传输数据。例如,将数据从客户端传送到服务器。

  • 资源共享:多个进程共享系统资源,保证高效使用。

  • 事件通知:某个进程需要通知其他进程某个事件的发生,例如进程的终止。

  • 进程控制:用于进程管理,协调进程的执行和资源的分配。

这些目的的核心是打破进程的独立性,让它们能够共享资源和信息,协同完成任务。 

2、管道的工作原理

管道是一种用于进程间通信的方式,它本质上是一个文件。无论是匿名管道还是命名管道,它们的原理都是通过文件描述符来共享数据。每个管道都有两个端口:一个是写端,另一个是读端。

管道最初是由 Unix 系统引入的,它允许具有“血缘关系”的进程(如父子进程)通过管道进行通信。管道的实现通常会涉及到内核为进程分配文件描述符。父进程在创建管道后,会为其子进程继承文件描述符,并通过关闭不需要的端口,确保通信的流向。

 2.1 什么是管道文件

管道文件是操作系统中用于实现进程间通信的特殊文件,具有以下几个显著特点:

  • 单向通信:管道是 单向 的通信方式,意味着数据只能从一个端流向另一个端。通常情况下,一个进程写数据到管道,而另一个进程从管道中读取数据。这种方式被称为“半双工通信”,如果需要实现双向通信,需要两个管道。

  • 基于文件的设计,管道本质上是内存中的文件; 管道文件并不是磁盘级别的文件,而是内存级别文件,管道文件没有自己的inode,也没有名字。过内存中的缓冲区进行存储,操作系统会将管道作为文件来处理。

  • 管道分为 匿名管道命名管道。匿名管道没有名字,是由操作系统在内存中创建的,仅限于有血缘关系(如父子进程或兄弟进程)的进程间通信。由于没有名字,匿名管道无法在进程间直接共享。

    与此不同,命名管道(FIFO)则拥有一个系统中的路径名,因此它可以被不具备血缘关系的进程之间共享。这使得命名管道的通信更加灵活。

  • 生命周期与进程绑定,管道的生命周期与创建它的进程生命周期紧密相关。当进程结束时,管道也会被操作系统回收。管道文件的生命周期由打开它的进程的生命周期决定,在进程终止时,管道的资源会被释放。

  • 内存缓冲区,管道的一个重要特点是,它在内存中创建一个缓冲区,用于存储待传输的数据。由于是内存中的缓冲区,管道中的数据并不会被持久化到磁盘中。这使得管道比磁盘文件更为高效,但数据在管道中的存储是临时的,不会在系统重启后保留

  • 阻塞行为与同步机制

    管道的通信遵循 阻塞 和 同步 机制。当读端尝试读取数据时,如果管道为空,进程会阻塞,直到写端写入数据。同样,写端如果尝试写入数据时,如果管道已满,进程也会阻塞,直到读端读取部分数据。

    这种阻塞行为本身提供了一定的同步机制。管道会保证写入数据的顺序,并且数据在被读取之前不会丢失。这使得进程间的通信是同步的,确保数据完整传输。

  • 管道大小限制,管道的大小在不同的操作系统和系统配置中可能有所不同。通常,管道大小会受到系统配置的限制。在 Linux 中,管道大小的默认值通常为 64KB(从 Linux 2.6.11 版本开始),不过在不同的系统或不同的内核版本中,管道的大小也可能有所变化

  • 在管道中,写入 与 读取 的次数并不是严格匹配的,此时读写次数没有强相关关系,管道是面向字节流读写的面向字节流读写又称为 流式服务:数据没有明确的分割,不分一定的报文段;与之相对应的是 数据报服务:数据有明确的分割,拿数据按报文段拿不论写端写入了多少数据,只要写端停止写入,读端都可以将数据读取。
  • 具有一定的协同能力让 读端 和 写端 能够按照一定的步骤进行通信(自带同步机制)当读端进行从管道中读取数据时,如果没有数据,则会阻塞,等待写端写入数据;如果读端正在读取,那么写端将会阻塞等待读端,因此 管道自带 同步与互斥 机制。

3、匿名管道的创建与使用

具体流程:

父进程创建匿名管道,同时以读、写的方式打开匿名管道,此时会分配两个 fd
fork 创建子进程,子进程拥有自己的进程系统信息,同时会继承原父进程中的文件系统信息,此时子进程和父进程可以看到同一份资源:匿名管道 pipe
因为子进程继承了原有关系,因此此时父子进程对于 pipe 都有读写权限,需要确定数据流向,关闭不必要的 fd,比如父进程写、子进程读,或者父进程读、子进程写都可以。

3.1、pipe 系统调用

匿名管道的创建通过 pipe() 系统调用来实现。该函数会创建一个管道,并返回两个文件描述符:一个用于读,另一个用于写。函数原型如下:

#include <unistd.h>int pipe(int pipefd[2]);

 传入一个大小为2的整型数组作为输出型参数操作系统就会生成一个管道文件,并且让进程以读写的方式分别打开进程,并且将进程的读管道文件的文件标识符写道pipe[1],写管到文件描述符写道pipe[1]之中。

int pipefd[2];
pipe(pipefd);  // 创建管道
3.2  父进程调用 fork() 创建子进程

当父进程调用 fork() 时,操作系统会创建一个新的子进程。子进程会继承父进程的文件描述符表,因此,父子进程可以共享父进程所创建的管道文件描述符。也就是说,父进程和子进程都会拥有相同的管道读端和写端。

pid_t pid = fork();
3.3. 父子进程的文件描述符共享

父进程和子进程共享管道的读写端口意味着:

  • 父进程子进程 都可以操作 pipefd[0]pipefd[1]但它们之间的角色(读或写)通常是根据进程的需求来确定的。

  • 父进程和子进程在创建时各自拥有自己的 进程资源但文件描述符表会被子进程继承,指向相同的管道内存资源。

3.4. 关闭不必要的文件描述符

由于管道是单向通信的,所以为了避免数据混乱,父进程和子进程通常会关闭不必要的文件描述符。例如,如果父进程要写数据到管道而子进程读取数据,父进程应该关闭管道的读端,子进程应该关闭管道的写端

// 父进程关闭管道的读端,子进程关闭管道的写端
close(pipefd[0]);  // 父进程关闭读端
close(pipefd[1]);  // 子进程关闭写端
3.5 父子进程通过管道进行通信
  • 父进程写数据:父进程通过管道的写端 pipefd[1] 向管道中写入数据。

    write(pipefd[1], "Hello from parent", 17);
    

    子进程读数据:子进程通过管道的读端 pipefd[0] 从管道中读取数据。

    char buf[128];
    read(pipefd[0], buf, sizeof(buf));
    
  • 父进程写入的数据会通过管道传递给子进程。

  • 子进程从管道中读取数据,通常会按顺序接收父进程写入的数据。

父子进程通信的具体例子

下面是一个完整的示例代码,展示了父子进程如何通过管道进行通信:

#include <iostream>
#include <cassert>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>using namespace std;int main()
{// 1、创建匿名管道int pipefd[2]; // 数组int ret = pipe(pipefd);assert(ret == 0);(void)ret; // 防止 release 模式中报警告// 2、创建子进程pid_t id = fork();if (id == 0){// 子进程内close(pipefd[1]); // 3、子进程关闭写端// 4、开始通信char buff[64]; // 缓冲区while (true){int n = read(pipefd[0], buff, sizeof(buff) - 1);    //注意预留一个位置存储 '\0'buff[n] = '\0';if (n >= 5 && n < 64){// 读取到了信息cout << "子进程成功读取到信息:" << buff << endl;}else{// 未读取到信息if (n == 0)cout << "子进程没有读取到信息,通信结束!" << endl;// 读取异常(消息过短)elsecout << "子进程读取数据量为:" << n << " 消息过短,通信结束!" << endl;break;}}close(pipefd[0]); // 关闭剩下的读端exit(0);          // 子进程退出}// 父进程内close(pipefd[0]); // 3、父进程关闭读端char buff[64];// 4、开始通信srand((size_t)time(NULL)); // 随机数种子while (true){int n = rand() % 26;for (int i = 0; i < n; i++)buff[i] = (rand() % 26) + 'A'; // 形成随机消息buff[n] = '\0';                    // 结束标志cout << "=============================" << endl;cout << "父进程想对子进程说: " << buff << endl;write(pipefd[1], buff, strlen(buff)); // 写入数据if (n < 5)break; // 消息过短时,不写入sleep(1);}close(pipefd[1]); // 关闭剩下的写端// 父进程等待子进程结束int status = 0;waitpid(id, &status, 0);// 通过 status 判断子进程运行情况if ((status & 0x7F)){printf("子进程异常退出,core dump: %d   退出信号:%d\n", (status >> 7) & 1, (status & 0x7F));}else{printf("子进程正常退出,退出码:%d\n", (status >> 8) & 0xFF);}return 0;
}

 

站在 文件描述符 的角度理解上述代码:

 

 所以,看待 管道 ,就如同看待 文件 一样!管道 的使用和 文件 一致,迎合 Linux一切皆文件思想。

 4.管道的四种场景 

4.1 场景一:父进程不写,子进程尝试读取

情况描述:

  • 父进程没有写入数据到管道。

  • 子进程尝试从管道中读取数据。

结果:

  • 由于管道为空,子进程在尝试读取时会进入阻塞状态。

  • 只有当父进程开始向管道中写入数据后,子进程才会成功读取数据。

形象化理解:

  • 这就像一个垃圾桶,子进程是倒垃圾的工作人员,而父进程是往垃圾桶里扔垃圾。如果垃圾桶为空,子进程(倒垃圾的人)就无法工作,必须等待父进程(扔垃圾的人)开始丢垃圾,才能开始工作。

4.2 场景二:父进程不断写入,直到管道写满,子进程不读取

情况描述:

  • 父进程持续向管道写入数据,直到管道被写满。

  • 子进程不进行读取操作。

结果:

  • 当管道的缓冲区满了,父进程会被阻塞,无法继续写入数据,直到子进程读取数据。

  • 这是因为管道有大小限制,管道满时,写端无法继续写入,必须等待管道中有空间才能继续写入。

形象化理解:

  • 就像垃圾桶满了后,不能继续往里面丢垃圾,必须等到垃圾桶被清空(子进程读取数据)之后,才能继续丢垃圾。

4.3 场景三:关闭写端,子进程读取数据

情况描述:

  • 父进程写入数据到管道,并关闭写端。

  • 子进程从管道中读取数据,并在读取到末尾时判断写端是否关闭。

结果:

  • 当父进程关闭写端后,子进程可以继续读取管道中的数据,直到数据读取完。

  • 子进程在读取到数据末尾时会收到 read的,表示已经没有更多数据可读取,且写端已关闭。

形象化理解:

  • 这类似于垃圾桶的垃圾已经被倒空,子进程(倒垃圾的人)会看到垃圾桶已经没有垃圾了。即使它继续尝试“倒垃圾”,也不会有新的垃圾,显示读取到了文件末尾。

4.4 场景四:关闭读端,父进程写入数据

情况描述:

  • 父进程是写端,子进程是读端。父进程写入数据。

  • 父进程在读取五次后关闭读端。

结果:

  • 当关闭读端后,写端(父进程)会收到 SIGPIPE 信号,通常导致进程终止。

  • 因为操作系统会发现,写端已没有可用的读取端(读端关闭了),它会强制终止写端进程以防止资源浪费。

形象化理解:

这就像垃圾桶的“倒垃圾的人”(写端)发现没有“垃圾桶”(读端)可以丢垃圾,因此操作系统会终止写端,避免无意义的行为继续发生。


5、匿名管道实操-进程控制

匿名管道作为 IPC 的其中一种解决方案,那么肯定有它的实战价值

场景:父进程创建了一批子进程,并通过多条匿名管道与它们链接,父进程选择某个子进程,并通过匿名管道与子进程通信,并下达指定的任务让其执行

5.1 逻辑设计

首先创建一批子进程及匿名管道 -> 子进程(读端)阻塞,等待写端写入数据 -> 选择相应的进程,并对其写入任务编号(数据)-> 子进程拿到数据后,执行相应任务

1.创建一批进程及管道

首先需要先创建一个包含进程信息的类,最主要的就是子进程的写端 fd,这样父进程才能通过此 fd 进行数据写入
循环创建管道、子进程,进行相应的管道链接操作,然后子进程进入任务等待状态,父进程将创建好的子进程信息注册
假设子进程获取了任务代号,那么应该根据任务代号,去执行相应的任务,否则阻塞等待
注意: 因为是创建子进程,所以存在关系重复继承的情况,此时应该统计当前子进程的写端 fd,在创建下一个进程时,关闭无关的 fd

具体体现为:每次都把 写端 fd 存储起来,在确定关系前 “清理” 干净

关于上述操作的危害,需要在编写完进程等待函数后,才能演示其作用 。

完整代码如下:

Task.hpp

#pragma once 
#include<iostream>
#include<vector>typedef void (*task_t)();void task1()
{std::cout << "lol 刷新日志" << std::endl;
}
void task2()
{std::cout << "lol 更新野区,刷新出来野怪" << std::endl;
}
void task3()
{std::cout << "lol 检测软件是否更新,如果需要,就提示用户" << std::endl;
}
void task4()
{std::cout << "lol 用户释放技能,更新用的血量和蓝量" << std::endl;
}
void LoadTask(std::vector<task_t>& task)
{task.push_back(task1);task.push_back(task2);task.push_back(task3);task.push_back(task4);
}

 processpool.cc

#include "Task.hpp"
#include <string>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <cassert>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>using namespace std;const int processnum =4;
vector<task_t> tasks; //把所有的任务,装进去class channel
{public:channel(int &cmdfd ,int &mypid,string &name):_name(name),_cmdfd(cmdfd),_mypid(mypid){}public:string _name;//子进程的名字int _cmdfd;//发信号的文件描述符  int _mypid;//我的PID
};void Menu()
{std::cout << "################################################" << std::endl;std::cout << "# 1. 刷新日志             2. 刷新出来野怪        #" << std::endl;std::cout << "# 3. 检测软件是否更新      4. 更新用的血量和蓝量  #" << std::endl;std::cout << "#                         0. 退出               #" << std::endl;std::cout << "#################################################" << std::endl;
}void slaver(int pool)
{while(true){int cmdcode=0;//通过调用码,去领任务int n=read(pool,&cmdcode,sizeof(int));cout <<"slaver say@ get a command: "<< getpid() << " : cmdcode: " <<  cmdcode << endl;if(cmdcode>0&&cmdcode<=tasks.size()) tasks[cmdcode-1]();if(n==0) break;}
}void InitProcesspool(vector<channel>&channels)
{vector<int> d;//把父进程的所有打开的写管道存储到里面for(int i=1;i<=processnum;i++){int pipeid[2];int n=pipe(pipeid);assert(!n);int pid=fork();if(pid==0){cout<<"创建的"<< i<<"号子进程"<<endl;for(auto &t:d){close(t);//关掉所有不相关的管道}close(pipeid[1]);//关闭写管道slaver(pipeid[0]);//读操作// close(pipeid[0]);//多此一举cout<<"关闭的"<< i<<"号子进程"<<endl;exit(0);}close(pipeid[0]);string name="创建的子进程"+to_string(i);channels.push_back({pipeid[1],pid,name});//那个管道发数据记录下来,d.push_back(pipeid[1]);//把一会发数据的管道号记下来sleep(1);}
}void setslaver(vector<channel>&channels)
{int which=0;int cnt=4;while(cnt--){int slect=0;Menu();cin>>slect;     if(!slect) break;   // rand((void)time(nullptr));// int i=srand()%5;cout<<"farher message"<<channels[which]._name<<endl;write(channels[which]._cmdfd,&slect,sizeof(int));     which++;which%=channels.size();sleep(1);}
}void Quitpool(vector<channel>&channels)
{for(auto& t:channels)//关闭所有的写管道{close(t._cmdfd);waitpid(t._mypid,nullptr,0);}
}int main()
{vector<channel> channels;//把打开的子进程装进来LoadTask(tasks);InitProcesspool(channels);//创建子进程setslaver(channels);//发配任务,采用轮询Quitpool(channels);//关闭写管道,等到read=0子进程退出,全部关闭return 0;
}

总体来说,在使用这个小程序时,以下关键点还是值得多注意的

注册子进程信息时,存储的是 写端 fd,目的是为了通过此 fd 向对应的子进程写数据,即使用不同的匿名管道

创建管道后,需要关闭父、子进程中不必要的 fd

需要特别注意父进程写端 fd 被多次继承的问题,避免因写端没有关干净,而导致读端持续阻塞关闭读端对应的写端后,读端会读到 0,可以借助此特性结束子进程的运行

在选择进程 / 任务 时,要做好越界检查

等待子进程退出时,需要先关闭写端,子进程才会退出,然后才能正常等待。

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

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

相关文章

sql:sql在office中的应用有哪些?

在Office软件套件中&#xff0c;主要是Access和Excel会用到SQL&#xff08;结构化查询语言&#xff09;&#xff0c;以下是它们在这两款软件中的具体应用&#xff1a; 在Access中的应用 创建和管理数据库对象&#xff1a; 创建表&#xff1a;使用CREATE TABLE语句可以创建新的数…

零基础完全理解视觉语言模型(VLM):从理论到代码实践

本文是《从LLM到VLM&#xff1a;视觉语言模型的核心技术与Python实现》的姊妹篇&#xff0c;主要面向零基础的读者&#xff0c;希望用更通俗易懂的语言带领大家入门VLM。本教程的完整代码可以在GitHub上找到&#xff0c;如果你有任何问题或建议&#xff0c;欢迎交流讨论。 写在…

数据结构 Map和Set

文章目录&#x1f4d5;1. 二叉搜索树✏️1.1 查找操作✏️1.2 插入操作✏️1.3 删除操作&#x1f4d5;2. Map的使用✏️2.1 Map的常用方法✏️2.2 TreeMap和HashMap的区别✏️2.3 HashMap的底层实现&#x1f4d5;3. Set的使用✏️3.1 Set的常用方法✏️3.2 TreeSet和HashSet的区…

树莓派5-系统 Debian 12 开启VNC远程访问踩坑记录

简单记录一下踩坑&#xff0c;安装vnc远程访问服务并设置开机自启1.查看系统版本&#xff0c;我这里的系统版本是 12cat /etc/os-release2.安装VNC服务sudo apt install realvnc-vnc-server realvnc-vnc-viewer -y3.创建服务单元文件&#xff1a;sudo nano /etc/systemd/system…

TASK2 夏令营:用AI做带货视频评论分析

TASK2 夏令营&#xff1a;用AI做带货视频评论分析**电商评论洞察赛题&#xff1a;从Baseline到LLM进阶优化学习笔记**一、 赛题核心解读1.1. 任务链条与目标1.2. 关键挑战与评分机制二、 Baseline方案回顾与瓶颈分析2.1. Baseline技术栈2.2. 核心瓶颈三、 进阶优化策略&#xf…

Docker:安装命令笔记

目录 零、安装&#xff1a;略 一、镜像 1.0、获取镜像&#xff1a; 1.1、查看镜像&#xff1a; 1.2、删除镜像&#xff1a; 二、容器 2.0、创建并启动容器 2.1、tomcat和jdk9的“创建并启动容器”的命令 2.2、容器操作 2.3、容器日志操作 零、安装&#xff1a;略 略 …

Python七彩花朵

系列文章 序号直达链接Tkinter1Python李峋同款可写字版跳动的爱心2Python跳动的双爱心3Python蓝色跳动的爱心4Python动漫烟花5Python粒子烟花Turtle1Python满屏飘字2Python蓝色流星雨3Python金色流星雨4Python漂浮爱心5Python爱心光波①6Python爱心光波②7Python满天繁星8Pytho…

【保姆级图文详解】MCP架构(客户端-服务端)、三种方式使用MCP服务、Spring AI MCP客户端和服务端开发、MCP部署方案、MCP安全性

文章目录前言一、MCP(model context protocol)1.1、概念描述1.2、MCP作用与意义1.3、MCP架构二、使用MCP(model context protocol)2.1、云平台使用MCP2.2、软件客户端使用MCP2.3、Spring AI程序中使用MCP三、Spring AI MCP(model context protocol)开发过程3.1、MCP服务端开发3…

Linux的 iproute2 配置:以太网(Ethernet)、绑定(Bond)、虚拟局域网(VLAN)、网桥(Bridge)笔记250713

Linux的 iproute2 配置:以太网(Ethernet)、绑定(Bond)、虚拟局域网(VLAN)、网桥(Bridge&#xff09;笔记250713 在 Linux 中使用 iproute2 工具集配置网络是现代且推荐的方法&#xff0c;它取代了旧的 ifconfig、route、brctl、vconfig 等命令。iproute2 提供了统一的接口 ip …

当信任上链解码区块链溯源系统开发逻辑与产业变革

当信任上链&#xff1a;解码区块链溯源系统的开发逻辑与产业变革在上海某高端超市的进口水果区&#xff0c;消费者王女士拿起一盒车厘子&#xff0c;用手机扫描包装上的二维码&#xff0c;屏幕立刻弹出一串动态信息&#xff1a;智利瓦尔帕莱索港口的装船时间、海关清关的具体日…

可视化DIY小程序工具!开源拖拽式源码系统,自由搭建,完整的源代码包分享

温馨提示&#xff1a;文末有资源获取方式传统的小程序开发对技术要求较高&#xff0c;这使得许多非技术人员望而却步。可视化DIY小程序工具应运而生&#xff0c;它通过拖拽式操作和开源代码系统&#xff0c;极大地降低了开发门槛&#xff0c;让更多人能够快速构建个性化小程序。…

【MLLM】多模态理解GLM-4.1V-Thinking模型

note GLM-4.1V-Thinking模型引入 课程采样强化学习&#xff08;RLCS, Reinforcement Learning with Curriculum Sampling&#xff09; 策略&#xff0c;在多个复杂推理任务中实现能力突破&#xff0c;整体性能达到 10B 级别视觉语言模型的领先水平。GLM-4.1V-9B-Thinking 通过…

【C++详解】STL-priority_queue使用与模拟实现,仿函数详解

文章目录一、priority_queue使用仿函数控制优先级sort算法里的仿函数二、手撕优先级队列优先级队列的容器适配器入堆出堆top/size/empty迭代器区间构造初始化(解耦)三、仿函数仿函数控制冒泡排序仿函数控制priority_queue比较逻辑仿函数使用场景仿函数的其他使用场景源码一、pr…

在mac m1基于ollama运行deepseek r1

1 下载和安装 在ollama的官网下载mac m1版本的ollama https://ollama.com/ 最终获得如下所示的下载地址 https://github.com/ollama/ollama/releases/latest/download/Ollama.dmg 然后点击安装&#xff0c;然后测试 ollama list 2 运行deepseek r1 deepseek-r1:8b 比较适…

TCP与UDP协议详解:网络世界的可靠信使与高速快递

> 互联网的骨架由传输层协议支撑,而TCP与UDP如同血管中的红细胞与血小板,各司其职却又缺一不可 ### 一、初识传输层双雄:网络通信的基石 想象你要给朋友寄送重要文件: - **TCP** 如同顺丰快递:**签收确认+物流追踪**,确保文件完整送达 - **UDP** 如同普通信件:**直接…

Datawhale AI 夏令营【更新中】

Datawhale AI 夏令营【更新中】夏令营简介大模型技术&#xff08;文本&#xff09;方向&#xff1a;用AI做带货视频评论分析机器学习&#xff08;数据挖掘&#xff09;方向&#xff1a;用AI预测新增用户夏令营简介 本次AI夏令营是Datawhale在暑期发起的大规模AI学习活动&#…

AutoDL挂载阿里云OSS

文章目录前言AutoDL 设置阿里OSS设置OSS配置相关key 相关竞猜时间前言 最近&#xff0c;AutoDL提示北京A区网盘功能要下架&#xff0c;然后需要对网盘中数据进行转移等操作&#xff0c;我想网盘中数据下载到本地&#xff0c;大概16G&#xff1b;直接在网盘那里下载&#xff0c…

java 基本数据类型所对应的包装类

一,对应列举Java 中有 8 种基本数据类型&#xff0c;每种基本数据类型都有对应的包装类&#xff0c;它们分别是&#xff1a;二,包装类的作用1. 满足面向对象编程需求Java 是面向对象的编程语言&#xff0c;基本数据类型不是对象&#xff0c;无法使用面向对象的特性&#xff08;…

牛客网50题-10

1.小苯的数字权值#include <iostream> #include <algorithm> using namespace std;const int max_n 2000000; int d[max_n 1]; int f[max_n 1];int main() {for(int i 1; i<max_n;i){for(int j i; j<max_n;ji){d[j];}}for(int i1; i<max_n;i){f[i] d…

基于springboot的大学公文收发管理系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业多年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了多年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…