【Linux】进程间通信——管道

目录

写在前面的话

什么是进程间通信

为什么要进行进程间通信

进程间通信的本质理解

进程间通信的方式

管道

System V IPC

POSIX IPC

管道

什么是管道

 匿名管道

什么是匿名管道

匿名管道通信的原理

pipe()的使用

匿名管道通信的特点

拓展代码

 命名管道

什么是命名管道

命名管道通信的原理

mkfifo的使用

代码模拟命名管道通信过程


写在前面的话

        本章是首次提出进程间通信的概念,所以会先介绍进程间通信的相关概念,以及整体的框架结构。

        而本文重点是先介绍进程间通信的基本概念,然后重点介绍进程间通信的第一种方式:管道。

什么是进程间通信

        进程间通信(Inter-Process Communication,IPC)是指操作系统或计算机系统中,不同进程之间进行数据交换和通信的机制或技术。由于进程是操作系统中独立运行的程序实例,而进程间通信允许这些独立的进程之间相互协作、共享资源和进行数据交换。

为什么要进行进程间通信

        根据我们前面讲的,进程间是相互独立的,进程具有独立性啊,那通信不就不独立了吗?

    答案是正确的,进程通信的确会破坏进程的完全独立性,因为进程通信的目的是为了实现进程之间的数据共享、同步和协作。通过进程通信,各个进程可以相互交互和共享资源,这意味着它们不再完全独立,而是具有一定的相互依赖性和关联性。

        尽管进程通信破坏了进程的完全独立性,但这种破坏是有意义且必要的。在实际的计算机系统和操作系统中,进程往往需要协同工作、共享资源和交换数据才能完成复杂的任务。进程通信提供了一种机制,使得不同进程之间可以进行必要的协作和交流,并提供了相应的同步和保护机制来确保数据的正确性和一致性。

        所以这是一种权衡和折中的方案,但大部分情况下进程是相互独立的。


综上,进程间通信主要是为了完成下面这些作用:

  • 数据传输:一个进程需要将它的数据发送给另一个进程。
  • 资源共享:多个进程之间共享同样的资源(包括本地共享和远程资源共享)。
  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

进程间通信的本质理解

        1.我们知道进程具有独立性,是通过虚拟地址空间 + 页表映射的方式来保持独立性的,所以通信起来成本会比较高。

        2.既然通信,那么前提是一定要让不同的进程看到同一块“内存”(特定的结构组织),这块"内存"不能隶属于任何一个进程,而更应该强调共享

进程间通信不是目的,而是手段!


进程间通信的方式

        大体上可以分为3种通信方式:

  • 管道

    • 匿名管道pipe
    • 命名管道
  • System V IPC

    • System V消息队列
    • System V共享内存
    • System V信号量

System V只能用于单机通信(本地通信).

  • POSIX IPC

    • 消息队列
    • 共享内存
    • 信号量
    • 互斥量
    • 条件变量
    • 读写锁

POSIX IPC可以在单机通信的基础上,进行网络通信(远程资源共享)。

        以上所提到的方式,我会在后面的章节逐一讲解,这个是进程间通信的方式.

今天我们将首要讲解进程间通信方式(一)——管道.

管道

什么是管道

        管道(Pipe)是一种进程间通信机制,用于在相关进程之间传输数据。它是一种特殊的文件描述符,它可以连接一个进程的输出(写入端)到另一个进程的输入(读取端),从而使得这两个进程可以通过管道进行数据传输。

        也就是说管道是单向传输的!现实生活中,我们所看听到的天然气管道、石油管道基本上都是单向传输的.

 匿名管道

什么是匿名管道

匿名管道(Anonymous Pipe)是进程间通信的一种机制,用于在具有亲缘关系(例如父子进程)或共享同一终端的兄弟进程之间传输数据。

        匿名管道是一种单向的数据流通道,它可以用于在进程之间传递数据。通常,一个进程作为管道的写入端(称为管道写入端),将数据写入管道;另一个进程作为管道的读取端(称为管道读取端),从管道中读取数据。

        匿名管道的创建是通过系统调用 pipe() 来完成的。pipe的使用后面会讲。

