IPC之二:使用命名管道(FIFO)进行进程间通信的例子

news2024/10/7 10:14:49

IPC 是 Linux 编程中一个重要的概念,IPC 有多种方式,本文主要介绍命名管道(FIFO),命名管道可以完成同一台计算机上的进程之间的通信,本文给出了多个具体的实例,每个实例均附有完整的源代码;本文所有实例在 Ubuntu 20.04 上编译测试通过,gcc版本号为:9.4.0;本文适合 Linux 编程的初学者阅读。

1 命名管道(FIFO)的基本概念

  • FIFO(First-In First-Out),在 Linux 文件系统中是一种特殊的文件,相对于匿名管道,FIFO 又被称为命名管道;

  • 在我的另一篇文章《IPC之一:使用匿名管道进行父子进程间通信的例子》中,介绍了管道(又称匿名管道)的基本概念;

  • 匿名管道和命名管道本质上是一种东西,在内核中都是一个 inode,只是因为匿名管道在物理文件系统中没有文件故而仅能通过进程间的继承来传递这个 inode 的索引号;而命名管道因为有了物理文件系统中的文件,通过这个文件可以找到内核中相应的 inode,故而对该文件有读写权限的进程均可以使用这个管道;

  • 命名管道的工作方式与匿名管道非常相似,但有一些明显的差异:

    1. 命名管道作为一种特殊文件存在于文件系统中,匿名管道仅存在于内核中,不存在于文件系统中;
    2. 不同祖先的进程可以通过命名管道交换数据,而匿名管道只能在有共同祖先的进程间进行通信;
    3. 命名管道的生命周期可以大于使用它的进程的生命周期,也就是说,当使用命名管道交换数据的所有进程都终止后,命名管道仍然可以保留在文件系统中供以后使用;匿名管道则不同,当使用匿名管道的进程全部终止后,匿名管道也会消亡。
  • 当进程通过命名管道交换数据时,内核会在内部传递所有数据,而不将其写入文件系统;因此,表示一个命名管道的特殊文件在文件系统上没有内容;文件系统中表示命名管道的文件名仅充当参考点,以便进程可以使用文件系统中的文件名找到内核中的 inode,从而访问管道。

  • 每个 FIFO 特殊文件代表着一个命名管道,由内核进行维护,必须先打开 FIFO 两端(读和写),然后才能传递数据;一般情况下,打开 FIFO 会产生阻塞,直到另一端也打开为止,后面我们会专门讨论 FIFO 的阻塞问题;

  • 进程也可以以非阻塞模式打开 FIFO;

    • 在使用非阻塞方式打开 FIFO 时,即便没有进程打开 FIFO 的写入端,一个进程以只读方式打开 FIFO 也会成功;
    • 即便是以非阻塞方式打开 FIFO,如果没有进程打开 FIFO 的读出端,以只写方式打开 FIFO 也会失败,产生错误:No such device or address
  • 以读写方式打开 FIFO 不管是在阻塞还是非阻塞方式下都会成功,其返回的文件描述符既是写入端又是读出端,以这种方式打开的 FIFO,既可以做写入端使用,也可以做读出端使用,但通常只能使用其一端(要么写入要么读出)。

2 在 shell 下建立一个命名管道(FIFO)

  • 有多种方法可以创建命名管道,其中下面两种方法可以直接在 shell 中创建命名管道;

  • 使用 mknod 命令建立命名管道

    mknod myfifo p
    
    • mknod 命令用于创建一个特殊文件,命令最后的参数 “p”,表示建立一个 FIFO 特殊文件,这个参数还可以是 b、c、u,分别表示其它的特殊文件;

    • 可以使用在线手册 man mknod 了解 mknod 命令的详细信息;

    • 使用 ls -l myfifo 可以查看我们刚刚创建的 FIFO,文件权限最前面的 “p”,表示这个文件是个命名管道 FIFO;

      Screenshot for ls fifo

    • 使用 mknod 创建 FIFO 时,可以使用参数 -m 权限 来设置权限,比如:mknod -m 0644 myfifo p 命令,创建的 FIFO 只有拥有者可读写,其它用户只读;

  • 使用 mkfifo 命令建立命名管道

    mkfifo myfifo
    
    • mkfifo 命令用于创建一个 FIFO 特殊文件;
    • 可以使用在线手册 man mkfifo 了解 mkfifo 命令的详细信息;
    • 使用 mkfifo 创建 FIFO 时,可以像 mknod 命令一样使用参数 -m 权限 来设置权限,比如:mkfifo -m 0666 myfifo 命令,创建一个 FIFO,所有用户可读写;
  • mknodmkfifo 创建的 FIFO 可以用 chmod 修改权限

    mkfifo -m 0644 myfifo
    chmod a=rw myfifo
    
    • mkfifo 创建了一个 FIFO,chmod 将其权限改为所有用户可读写。
  • mknodmkfifo 创建的 FIFO 可以用 rm 删除

    rm myfifo
    

