Linux——匿名管道

news2024/9/24 9:22:45

Linux——匿名管道

  • 什么是管道
  • 匿名管道的底层原理
  • 观察匿名管道现象
    • 读写端的几种情况
    • 写端慢,读端快
    • 写端快,读端慢
  • 管道的大小
    • 写端关闭,读端一直读
    • 写端一直写,读端关闭

我们之前一直用的是vim来编写代码,现在有了vscode这样强大的编辑器,我们可以把我们的vim放一边了,如果还有小伙伴还没有配置好vscode的远端,可以点击这里:

https://blog.csdn.net/qq_67693066/article/details/136368891

我们今天进入管道的学习:

什么是管道

在计算机领域,管道(Pipeline)是一种将多个命令连接在一起以形成数据流的机制。它允许一个命令的输出成为另一个命令的输入,从而实现命令之间的数据传递和处理。

在 Unix/Linux 系统中,管道通常用竖线符号 | 表示。通过管道,可以将一个命令的输出传递给另一个命令进行处理,从而构建复杂的数据处理流程。

例如,假设我们有两个命令 command1 和 command2,我们可以使用管道将它们连接起来:

command1 | command2

这将会把 command1 的输出作为 command2 的输入,command2 将处理 command1 的输出并生成最终的结果。

管道的优势包括:

简化复杂任务: 管道可以将多个简单的命令组合成一个复杂的任务,使得任务的实现更加简单和高效。
模块化和可重用性: 通过将命令连接在一起,可以更好地组织代码并提高代码的可重用性。每个命令都可以专注于完成一个特定的任务。
减少临时文件: 管道可以避免将数据存储到临时文件中,从而减少了文件 I/O 的开销和磁盘空间的占用。
实时处理: 管道允许命令之间的实时数据传递,这对于需要连续处理数据的任务非常有用,比如日志处理、数据流分析等。

简单来说,管道就是连接多个指令。我们之前也在频繁使用管道:比如我们想统计当前登录到系统的用户数量。
在这里插入图片描述
who指令的结果作为wc -l的输入。

匿名管道的底层原理

我们这里讲的简单一点,现在我们有一个进程,它自身会被以读和写的方式分别打开一次:
在这里插入图片描述
然后这个读和写都会往一个缓冲区输入输出数据:

这个时候父进程创建子进程,子进程发生浅拷贝,指向没有发生变化:
在这里插入图片描述
这里注意一下,管道一般是单向的,所以我们现在想让父进程读,让子进程写
在这里插入图片描述
这样形成了一个单向通道,这个就是一个基本的匿名管道

匿名管道(Anonymous Pipe)是一种用于进程间通信的机制,特别是在 Unix 和类 Unix 系统中。它允许一个进程将输出发送到另一个进程的输入,从而实现进程间的数据传输。
以下是匿名管道的一些关键特点:
单向通信:匿名管道是单向的,只能支持单向数据流。它只能用于单一方向的通信,通常是父进程到子进程或者相反。
创建:匿名管道通过调用系统调用 pipe() 来创建。这个系统调用创建了一个管道,返回两个文件描述符,其中一个用于读取管道,另一个用于写入管道。
父子进程通信:通常,匿名管道用于父子进程之间的通信。在创建子进程后,父进程可以将数据写入管道,而子进程则可以从管道中读取这些数据。
半双工:匿名管道是半双工的,意味着数据只能在一个方向上流动。如果需要双向通信,则需要创建两个管道,或者使用其他的进程间通信机制,比如命名管道或套接字。
进程同步:匿名管道通常用于进程间的同步和协作。一个进程可能会阻塞在读取管道上,直到另一个进程写入数据到管道中为止。
匿名管道在 Unix 系统中被广泛应用,特别是在 shell 编程和进程间通信方面。它提供了一种简单而有效的方式,允许不同进程之间进行数据交换和协作

我也有专门创建管道的函数pipe
在这里插入图片描述
我们可以来试一下:

#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;

int main()
{
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   cout<<"pipefd[0]"<<"--->"<<pipefd[0]<<"pipefd[1]"<<"--->"<<pipefd[1]<<endl;

    return 0;
}