匿名管道通信的原理

        管道通信的背后是进程之间通过管道进行通信。

        我们知道一个进程要运行,首先要加载到内存,然后创建一个task_struct结构体,里面会有一个files_struct结构体,然后这个结构体里又有一个fd_array[]数组,每个元素指向对应的文件struct_file,里面包含了文件内容等.

        此时我们fork之后的子进程会重新创建一份task_struct,内容继承父进程的,此时fd_array[]里的内容也被子进程继承,即父进程打开的文件 子进程也继承了下来。它们指向的文件是 相同的.

        假设父进程3号文件描述符是读取文件的,4号文件描述符也用来写入文件的,子进程继承以后,fd=3也是用来读取文件的,fd=4也是用来写入文件的

         此时我们想让父进程进行写入fd=4,子进程进行读取fd=3,所以父进程就要关闭读端fd=3,子进程关闭写端fd=4。

这样我们就做到了不同的进程看到了同一份资源(通过fork子进程),而且通过文件描述符的方式完成了进程间的单向通信。

综上,管道内部本质大体是如下流程:

        1.父进程分别以读写方式打开一个文件

        2.fork()创建子进程

        3.双方各自关闭不需要的文件描述符

整体图如下:

pipe()的使用

        既然我们知道了思路,那我们可以用代码来使用一下管道。

        首先,父进程如何使用读和写方式分别打开文件呢?

这里使用到了pipe函数,函数用法及原型如下:

        参数pipefd为输出型参数,我们提前在外部定义好数组,然后传入,结果就会保存在这个数组中,分别为pipefd[0],代表的是读端,pipefd[1],代表的是写端.

        第二步我们利用fork创建子进程。

        最后,子进程用来读取文件的内容,并关闭写端pipefd[1],父进程用来写入内容,同时关闭读端。

匿名管道通信的特点

一个小demo如下:

#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;int main()
{// 1.创建管道int pipefd[2] = {0}; // pipefd[0] :读端, pipefd[1] :写端int n = pipe(pipefd);assert(n != -1);#ifdef DEBUG#endif// cout << pipefd[0] << "  " << pipefd[1] << endl;// 2.创建子进程pid_t id = fork();assert(id != -1);if (id == 0){// 子进程// 3.构建单向通信的信道,父进程写入,子进程读取// 3.1关闭子进程不需要的fdclose(pipefd[1]);char buffer[1024];while (true){ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = 0;cout << "child get a message [" << getpid() << "] Father# " << buffer << endl;}}exit(0);}// 父进程// 构建单向通信的信道// 3.1 关闭父进程不需要的fdclose(pipefd[0]);string message = "我是父进程,我正在给你发消息";int count = 0;char send_buffer[1024];while (true){// 3.2 构建一个变化的字符串snprintf(send_buffer, sizeof(send_buffer), "%s[%d] : %d", message.c_str(), getpid(), count++);// 3.3 写入write(pipefd[1], send_buffer, strlen(send_buffer));// 3.4 sleepsleep(1);}pid_t ret = waitpid(id, nullptr, 0);assert(ret > 0);close(pipefd[1]);return 0;
}

然后此时我们编译运行 :

 可以看到,父进程写入的内容,子进程全部读到了。        