3 使用C语言创建命名管道

  • 使用系统调用 mknod() 创建命名管道

    原型:#include <sys/types.h>
         #include <sys/stat.h>
         #include <fcntl.h>
         #include <unistd.h>
    
         int mknod(const char *pathname, mode_t mode, dev_t dev);
    说明:mknod() 可以在文件系统上建立一个文件节点(普通文件、特殊文件、命名管道)
    返回:调用成功返回 0
         调用失败返回 -1
         errno = EFAULT (pathname invalid)
                 EACCES (permission denied)
                 ENAMETOOLONG (pathname too long)
                 ENOENT (invalid pathname)
                 ENOTDIR (invalid pathname)
                 (其它错误码请查看 man 手册)
    
    • pathname 是文件路径不用多说

    • mode 是文件类型和文件权限,比如:S_IFIFO | 0666 表示建立一个 FIFO 特殊文件,权限为 0666 (所有用户可读写)

    • dev 是设备号,只有当文件类型为 S_IFCHR 或 S_IFBLK 时,才有设备号,其他文件类型填 0 即可;

    • 在线手册 man 2 mknod 可以查询该系统调用的详细信息

    • 下面的调用创建了一个 FIFO 文件:/tmp/myfifo,权限为:0666 (所有用户可读写)

      mknod("/tmp/myfifo", S_IFIFO | 0666, 0);
      
    • 但实际上设置的文件权限还要受到 umask 的影响,最终的文件权限是 0666 & ~umask

    • 可以使用 shell 命令 umask 查看当前的 umask

      Screenshot of umask

    • 为了保证创建的 FIFO 文件的权限,可以在调用 mknod() 前临时删除 umask

      umask(0);
      mknod("/tmp/myfifo", S_IFIFO|0666, 0);
      
  • 使用 mknodat() 创建命名管道

    原型:#include <fcntl.h>           /* Definition of AT_* constants */
         #include <sys/stat.h>
    
         int mknodat(int dirfd, const char *pathname, mode_t mode, dev_t dev);
    
    • mknod() 相比,这个系统调用多了一个参数 dirfddirfd 是文件系统中一个打开的目录的文件描述符;
    • 当 pathname 为绝对路径时,参数 dirfd 被忽略,该调用与 mknod() 完全一样;
    • 当 pathname 为相对路径时,它是相对于 dirfd 所引用的目录,而不是相对于调用进程的当前工作目录;
    • mknod() 中的 pathname 也是可以使用相对路径的,但是相对于调用进程时的当前目录;
    • 下面代码使用 mknodat() 创建了一个 FIFO 文件:
      int dirfd = open("/tmp", O_RDONLY);
      mknodat(dirfd, "./myfifo", S_IFIFO|0666, 0); 
      
  • 使用系统调用 mkfifo() 创建命名管道

    原型:#include <sys/types.h>
         #include <sys/stat.h>
    
         int mkfifo(const char *pathname, mode_t mode);    
    
    • 毫无疑问,mkfifo() 就是专门用来创建 FIFO 文件的,pathname 为 FIFO 文件路径,mode 为文件权限,当然这个权限还要受到 umask 的影响,请参考前面的说明。
  • 使用系统调用 mkfifoat() 创建命名管道

    #include <fcntl.h>           /* Definition of AT_* constants */
    #include <sys/stat.h>
    
    int mkfifoat(int dirfd, const char *pathname, mode_t mode);    
    
    • mkfifoat()mkfifo() 的关系与 mknodat()mknod() 的关系一样,请参考 mknodat() 的说明