运行:
在这里插入图片描述
这里我们发现pipefd[0]指代的是3,而我们的pipefd[1]指代的是4。其实也很好理解,因为0,1,2被标准输入,标准输出,标准错误占了。所以从3开始。

同时,如果我么查手册会看到这样一段话:
在这里插入图片描述
这段话的主要意思是pipefd[0]是读端,而pipefd[1]是写端。这为我们以后哪个开哪个关提供了依据。

观察匿名管道现象

我们先搭建架子来观察我们匿名管道的现象:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
   }
   
   if(id ==0)
   {
       //子进程要做的事
       exit(0);
   }
   
   //父进程要做的事

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

现在我们想让子进程写,父进程读,我们把相应用不到的管道关闭:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
using namespace std;

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

我们让子进程写入一些东西,然后让父进程来读,看看行不行:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 10;

       while(cnt)
       {
           //缓冲区
           char message[MAX];

           //向缓冲区里写
           snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

           cnt--;

           //向管道写
           write(pipefd[1],message,strlen(message));

           sleep(1);
       }
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      if(n > 0)
      {
         cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      }
     
   }

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

在这里插入图片描述
我们看到父进程真的拿到了子进程写的东西,这就是一个最基本的管道的应用。

读写端的几种情况

写端慢,读端快

我们模拟一下,写端慢,读端快的情况

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 10;

       while(cnt)
       {
           //缓冲区
           char message[MAX];

           //向缓冲区里写
           snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

           cnt--;

           //向管道写
           write(pipefd[1],message,strlen(message));

           sleep(100); //模拟写端慢
       }
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      if(n > 0)
      {
         cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      }
     
   }

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

在这里插入图片描述
我们发现父进程处于一个休眠的状态,很明显,它是在等待我们的子进程进行写入。

这里我们可以得出匿名管道具有同步机制,读端和写端是协同工作的。

写端快,读端慢

我们调换一下,让写端快,读端快:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 10000;

       while(cnt)
       {
           //缓冲区
           char message[MAX];

           //向缓冲区里写
           snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

           cnt--;

           //向管道写
           write(pipefd[1],message,strlen(message));

           cout<<"writing......"<<endl;
       }
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      sleep(2); //睡眠2秒
      ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      if(n > 0)
      {
         cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      }
     
   }

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}


执行:
在这里插入图片描述
过了2秒之后:
在这里插入图片描述
数据一瞬间出来了。

这里我们可以得出匿名管道是面向字节流的,它没有硬性规定我写一条你必须马上读一条,而是以字节流的形式读或写。

管道的大小

我们可以写一段代码来测试我们管道的大小:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 0;

       while(1)
       {
         //   //缓冲区
         //   char message[MAX];

         //   //向缓冲区里写
         //   snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

         //   cnt--;

         //   //向管道写
         //   write(pipefd[1],message,strlen(message));

         //   cout<<"writing......"<<endl;

         char c = 'a';
         write(pipefd[1], &c, 1);
         cnt++;
         cout << "write ....: " << cnt << endl;
       }
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      // sleep(2); //睡眠2秒
      // ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      // if(n > 0)
      // {
      //    cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      // }
     
   }

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

在这里插入图片描述
我们发现最后结果是65536,折合下来也就是64kb左右的大小。

我们也可以用指令来查看管道大小:ulimit -a
在这里插入图片描述

我们查看的管道大小为512 * 8 = 4kb,好像比我们看到的小。这个其实不是真正的大小。

写端关闭,读端一直读

我们现在让写段写一段时间后直接关闭,但是读端没有关闭:

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 0;

       while(1)
       {
           //缓冲区
           char message[MAX];

           //向缓冲区里写
           snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

           cnt++;

           //向管道写
           write(pipefd[1],message,strlen(message));

          //跳出
          if(cnt > 3) break;

         // char c = 'a';
         // write(pipefd[1], &c, 1);
         // cnt++;
         // cout << "write ....: " << cnt << endl;
       }

       //关闭写端
       close(pipefd[1]);
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      //sleep(2); //睡眠2秒
      ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      if(n > 0)
      {
         cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      }

      cout<<"father return value:"<< n << endl;
     
     sleep(1);
   }

   //回收子进程
   pid_t rid = waitpid(id,nullptr,0);
   if(rid == id)
   {
      cout<<"wait success"<<endl;
   }

   return 0;
}