这里是静态图,看不出效果,这些信息其实是时隔1秒打印一条的.

        我们看只有子进程在读取,可子进程我们并没有加任何的sleep啊,理论上应该子进程一直打印才对啊。

        显示器是一个文件,而管道也是一个文件,父子进程同时向显示器 写入时,没有说一个进程会等另一个进程,而是各自打印各自的消息,互相干扰,这是缺乏访问控制。而管道文件提供了访问控制,使得子进程读取完得等待父进程写入才能读取。

        这样,如果我们让子进程读取先sleep 10秒,期间父进程每隔1秒写入,等10秒过后,子进程开始读取,但会把 父进程写入10次的文件的内容全部一下读出来。这说明写入次数和读取次数没有直接关系。即管道是面向字节流的,具体怎么读需要定制协议,后面会说,

        这里就针对与管道的特点做一些总结:

  • 管道是用来进行具有血缘关系的进程进行进程间通信 --- 常用于父子间通信
  • 管道具有通过让进程间协同,提供了访问控制
  • 管道提供的是面向字节流式的通信服务 --- 面向字节流 --- 通过定制协议实现
  • 管道是基于文件的,文件的生命周期是随进程的,即管道的生命周期也随进程的!
  • 管道是单向通信的,就是半双工通信的一种特殊方式.

        上面最后一条提到了半双工概念,这里来解释一下:

        半双工通信的双方只能在同一时间点单向的传输数据,即两个参与者不能同时发送和接收数据。在半双工通信中,通信双方必须交替使用共享的通信信道。例如,当一个人在对讲机上说话时,另一个人必须停止接收,然后才能回应。典型的半双工通信方式包括对讲机和卫星电台。

        全双工:全双工通信允许在同一时间点双向地传输数据。这意味着通信的两个参与者能够同时发送和接收数据,而不需要交替使用通信信道。在全双工通信中,通信双方可以同时进行发送和接收操作,彼此之间的数据传输互不干扰。例如,电话通话是一个典型的全双工通信场景,双方可以同时说话和倾听对方的声音。

         顺带总结一下管道的几种情况:

        a.写快,读慢,写满就不能再写了

        b.写慢,读快,管道没有数据时,读必须等待

        这两种是由访问控制提供的.

        c.写关,读继续读,会标识读到了文件结尾

        d.写继续写,读关,OS会终止写进程

拓展代码

        利用匿名管道的方式,创建多个子进程,然后父进程分别派发随机的任务,

        总代码流程是:父进程首先load()加载方法,然后for循环创建多个进程,每次创建完成后,该进程都要与父进程(pipefd[1])建立关联,以方便父进程管理这些子进程。

        其中每个子进程调用 waitCommand函数,会阻塞在read,等待着父进程的写入,然后父进程开始分发任务,当是对应的子进程时,子进程会执行对应的任务,然后继续while循环等待。

共两个文件,第一个文件ProcessPool.cc文件

#include <iostream>
#include <vector>
#include <ctime>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "Task.hpp"
using namespace std;#define PROCESS_NUM 5
int waitCommand(int waitFd, bool quit) // 如果对方不发任务就阻塞
{uint32_t command = 0;ssize_t s = read(waitFd, &command, sizeof(command));if (s == 0){quit = true;return -1;}assert(s == sizeof(command));return command;
}
void sendAndWakeup(pid_t who, int fd, uint32_t command)
{write(fd, &command, sizeof(command));cout << "main process: call process " << who << " execute " << desc[command] << " through " << fd << endl;
}int main()
{load();// pid : pipefdvector<pair<pid_t, int>> slots;// create multiple child processfor (int i = 0; i < PROCESS_NUM; i++){// 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);pid_t id = fork();assert(id != -1);// 让子进程读取if (id == 0){// 关闭写端close(pipefd[1]);// child processwhile (true){// 等命令bool quit = false;int command = waitCommand(pipefd[0], quit); // 如果对方不发任务就阻塞if (quit)break;// 执行对应的命令if (command >= 0 && command < handlerSize()){callbacks[command]();}else{cout << "非法 command" << endl;}}exit(1);}// father processclose(pipefd[0]);slots.push_back(pair<pid_t, int>(id, pipefd[1]));}// 父进程派发任务srand((unsigned long)time(nullptr) ^ getpid() ^ 2311156L);while (true){   //选择一个任务 int command = rand() % handlerSize();//选择一个进程,采用随机数的方式,选择进程来完成任务,随机数的方式负载均衡int choice = rand() % slots.size();// 把任务给指定的进程sendAndWakeup(slots[choice].first, slots[choice].second, command);sleep(1);int select;//以下是手动派发任务// int command = 0;// cout << "######################################" << endl;// cout << "1.show functions        2.send command" << endl;// cout << "######################################" << endl;// cout << "Please Select > ";// cin >> select;// if (select == 1)//     showHandler();// else if (select == 2)// {//     cout << "Enter your Command > ";//     // 选择任务//     cin >> command;//     // 选择进程//     int choice = rand() % slots.size();//     // 把任务给指定的进程//     sendAndWakeup(slots[choice].first, slots[choice].second, command);// }// else// {// }}// 关闭fd,结束所有进程for (auto &slot : slots){close(slot.second);}// 回收所有子进程for (auto &slot : slots){waitpid(slot.first, nullptr, 0);}return 0;
}

        第二个文件为Task.hpp文件,主要是包含了任务的加载及任务的执行方法。

        