4 命名管道(FIFO)的实例

  • 在命名管道上的 I/O 操作与匿名管道是一样的,使用 read() 从管道中读出数据,使用 write() 向管道中写入数据,不能使用 lseek() 等;

  • 有一个明显的区别是在对命名管道进行 I/O 操作之前,必须用 open() 打开 FIFO 文件,这在匿名管道上是不需要的,因为匿名管道仅存在于内核中,并不存在于物理的文件系统中,而命名管道是存在于物理文件系统中的;

  • 由于 FIFO 文件存在于文件系统中,所以,对命名管道也可以使用流式文件的 I/O 操作,即:

    • fopen() 打开 FIFO 文件
    • 使用 fgetc()/fputc()、fgets()/fputs() 进行读写操作;或者
    • 使用 fscanf()/fprintf() 进行读写操作;或者
    • 使用 fread()/fwrite() 进行读写操作;
    • fclose() 关闭 FIFO 文件
  • 下面是一个使用 FIFO 传递消息的实例,分为两个独立的程序

    • 服务端源程序:fifo-server.c(点击文件名下载源程序),从 FIFO 上接收数据
    • 客户端源程序:fifo-client.c(点击文件名下载源程序),向 FIFO 中写入消息
    • 这个实例中使用 fopen() 打开 FIFO,客户端使用 fputs() 向 FIFO 中写入数据,服务端使用 fgets() 从 FIFO 中读出数据,使用 fclose() 关闭 FIFO;
    • 也可以使用 open()/write()/read()/close() 这组系统调用来进行 FIFO 的操作;
    • 这个实例使用 mknod() 创建 FIFO,当然也可以使用 mkfifo() 创建 FIFO;
    • 其实这个实例中也可以不使用 fifo-client 这个程序,我们可以在 shell 下使用 echo "Hello World!">myfifo 来代替 fifo-client 完成向 FIFO 发送信息的功能;
    • fifo-client 程序演示了使用流式文件向 FIFO 中写入数据的方法,特别要注意的是程序最后的 fflush(),在本例中,fflush() 并不是必须的,因为紧接着就使用了 fclose(),但在很多情况下,如果不使用 fflush(),数据会被缓存起来,不会立即发送出去。
  • FIFO 这种特殊文件有些地方确实特殊:

    • 从 FIFO 中读数据的进程在打开 FIFO 时会阻塞,直到有一个向这个 FIFO 中写数据的进程打开 FIFO;
    • 向 FIFO 中写数据的进程在打开 FIFO 时会阻塞,直到有一个从这个 FIFO 中读数据的进程打开 FIFO;
    • 进程从 FIFO 中读数据时会阻塞,直到有进程向 FIFO 中写入数据;
    • 向一个没有读出端的 FIFO 中写入数据时,会产生错误;

      这种情况通常发生在 FIFO 两端正常打开后,读出端关闭,导致管道没有读出端

  • 编译运行

    • 编译
      gcc -Wall fifo-server.c -o fifo-server
      gcc -Wall fifo-client.c -o fifo-client
      
    • 这个实例需要在两个终端中运行,一个终端中运行 ./fifo-server;另一个终端中运行 ./fifo-client "Hello world!"
    • 初次运行时,要先运行 ./fifo-server 再运行 ./fifo-client,因为 FIFO 文件是在 fifo-server 程序中创建的;
    • fifo-server 不会自行退出,需要在键盘上输入 ctrl+c 才能退出;
    • fifo-client 可以在 shell 下使用命令:echo "Hello world!">myfifo 来代替。
  • fifo-server 的运行截图:

    Screen of fifo-server