在这里插入图片描述
这样表示:写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾

同时注意,进程退出,管道自动关闭

写端一直写,读端关闭

#include<iostream>
#include<unistd.h>
#include<cassert>
#include<sys/types.h>
#include<sys/wait.h>
#include<stdio.h>
#include<string.h>
using namespace std;
#define MAX 1024

int main()
{
   //建立管道
   int pipefd[2] = {0};
   int n = pipe(pipefd);
   assert(n == 0);

   //创建子进程
   pid_t id = fork();

   //子进程
   if(id < 0)
   {
    perror("fork fail");
    return 1;
   }
   
   if(id ==0)
   {
       //子进程要做的事
       close(pipefd[0]); //关闭读的通道

       //向管道写入
       int cnt = 0;

       while(true)
       {
           //缓冲区
           char message[MAX];

           //向缓冲区里写
           snprintf(message,sizeof(message),"hello father I am child my pid:%d cnt:%d ", getpid(),cnt);

           cnt++;

           //向管道写
           write(pipefd[1],message,strlen(message));

           sleep(1);

          //跳出
          //if(cnt > 3) break;

         // char c = 'a';
         // write(pipefd[1], &c, 1);
         // cnt++;
         // cout << "write ....: " << cnt << endl;

         sleep(1);
       }

       //关闭写端
       //close(pipefd[1]);
       exit(0);
   }
   
   //父进程要做的事
   close(pipefd[1]); //关闭写的通道

   //从管道中读取数据
   char buffer[MAX];

   while(true)
   {
      //sleep(2); //睡眠2秒
      ssize_t n = read(pipefd[0],buffer,sizeof(buffer));
    
      if(n > 0)
      {
         cout << getpid() << "," << "chid say :" << buffer << "to me" << endl;
      }

      cout<<"father return value:"<< n << endl;
     
     sleep(1);

     //直接跳出
     break;
   }

   //关闭读端
   close(pipefd[0]);

   sleep(5);
   int status = 0;
   pid_t rid = waitpid(id, &status, 0);
   if (rid == id)
   {
      cout << "wait success, child exit sig: " << (status&0x7F) << endl;
   }

   // //回收子进程
   // pid_t rid = waitpid(id,nullptr,0);
   // if(rid == id)
   // {
   //    cout<<"wait success"<<endl;
   // }

   return 0;
}

我们得到一下它的信号:
在这里插入图片描述
我们查一下13号信号:
在这里插入图片描述
13号信号是:SIGPIPE:

SIGPIPE 是在进程向一个已经被关闭的管道(或者其他的类似的通信方式)写入数据时,内核向该进程发送的信号。这个信号的默认行为是终止进程。
常见的场景是,一个进程向另一个进程通过管道发送数据,但接收数据的进程提前退出,导致写入数据的进程尝试往已关闭的管道写入数据。在这种情况下,内核会发送 SIGPIPE 信号给写入数据的进程,通知它目标进程已经退出,不再接收数据。

所以我们才有上述现象。

总结一下管道有4种情况:

管道的4种情况

  1. 正常情况,如果管道没有数据了,读端必须等待,直到有数据为止(写端写入数据了)
  2. 正常情况,如果管道被写满了,写端必须等待,直到有空间为止(读端读走数据)
  3. 写端关闭,读端一直读取, 读端会读到read返回值为0, 表示读到文件结尾
  4. 读端关闭,写端一直写入,OS会直接杀掉写端进程,通过想目标进程发送SIGPIPE(13)信号,终止目标进程

5种特性:

管道的5种特性

  1. 匿名管道,可以允许具有血缘关系的进程之间进行进程间通信,常用与父子,仅限于此
  2. 匿名管道,默认给读写端要提供同步机制 — 了解现象就行
  3. 面向字节流的 — 了解现象就行
  4. 管道的生命周期是随进程的
  5. 管道是单向通信的,半双工通信的一种特殊情况

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

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

相关文章

Vue路由(黑马程序员)

路由介绍 将资代码/vue-project(路由)/vue-project/src/views/tlias/DeptView.vue拷贝到我们当前EmpView.vue同级&#xff0c;其结构如下&#xff1a; 此时我们希望&#xff0c;实现点击侧边栏的部门管理&#xff0c;显示部门管理的信息&#xff0c;点击员工管理&#xff0c;显…

CK98-数学家键盘配置

官方驱动和说明书下载地址 https://www.coolkiller.cn/download/lists_6.html 介绍&#xff1a;https://new.qq.com/rain/a/20221229A09B1M00 官方CK-98数学家驱动版本&#xff08;谨慎更新&#xff09; 如果升级驱动出现问题&#xff0c;重启驱动软件后会默认让你恢复的。 …

vue ts html 中如何遍历 Enum 类型构建页面结构

vue ts html 中如何遍历 Enum 类型构建页面结构 Enum 被用在一些有明确有限数量的值的时候&#xff0c;比如定义菜单的类型 一、需求 定义了一个 Enum 用来标记菜单类型&#xff1a; enum EnumMenuType {目录 1,菜单,按钮,外链 }你需要知道 Enum 它的序号是随第一个定义的值…

第四十七回 一丈青单捉王矮虎 宋公明二打祝家庄-强大而灵活的python装饰器

四面全是埋伏&#xff0c;宋江和众人一直绕圈跑不出去。正在慌乱之时&#xff0c;石秀及时赶到&#xff0c;教大家碰到白杨树就转弯走。走了一段时间&#xff0c;发现围的人越来越多&#xff0c;原来祝家庄以灯笼指挥号令。花荣一箭射下来红灯龙&#xff0c;伏兵自己就乱起来了…

简单控件属性设置

1、设置文本的内容 <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"…

计算机毕业设计分享-ssm心理咨询预约管理系统 19086(赠送源码数据库)JAVA、PHP,node.js,C++、python,大屏数据可视化等

本科生毕业设计&#xff08;论文&#xff09; 题 目心理咨询预约管理系统的设计与实现 学 院 XXXXX 专业班级 XXXXX 学生姓名 XXXX 指导岗位 XXXX 撰写日期&#xff1a;2023年4月 目 录 摘要 1 绪论 1.1背景及意义 …

【K8S类型系统】一文梳理 K8S 各类型概念之间的关系(GVK/GVR/Object/Schema/RestMapper)

参考 k8s 官方文档 https://kubernetes.io/zh-cn/docs/reference/https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/ 重点 Kubernetes源码学习-kubernetes基础数据结构 - 知乎 重点 Kubernetes类型系统 | 李乾坤的博客 重点 k8s源码学习-三大核心数…

Linux如何查看端口是否占用

在Linux中&#xff0c;有多种方法可以用来检查端口是否被占用。以下是一些常用的命令&#xff1a; netstat&#xff1a;这是一个非常通用的命令&#xff0c;可以用来查看所有端口的使用情况。如果你想查找特定的端口是否被占用&#xff0c;可以使用netstat命令配合grep。例如&…

QSlider 介绍与使用

1. 简单介绍一下QSlider 当使用 Qt 编写程序时&#xff0c;QSlider 是一个常用的控件&#xff0c;用于实现滑块功能。以下是 QSlider 的主要接口函数和信号&#xff1a; 接口函数&#xff1a; setMinimum(int min) / setMaximum(int max)&#xff1a; 设置滑块的最小值和最大…

Trie树(1.字符串统计____2.最大异或对求解)

Trie树 文章目录 Trie树Trie字符串统计正解 最大异或对1.暴力 &#xff08;可以过6/10个测试点)2. Trie树模拟 用法&#xff1a;高效地存储和查找字符串集合的数据结构 存储形式&#xff1a; 将n个单词各个字符进行枚举&#xff0c;若是&#xff08;根节点所指向包含字符c&…