#pragma once
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<unistd.h>
#include<functional>
using namespace std;typedef function<void()> func;vector<func> callbacks;
unordered_map<int,string> desc;void readMySQL()
{cout << "sub process[" << getpid() << "] 执行数据库被访问的任务\n" << endl;
}
void executeURL()
{cout << "sub process[" << getpid() << "] 执行url解析任务\n" << endl;
}
void cal()
{cout << "sub process[" << getpid() << "] 执行加密任务\n" << endl;
}
void save()
{cout << "sub process[" << getpid() << "] 执行数据持久化\n" << endl;
}void load()
{desc.insert({callbacks.size(),"readMySQWL:读取数据库"});callbacks.push_back(readMySQL);desc.insert({callbacks.size(),"executeURL:解析URL"});callbacks.push_back(executeURL);desc.insert({callbacks.size(),"cal:进行加密计算"});callbacks.push_back(cal);desc.insert({callbacks.size(),"save:进行数据的文件保存"});callbacks.push_back(save);
}
void showHandler()
{for(auto& iter: desc){cout << iter.first << "\t" << iter.second << endl;}
}
int handlerSize()
{return callbacks.size();
}

 

这样每次父进程都会随机给子进程派发随机的任务:

 

 命名管道

         与匿名管道不同,命名管道不需要亲缘关系的进程之间,也不需要共享同一终端。任意进程可以通过打开命名管道的读取端和写入端来与其进行通信。

什么是命名管道

命名管道(Named Pipe)是一种独立进程之间通信的机制,用于在无关的进程之间进行数据传输。

        命名管道通过在文件系统中创建一个特殊的文件来实现通信。这个特殊的文件被称为FIFO(First-in, First-out)或命名管道。

命名管道通信的原理

        和匿名管道一样,想让双方通信,必须先让双方看到同一份资源!它和匿名管道本质是一样的,只是看到资源的方式不同

        匿名管道是通过父子进程继承来看到同一份资源的,也叫做管道文件,这个文件是纯内存级的,所以没有名字,叫做匿名管道。

        而命名管道是在磁盘上有一个特殊的文件,这个文件可以被打开,但是打开后不会将内存中的数据刷新到磁盘。在磁盘上就有了路径,而路径是唯一的,所以双方就可以通过文件的路径 来看到同一份资源,即管道文件。

这是命名管道的流程:

  1. 创建命名管道:通过调用系统调用 mkfifo() 在文件系统中创建一个特殊的文件,这个文件就是命名管道。创建命名管道时,需要指定管道的名称和所需的权限。

  2. 打开命名管道:进程通过调用系统调用 open() 来打开命名管道,得到一个文件描述符。进程可以通过打开具有相同名称的文件来打开命名管道的读取端和写入端。

  3. 进程通信:一旦命名管道被打开,进程就可以使用文件描述符进行通信。每个进程可以选择读取端或写入端与命名管道进行交互。

  4. 数据传输:进程在读取端通过调用 read() 系统调用从命名管道中读取数据,而在写入端通过调用 write() 系统调用将数据写入命名管道中。读取端和写入端可以通过文件描述符进行数据的发送和接收。

  5. 关闭命名管道:进程完成通信后,可以通过调用 close() 关闭命名管道的文件描述符来释放资源。当所有对命名管道的引用都被关闭时,管道的文件系统条目将被删除。