5 命名管道(FIFO)的阻塞机制

  • 打开 FIFO 有三种方式:只读方式、只写方式和读写方式

  • 使用阻塞方式(没有 O_NONBLOCK 标志)打开 FIFO

    • 以只读方式(O_RDONLY)打开 FIFO 的读出端会发生阻塞,直至 FIFO 写入端被其它进程打开;
    • 以只写方式(O_WRONLY)打开 FIFO 的写入端会发生阻塞,直至 FIFO 读出端被其它进程打开;
    • 以读写方式(O_RDWR)方式打开 FIFO 会直接返回成功,因为打开的 FIFO 描述符既可作读出端,也可以作写入端,相当于打开了 FIFO 的两端;
    • 以只读方式(O_RDONLY)成功打开的 FIFO,在读出数据时:
      • 如果写入端打开,则发生阻塞,直到写入端将数据写入 FIFO 后,返回读出的数据长度(字符数);
      • 如果写入端关闭,则立即返回 EOF(读出数据长度为 0);
    • 以只写方式(O_WRONLY)成功打开的 FIFO,向 FIFO 写入数据时:
      • 如果读出端打开,则会直接返回成功,读出端可以正常读出数据;
      • 如果读出端关闭,则出现错误,并产生 SIGPIPE 信号。
  • 使用非阻塞方式(设置 O_NONBLOCK 标志)打开 FIFO

    • 以只读方式(O_RDONLY)打开 FIFO 时不会阻塞,返回成功;
    • 以只写方式(O_WRONLY)打开 FIFO 时不会阻塞:
      • 如果 FIFO 的读出端没有打开,直接返回失败;
      • 如果 FIFO 的读出端已经打开,直接返回成功
    • 从以只读方式(O_RDONLY)打开的 FIFO 中读取数据不会阻塞:
      • FIFO 的写入端已打开,但 FIFO 中没有数据时,会产生错误:Resource temporarily unavailable
      • FIFO 的写入端已打开,且 FIFO 中有数据时,返回读出数据的长度(字节数);
      • 当 FIFO 写入端关闭时,返回 EOF(读出数据长度为 0)。
    • 向以只写方式(O_WRONLY)打开的 FIFO 写入数据时不会阻塞,立即返回:
      • 当 FIFO 读出端关闭时,写入数据时出错,并产生 SIGPIPE 信号;
      • 当 FIFO 读出端打开时,写入成功,写入的数据可以被读出端正常读出。
  • 这种教条式的说明其实很枯燥,建议读者编写一些测试程序来体会上面的“教条”。

6 SIGPIPE 信号和 EOF

  • 在上一节的“教条”中,我们提出过两个要点:

    1. 当一个向 FIFO 写入数据的进程将写入端关闭时,正在等待从 FIFO 中读取数据进程会收到一个 EOF;
    2. 当一个从 FIFO 读出数据的进程将读出端关闭时,向 FIFO 中写入数据的进程在写入数据时系统会发出一个 SIGPIPE 信号。
  • 这一节专门讨论与 FIFO 有关的 SIGPIPE 信号和 EOF(End Of File);

  • 在 Linux 的信号集有一个 SIGPIPE 信号是和 FIFO 相关的;

  • 只有在向 FIFO 写入数据时才会产生 SIGPIPE 信号;也就是说,虽然读出端关闭,但如果不向 FIFO 中写入数据,也不会产生 SIGPIPE 信号;

  • 写入数据时,write() 也会返回错误,所以,通常情况下并不一定要捕捉 SIGPIPE 信号;

  • 源程序:fifo-sigpipe.c(点击文件名下载源程序)演示了在向 FIFO 写入数据时,如何截获 SIGPIPE 信号;

    • 编译:gcc -Wall fifo-sigpipe.c -o fifo-sigpipe
    • 运行这个程序需要两个终端窗口,在第一个终端上运行:./fifo-sigpipe,程序运行后将被阻塞在 open() 上,在第二个终端上运行:cat myfifo,来模拟从 FIFO 中读取数据;
    • 我们可以看到第一个终端中由 fifo-sigpipe 每 5 秒一次发出的信息可以在第二个终端上收到并显示出来,此时我们在第二个窗口中输入 ctrl+c 终止 cat 的运行,则在第一个窗口中可以看到截获到的 SIGPIPE 信号;
    • ./fifo-sigpipe 是 FIFO 的写入端,cat fifo-sigpipe 是 FIFO 的读出端,终止读出端,然后再次运行 cat fifo-sigpipe,可以继续收到 fifo-sigpipe 发出的信息。
  • EOF 是读文件时常用的判断文件结束的方法,在 FIFO 上表示写入端被关闭:

    • 以阻塞方式打开 FIFO 进行读操作时,EOF 表示写入端已被关闭;
    • 以阻塞方式打开 FIFO 进行读操作时,如果写入端打开但 FIFO 中没有数据,则会产生阻塞;
    • 以非阻塞方式打开 FIFO 时,写入端关闭时,读出端会返回 EOF(读出数据长度为 0);
    • 以非阻塞方式打开 FIFO 时,写入端打开但 FIFO 中没有数据时,读出端不会返回 EOF,会产生错误:Resource temporarily unavailable
  • 源程序:fifo-eof.c(点击文件名下载源程序)演示了在从 FIFO 中读数据时,如何截获 EOF;

    • 编译:gcc -Wall fifo-eof.c -o fifo-eof
    • 运行这个程序需要两个终端窗口,在第一个终端上运行:./fifo-eof,程序运行后将被阻塞在 open() 上,在第二个终端上运行:echo "Hello world">myfifo,来模拟向 FIFO 写入数据;
    • 当第二个终端的命令启动后,第一个终端上的 fifo-eof 程序会收到第二个终端的命令发来的数据,然后,第二个终端命令执行完毕退出,相当于关闭了 FIFO 的写入端,此时,可以在第一个终端的 fifo-eof 程序上看到捕捉到的 EOF;
    • ./fifo-eof 是 FIFO 的读出端,echo "Hello world">myfifo 是 FIFO 的写入端,终止写入端,然后再次运行 echo "Hello world">myfifofifo-eof 可以再次从 FIFO 中收到信息;
    • 不管在打开 FIFO 时是阻塞状态还是非阻塞状态,当 FIFO 的写入端关闭时,读出端都会收到 EOF。