第九讲-I/0 Interface

1. I/O接口 2. 程序查询I/O方式 3. 中断与中断l/O方式

即插即用篇 | YOLOv8 引入 DoubleAttention 注意力机制 | 《A2-Nets: Double Attention Networks》

论文名称:《A2-Nets: Double Attention Networks》 论文地址:https://arxiv.org/pdf/1810.11579.pdf 文章目录 1 原理2 源代码3 添加方式4 模型 yaml 文件template-backbone.yamltemplate-small.yamltemplate-large.yamltemplate-neck.yamlyolov8-C2

2024国际元宇宙博览会:阿里元境以元宇宙数字内容助力文旅数字化发展

2月26日&#xff0c;MES2024国际元宇宙博览会在深圳会展中心正式开幕&#xff0c;大会以“向3D出发&#xff0c;元宇宙来袭&#xff0c;电竞娱乐正当时”为主题&#xff0c;聚焦元宇宙产业链&#xff0c;以“汇聚企业创新&#xff0c;助力产业重构&#xff0c;推动行业发展”为…

【数学建模获奖经验】2023第八届数维杯数学建模:华中科技大学本科组创新奖获奖分享

2024年第九届数维杯大学生数学建模挑战赛将于&#xff1a;2024年5月10日08:00-5月13日09:00举行&#xff0c;近期同学们都开始陆续进入了备赛阶段&#xff0c;今天我们就一起来看看上一届优秀的创新奖选手都有什么获奖感言吧~希望能帮到更多热爱数学建模的同学。据说点赞的大佬…

数据库题专题训练(包含专题讲解+一整套题) 复试+升本比较适合

【拯救者】数据库题专题训练(包含专题讲解一整套题) 复试专升本期末 更新中 1️⃣ 先讲对应的专题 2️⃣ 最后来一整套模拟考题 &#x1f357;提供文档下载 【拯救者】Ep1_ER图专题(上) ​ ⭐️ 画ER图: 关注三个点 1. 找实体 2.找实体的属性 3.找实…

yolo目标检测实战

该博客主要介绍了&#xff1a; 1. 如何制作yolo目标检测数据集 2.如何在自己的数据集上训练yolo 3.训练好后的模型如何进行推理 1.数据标注 关于数据如何标注&#xff0c;请查看这篇博文 2.数据集目录结构 重点关注红框内部的结构 images: 图片目录 images/train: 训练集…

mysql根据指定顺序返回数据--order by field

在查询数据的时候&#xff0c;在in查询的时候&#xff0c;想返回的数据根据 in里的数据顺序返回&#xff0c;可以直接在orderby中通过 FIELD(字段名称逗号分隔的值的顺序) 进行指定&#xff1b;示例没有加 order by field添加 order by field效果

边缘智能网关:让环境监测更智能

在环境监测领域&#xff0c;边缘智能网关可用于区域环境的实时监测、分析和预警&#xff0c;例如河湖水位监测、雨雪监测、风沙/风速监测&#xff0c;通过实时采集并分析环境变化数据&#xff0c;能够有助于对于突发、急发的各种自然灾害进行快速预警和应对。 一、边缘智能网关…

JeecgBoot3.6.1 中打印功能实现

JeecgBoot3.6.1中实现打印功能 前言 在项目开发中我们可能会遇到打印得需求&#xff0c;基于JeecgBoot框架做了如下案例 一、前端 1、vue页面代码&#xff1a;List.vue <template><BasicTable register"registerTable" :rowSelection"rowSelectio…

【RT-Thread基础教程】邮箱的使用

文章目录 前言一、邮箱的特性二、邮箱操作函数2.1 创建邮箱创建动态邮箱创建静态邮箱 2.2 删除邮箱2.3 发邮件2.4 取邮件 三、示例代码总结 前言 RT-Thread是一个开源的实时嵌入式操作系统&#xff0c;广泛应用于各种嵌入式系统和物联网设备。在RT-Thread中&#xff0c;邮箱是…