mkfifo的使用

        上面提到了需要使用mkfifo来创建这个特殊的文件,来让独立的进程之间通过它进行通信,下面来看一下它的用法。

mkfifo [选项] 文件名

         非常简单的使用方法,选项我们一般不用,所以直接mkfifo + 文件名即可

        我们在当前路径下创建一个name_pipe的文件.

注意权限的最前面是p,代表是管道文件。


我们此时echo一句消息到这个管道文件中:

        我们发现这里阻塞住了,这是因为一方向管道文件里写入了,但是另外一方还没有读,所以此时我们新建一个窗口,然后读取name_pipe里的内容:

 

         这样信息便成功的被读取出来了,这就是mkfifo的简单使用。

代码模拟命名管道通信过程

        其实过程和匿名管道类似,只是看到同一资源的手段不一样。

        上面讲的mkfifo是指令创建,但是如果我想用代码该如何实现呢?这里有一个mkfifo函数:

         第一个参数是创建的管道文件的路径,第二个是权限。

         当创建成功时,mkfifo返回0,否则返回-1.

整体的流程是是这样的:

我们首先可以分成 服务端 和 客户端,服务端负责

        1.创建管道文件并打开

        2.进行与客户端正常的通信

        3.最后关闭并删除管道文件

而客户端

        1.首先要打开管道文件

        2.然后进行与服务端正常的通信流程即可

        这里为了方便,我们加入了日志,可以看到每一步的动作。

        所以一共四个文件,分别为comm.hpp,client.cc,server.cc,Log.hpp.

comm.hpp

#pragma once
#include<iostream>
#include<cstdio>
#include<cstring>
#include<unistd.h>
#include<vector>
#include<string>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>using namespace std;#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";

client.cc

#include "comm.hpp"
int main()
{//1.获取管道文件int fd = open(ipcPath.c_str(),O_WRONLY);if(fd < 0){perror("open");exit(1); }//2.通信过程string buffer;while(true){cout << "please Enter Message Line :> ";getline(cin,buffer);write(fd,buffer.c_str(),buffer.size());}//3.关闭文件return 0;
}

server.cc

#include"comm.hpp"
#include"Log.hpp"
int main()
{//1.创建管道文件if(mkfifo(ipcPath.c_str(),MODE) < 0){perror("mkfifo");exit(1);}Log("创建管道文件成功",Debug) << "step 1" << endl;//2.正常的文件操作int fd = open(ipcPath.c_str(),O_RDONLY);if(fd < 0){perror("open");exit(2);}Log("打开管道文件成功",Debug) << "step 2" << endl;//3.编写正常的通信代码char buffer[SIZE]; while(true){memset(buffer,'\0',sizeof(buffer));ssize_t s = read(fd,buffer,sizeof(buffer)-1);if(s > 0){cout << "client say> " << buffer << endl;}else if(s == 0){//end of filecerr << "read emd of file, client quit, server quit too!" << endl;break;}else{//read errorperror("read");}}//4.关闭文件close(fd);Log("关闭管道文件成功",Debug) << "step 3" << endl;unlink(ipcPath.c_str());//通信完毕就删除文件Log("删除管道文件成功",Debug) << "step 4" << endl;return 0;
}

Log.hpp

#pragma once
#include <iostream>
#include <ctime>
#include<string>
using namespace std;#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3string msg[] = {"Debug ","Notice","Warning","Error"
};ostream& Log(string message,int level)
{cout << " | " << (unsigned)time(NULL) << " | " << msg[level] << " | " << message;return cout;
}

 然后我们再编译运行,可以再创建一个Makefile文件,直接编译好所有的文件,内容如下:

.PHONY:all
all:client serverclient:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf client server

此时我们直接make即可。然后会得到两个可执行文件client和server.

我们打开两个窗口,首先运行server.

 第一步创建管道完成,然后我们在另一个窗口运行客户端.

运行起来后,显示打开文件也成功了,这个时候,我们在客户端输入,服务端都能读取到:

 然后我们ctrl + c 退出客户端,此时服务端也会break跳出循环,然后结束.

 这样,利用命名管道通信的代码流程也就完成了.

 

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

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