7 使用流式文件操作 FIFO 的一些差异

  • 使用流式文件的操作函数(fopen()/fread()/fwrite()/fclose()等)是可以操作 FIFO 的,这个在前面的实例中已经有演示,但是与低级文件I/O的系统调用比是有一些差异的,本节将讨论已经发现的一些差异;
  • 使用流式文件的方式从 FIFO 中读出数据时,如果遇到 EOF,必须要关闭 FIFO,然后再次打开,才能继续从 FIFO 中读取数据;
    • fifo-server.c 这个实例中,演示了流式文件方式从 FIFO 中读取数据时,遇到 EOF 需要再次打开 FIFO 的过程;
    • 读者可以尝试修改这个程序,在遇到 EOF 后不关闭 FIFO,继续读取数据,是读不出数据的;
    • 但是使用低级文件 I/O (open()/read()/write()/close())操作 FIFO,在遇到 EOF 时,则不需要关闭 FIFO;
    • fifo-eof.c 这个实例中,演示了使用低级文件 I/O 从 FIFO 中读取数据时,遇到 EOF 无需关闭 FIFO 的过程;
  • 当使用流式文件的方式向 FIFO 中写入数据时,因为流式文件缓存的原因,写入的数据可能不会马上进入到 FIFO 中;
    • 如果希望写入的数据立即进入 FIFO 的话,应该在调用写入函数(fputs()/fwrite()等)之后,立即调用 fflush(),该函数的意义在于强制将缓冲区中的数据写入到指定的流式文件中;
    • 使用低级文件 I/O 向 FIFO 中写入数据则没有这个问题;
  • 当使用流式文件的方式向 FIFO 中写入数据时,如果 FIFO 的读出端关闭,写入函数仍然会返回成功;
    • 尽管函数调用返回成功,但向 FIFO 写入的数据会被忽略,再次打开的读出端无法读出这些数据;
    • 和使用低级文件 I/O 写入 FIFO 时一样,会捕捉到一个 SIGPIPE 信号,所以使用流式文件的方式向 FIFO 写入数据时,要靠捕捉 SIGPIPE 信号来判断写入失败;
    • 使用低级文件 I/O 写入 FIFO 时,如果 FIFO 读出端已关闭,写入函数(write())会报告错误,这使得我们可以考虑不去捕捉 SIGPIPE 信号。

8 后记

  • 本文涉及的所有观点均编有测试程序进行测试,限于篇幅,不能都列在文章中;
  • 以本文提供的四个范例已经足够衍生出很多的测试程序:
    • fifo-server.c - 是使用流式文件的方式从 FIFO 中读数据的,可以尝试将 fopen()/fclose() 放在循环外面,可以验证使用流式文件的方式读取 FIFO 时遇到 EOF 需要重新打开 FIFO 的观点;
    • fifo-server.c - 可以将这个程序改成使用低级文件 I/O 读取 FIFO 的方式,以比较流式文件的操作函数和低级文件 I/O 的操作函数之间的差异;
    • fifo-client.c - 可以加一个循环,使程序每隔 5 秒向 FIFO 写入一次数据,同时去掉 fflush(),可以验证流式文件的缓存对 FIFO 写入的影响;
    • fifo-client.c - 可以将其改成使用低级文件 I/O 写入 FIFO 的方式,以比较流式文件的操作函数和低级文件 I/O 的操作函数之间的差异;
    • fifo-sigpipe.c - 可以将其改为使用流式文件方式向 FIFO 写入数据,验证一下在读出端关闭时,fputs() 仍然返回成功的情况,同时体会一下如何在流式文件方式下捕捉 SIGPIPE 信号;
  • 本质上说,命名管道和匿名管道在内核中的实现基本是一样的,所以,命名管道其实也是半双工的,要建立一个全双工的命名管道只需建立两条管道即可;可以参考我的另一篇介绍匿名管道的文章《IPC之一:使用匿名管道进行父子进程间通信的例子》
  • 命名管道如果不从文件系统中删除,会一直存在下去,这点和匿名管道有很大的区别,删除一个命名管道和删除一个普通文件无异;
  • 为了让所有进程都可以访问到建立的命名管道,通常会把一个命名管道建在 /tmp/ 下,好处有两点:
    1. 这个目录的权限是 0777,所有的用户可进行读写;
    2. 这个目录是一个内存文件系统,并不在物理磁盘上,所以重新启动后你建立的命名管道也会消失,减少了维护。
  • 与匿名管道一样,Linux 下命名管道的缓冲区长度也是有限制的,最大长度为 4096 字节,定义在头文件:linux/limits.h 中:
    #define PIPE_BUF        4096	/* # bytes in atomic write to a pipe */
    
  • 向 FIFO 中写入数据的长度不超过这个阈值,将是一个原子操作。

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

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

相关文章

axios接受文件流并下载

需求场景 前端发送请求&#xff0c;后端传回文件流&#xff0c;前端接受到后立刻打开下载窗口下载文件 注意事项 请求api需要添加&#xff1a;responseType:blob&#xff0c; axios拦截器拦截错误状态码 (假设是code) 那里的if从res.code ! 200改为res.code && res.…

SSD202D-kernel-uimage后面加入dtb

情况是这样的,由于我们这边烧录的是uImage.xz 是经过压缩的uimage文件,涉及到解码,boot获取dtb会需要解码,解码不知为何会延时十几秒等待 这是万万不能的,于是就使用了别的方法就把dtb放到kernel的空间多余的地方,这样只要能读到即可 于是我开始了设计方法 可以看到这个dtb的…

以mod_jk方式整合apache与tomcat(动静分离)

前言&#xff1a; 为什么要整合apache和tomcat apache对静态页面的处理能力强&#xff0c;而tomcat对静态页面的处理不如apache&#xff0c;整合后有以下好处 提升对静态文件的处理性能 利用 Web 服务器来做负载均衡以及容错 更完善地去升级应用程序 jk整合方式介绍&#…

Sqlserver还原数据库为另外的名字

Sqlserver还原数据库为另外的名字 在工作中需要还原数据库的时候原来的数据库不变&#xff0c;而是需要还原成一个新的数据库 1、备份test数据库 2、新建一个test1数据库 3、设置test1数据库文件的权限 右键这两个文件->属性 4、在test1上还原数据库 5、数据库还原配置…

比特鹏哥5-数组【自用笔记】

比特鹏哥5-数组【自用笔记】 1.数组的概念2.一维数组的创建和初始化创建的语句结构初始化的语句结构 3.一维数组的使用数组的下标&#xff1a;从0开始&#xff0c;n个数组&#xff0c;最后一个的下标是n-1 4.一维数组在内存中的存储5.sizeof计算数组元素个数可以计算元素个数并…

守住L2?争夺高阶智驾?留给外资Tier1的时间不多了!

14.08%&#xff0c;这是2023年1-6月中国市场&#xff08;不含进出口&#xff09;乘用车前装标配L2&#xff08;含L2&#xff09;搭载中国本土系统解决方案的份额占比。而在NOA等高阶赛道&#xff0c;中国本土势力已经占据上风。 这个过去一直被外资Tier1垄断的智能化细分市场&a…

机器学习深度学习——循环神经网络RNN

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习—语言模型和数据集 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助…

JDBC处理批量数据提高效率

文章目录 0 说明1 如何使用jdbc操作数据库1.1 加载数据库驱动1.2 建立数据库连接1.3 创建Statement或者PreparedStatement用来执行SQL1.4 开始执行SQL语句1.5 处理结果集1.6 关闭连接1.7 完整代码 2 批量操作数据库3 如何打印SQL语句4 jdbc常用开源类库5 获取自增id6 获取数据源…