相关文章

IDEA离线环境搭建远程开发-Windows

公司的云桌面实在太卡&#xff0c;多个微服务项目跑起来&#xff0c;直接无法进行其它编码工作&#xff0c;所以想到使用Idea提供的远程开发功能&#xff0c;将服务运行在服务器&#xff0c;电脑只提供给开发页面展示&#xff0c;提高效率。 环境介绍&#xff1a; 开发环境&…

SystemVerilog数组参数传递及引用方法总结

一、将常数数组传递给task/function 如下面的程序&#xff0c;将一个常数数组传递给function module my_array_test();function array_test(int array[4]);foreach(array[i]) begin$display("array[%0d] %0d", i, array[i]);endendfunctioninitial beginarray_tes…

景联文科技高质量成品数据集上新啦!

景联文科技近期上新多个成品数据集&#xff0c;包含图像、视频等多种类型的数据&#xff0c;涵盖丰富的场景&#xff0c;可满足不同模型的多元化需求。 高质量成品数据集可用于训练和优化模型&#xff0c;使得模型能够更加全面和精准地理解和处理任务&#xff0c;更好地应对复…

anaconda创建虚拟环境在D盘

【看一看就行&#xff0c;又是挺水的一期&#xff08;每一季都掺和一点子水分也挺好&#xff09;】 一、创建&#xff1a; conda create --prefixD:\python37\py37 python3.7 这下就在D盘了&#xff1a; 二、激活刚刚那个环境&#xff1a; activate D:\pyhton37\py37​ &…

如何微调医疗大模型llm:llama2学习笔记

三个微调方向&#xff1a;简单医疗问答 临床问答 影像学 一般流程&#xff1a; 1 数据集准备 2 模型基座选择 3 微调 4 案例拆解 1 数据集准备&#xff1a;两种类型&#xff0c;一种文本一种影像 扩展&#xff0c;多模态 2 模型基座选择 多模态处理所有视频&#xff0c;文本…

【汇总】解决Ajax请求后端接口,返回ModelAndView页面不跳转

【汇总】解决Ajax请求后端接口&#xff0c;返回ModelAndView不跳转 问题发现问题解决方法一&#xff1a;直接跳转到指定URL&#xff08;推荐&#xff09;方法二&#xff1a;将返回的html内容&#xff0c;插入到页面某个元素中方法三&#xff1a;操作文档流方法四&#xff1a;使…

redis的安装和配置

一、nosql 二、redis的安装和配置 redis的安装&#xff1a; redis常见配置&#xff1a; 配置文件redis.conf

【FAQ】调用EasyDSS返回的直播快照接口,无法编辑只能新建的原因排查与解决

EasyDSS视频直播点播平台集视频直播、点播、转码、管理、录像、检索、时移回看等功能于一体&#xff0c;可提供音视频采集、视频推拉流、播放H.265编码视频、存储、分发等视频能力服务&#xff0c;在应用场景上&#xff0c;平台可以运用在互联网教育、在线课堂、游戏直播等领域…

【H5移动端】常用的移动端方案合集-键盘呼起、全面屏适配、图片大小显示、300ms点击延迟、首屏优化(不定期补充~)

文章目录 前言键盘呼起问题靠近底部的输入项被键盘遮挡底部按钮被顶上去 全面屏适配图片大小显示问题解决300ms延迟首屏优化 前言 这篇文章总结了我在工作中做H5遇到的一些问题&#xff0c;包括我是怎么解决的。可能不是当下的最优解&#xff0c;但是能保证解决问题。 单位适…

智慧防汛,数字科技的力量

随着夏日的脚步临近&#xff0c;台风季节即将降临。对于那些居住在沿海地区的人们来说&#xff0c;台风是一种常见的自然灾害&#xff0c;其带来的风雨可能对生命和财产造成严重威胁。然而&#xff0c;随着数字科技的飞速发展&#xff0c;可视化技术为防汛抗台工作带来了全新的…

【C++】STL——queue的介绍和使用、queue的push和pop函数介绍和使用、queue的其他成员函数