【CSS】网格布局(简单布局、网格合并、网格嵌套)

文章目录 CSS网格布局&#xff08;Grid Layout&#xff09;1. 简单布局2. 网格合并3. 网格嵌套4. 总结 CSS网格布局&#xff08;Grid Layout&#xff09; CSS网格布局&#xff08;Grid Layout&#xff09;是一种强大且灵活的CSS布局系统&#xff0c;允许开发者以网格形式组织和…

快乐的马里奥(广搜入门)

题面 题目描述 马里奥是一个快乐的油漆工人&#xff0c;这天他接到了一个油漆任务&#xff0c;要求马里奥把一个 n 行 m 列的矩阵每一格都用油漆标记一个数字&#xff0c;标记的顺序按照广度优先搜索的方式进行&#xff0c;也就是他会按照如下方式标记&#xff1a; 1、首先标记…

基于springboot+vue的房屋租赁系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

云原生之使用Docker部署homarr个人导航页

云原生之使用Docker部署homarr个人导航页 一、homarr介绍1.1 homarr简介1.2 homer特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载homarr镜像五、部署homarr导航页5.1 …

彩虹云商城搭建完整教程 完整的学习资料

彩虹云商城搭建完整教程 完整的学习资料提供给大家学习 随着电子商务的快速发展&#xff0c;越来越多的企业开始意识到开设一个自己的电子商城对于销售和品牌推广的重要性。然而&#xff0c;选择一家合适的网站搭建平台和正确地构建一个商城网站并不是一件容易的事情。本文将为…

塔矢行洋对战藤原佐为,谁才是最接近神之一手的人

大家好, 我是嘉宾, 今天我们来盘点一下古今第一高手对局 &#xff0c;塔矢行洋对战藤原佐为&#xff0c;谁才是最接近神之一手的人&#xff0c; 在所有设定都点击好之后, 塔矢行洋下出了自己的第一步 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 佐…

C语言内嵌汇编

反编译&#xff08;二进制文件或者so库&#xff09; objdump --help objdump -M intel -j .text -ld -C -S out > out.txt #显示源代码同时显示行号, 代码段反汇编-M intel 英特尔语法-M x86-64-C:将C符号名逆向解析-S 反汇编的同时&#xff0c;将反汇编代码和源代码交替显…

C++利用mutex和thread实现一个死锁

程序 #include<iostream> #include<mutex> #include<thread> using namespace std; mutex mtx1; mutex mtx2; void A(){mtx1.lock();cout<<"a:mtx1"<<endl;this_thread::sleep_for(chrono::milliseconds(1000));mtx2.lock();cout<…

《Java-SE-第三十一章》之网络编程

前言 在你立足处深挖下去,就会有泉水涌出!别管蒙昧者们叫嚷:“下边永远是地狱!” 博客主页&#xff1a;KC老衲爱尼姑的博客主页 博主的github&#xff0c;平常所写代码皆在于此 共勉&#xff1a;talk is cheap, show me the code 作者是爪哇岛的新手&#xff0c;水平很有限&…

右值引用与移动语义与完美转发

右值引用 右值 什么是右值&#xff0c;没有地址临时数据的我们称之为右值 我们无法对10、aa、字符串取地址的值我们称之为右值。因为他们是临时数据&#xff0c;并不保存再内存中&#xff0c;所以我们右值没有地址&#xff0c;也无法被赋值&#xff08;除const外&#xff0c;左…

【Elasticsearch】学好Elasticsearch系列-分词器

本文已收录至Github&#xff0c;推荐阅读 &#x1f449; Java随想录 先看后赞&#xff0c;养成习惯。 点赞收藏&#xff0c;人生辉煌。 文章目录 规范化&#xff1a;normalization字符过滤器&#xff1a;character filterHTML Strip Character FilterMapping Character FilterP…

IMV6.0

一、背景 经历了多个版本&#xff0c;基础内容在前面&#xff0c;可以使用之前的基础环境&#xff1a; v1&#xff1a; https://blog.csdn.net/wtt234/article/details/132139454 v2&#xff1a; https://blog.csdn.net/wtt234/article/details/132144907 v3&#xff1a; https…