文章目录 1.queue的介绍2.queue的使用2.1queue构造函数2.2queue的成员函数&#xff08;1&#xff09;empty() 检测队列是否为空&#xff0c;是返回true&#xff0c;否则返回false&#xff08;2&#xff09;size() 返回队列中有效元素的个数 &#xff08;3&#xff09;front() 返…

在使用Python爬虫时遇到解析错误解决办法汇总

在进行Python爬虫任务时&#xff0c;遇到解析错误是常见的问题之一。解析错误可能是由于网页结构变化、编码问题、XPath选择器错误等原因导致的。为了帮助您解决这个问题&#xff0c;本文将提供一些实用的解决办法&#xff0c;并给出相关的代码示例&#xff0c;希望对您的爬虫任…

无涯教程-Lua - Modules(模块)

模块就像可以使用 require 加载的库&#xff0c;并且具有包含Table的单个全局名称&#xff0c;该模块可以包含许多函数和变量。 Lua 模块 其中一些模块示例如下。 -- Assuming we have a module printFormatter -- Also printFormatter has a funtion simpleFormat(arg) -- …

手把手教你安装Eclipse最新版本的详细教程 (非常详细,非常实用)

简介 首先声明此篇文章主要是针对测试菜鸟或者刚刚入门的小伙们或者童鞋们&#xff0c;大佬就没有必要往下看了。 写这篇文章的由来是因为后边要用这个工具&#xff0c;但是由于某些原因有部分小伙伴和童鞋们可能不会安装此工具&#xff0c;为了方便小伙伴们和童鞋们的后续学习…

[Linux]手把手教你制作进度条小程序

[Linux]制作进度条小程序 文章目录 [Linux]制作进度条小程序C语言中的\n和\r字符缓冲区的刷新策略进行进度条代码编写 C语言中的\n和\r字符 C语言中字符分为两种: 可显字符控制字符 其中可显字符就是字符a这类的字符&#xff0c;控制字符就是\n这种控制字符。 对于我们制作…

【Axure教程】移动端二级滑动选择器

今天教大家制作移动端二级滑动选择器的原型模板&#xff0c;该原型已全国一二级省市选择器为案例&#xff0c;因为该原型用中继器做的&#xff0c;所以制作完成之后使用也很方便&#xff0c;只需修改中继器表格里的内容即可 一、效果展示 1. 拖动选择 2. 快捷选择 【原型预览…

Docker的安装和部署

目录 一、Docker的安装部署 &#xff08;1&#xff09;关闭防火墙 &#xff08;2&#xff09;关闭selinux &#xff08;3&#xff09;安装docker引擎 &#xff08;4&#xff09;启动docker &#xff08;5&#xff09;设置docker自启动 &#xff08;6&#xff09;测试doc…

自然语言处理学习笔记(二)————语料库与开源工具

目录 1.语料库 2.语料库建设 &#xff08;1&#xff09;规范制定 &#xff08;2&#xff09;人员培训 &#xff08;3&#xff09;人工标注 3.中文处理中的常见语料库 &#xff08;1&#xff09;中文分词语料库 &#xff08;2&#xff09;词性标注语料库 &#xff08;3…

《皮囊》阅读笔记

《皮囊》阅读笔记 2023年8月2号在杭州小屋读完&#xff0c;该书共收录14篇散文&#xff0c;内容大致分为两部分&#xff1a;前半部分讲述作者的阿太&#xff08;外婆的母亲&#xff09;、母亲、父亲关于生活哲学、房子、疾病与信仰的故事&#xff0c;后半部分讲述生活在小镇的张…

QT充当客户端模拟浏览器等第三方客户端对https进行双向验证

在 ssl单向证书和双向证书校验测试及搭建流程 文章中&#xff0c;已经做了基于https的单向认证和双向认证&#xff0c;&#xff0c;&#xff0c; 在进行双向认证时&#xff0c;采用的是curl工具或浏览器充当客户端去验证。 此次采用QT提供的接口去开发客户端向服务器发送请求&a…