第七章:进程间通信(IPC)——构成进程间通信的信道方案
系列文章目录
文章目录
- 系列文章目录
- 前言
- 进程间通信介绍
- 进程间通信目的
- 进程间通信发展
- 进程间通信分类
- 进程通信的原理
- 管道
- 什么是管道
- pipe
- 管道通信特点
- 简单设计
- 命名管道
- 什么是命名管道
- mkfifo
- strcmp/strncasecmp
- unlink
- getch
- 简单设计
- 共享内存
- 什么是共享内存
- shmget/ftok
- ipcs
- shmctl
- shmat/shmdt
- 共享内存的特点
- 消息队列
- msgget/msgctl
- msgsnd/msgrcv
- 信号量
- 互斥等四个概念
- 什么是信号量
- semget/semctl
- semop
- 理解IPC
- 总结
前言
进程通信是数据传输的重要方式。
进程间通信介绍
进程间通信目的
-
数据传输:一个进程需要将它的数据发送给另一个进程
-
资源共享:多个进程之间共享同样的资源。
-
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
-
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信发展
-
管道
-
System V进程间通信
-
POSIX进程间通信
进程间通信分类
-
管道
-
匿名管道pipe
-
命名管道
-
-
System V IPC
-
System V 消息队列
-
System V 共享内存
-
System V 信号量
-
-
POSIX IPC
-
消息队列
-
共享内存
-
信号量
-
互斥量
-
条件变量
-
读写锁
-
进程通信的原理
让两个不同的进程进行通信,前提条件是让两个进程看到同一份”资源“(不违背进程的独立性并且由OS提供直接或间接提供)。
任何进程通信手段:
-
先让不同的进程看到同一份资源。(通信方式)
-
让一方写入,一方读取,完成通信过程。
管道
什么是管道
管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”,管道也是文件 。
who | wc -l
// | 管道符
-
创建子进程的时候,只会浅拷贝进程相关的数据结构对象,不会赋值父进程打开的文件对象!
-
管道是一个OS提供的内存匿名文件,以读写的方式打开。
-
fork之后需要确定数据的流向,关闭不必要的fd。
-
因为只支持单向通信,所以称之为管道。
pipe
man pipe#include <unistd.h>int pipe(int pipefd[2]);On success, zero is returned. On error, -1 is returned, and errno is set appropriately.
管道通信特点
-
单向通信(半双工)。
-
管道本质是文件,因为fd的生命周期随进程的,即管道的生命周期是随进程的。
-
管道通信,通常用来进行具有”血缘“关系的进程进行通信,常用于父子进程通信——pipe打开管道,并不清楚管道的名字——匿名管道。
-
管道通信中,写入的次数,和读取的次数,并不是严格匹配的(读写没有强相关)—— 面向字节流
-
管道具有一定的协同能力,让reader和writer能够按照一定的步骤进行通信 —— 自带同步与互斥机制
-
如果read读取完毕了所有的管道数据,如果对方不发,就只能等待。
-
如果write端将管道写满了,就不能写了(管道容量有限)。
-
关闭了写端,读取完管道数据,再读取read就会返回0,表明读到了文件结尾。
-
如果写端一直再写,读端关闭,OS会通过信号的方式杀死一直在写入的进程。
-
-
当要一次写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性;当要一次写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
简单设计
父进程可以通过向子进程写入特定的消息,唤醒子进程,甚至让子进程定向的执行某种任务。
父进程要管理自己创建的管道和进程。
#include <iostream>
#include <string>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include "Task.hpp"
using namespace std;const int gnum = 3;
Task t;class EndPoint
{
private:static int number;
public:pid_t _child_id;int _write_fd;std::string processname;
public:EndPoint(int id, int fd) : _child_id(id), _write_fd(fd){//process-0[pid:fd]char namebuffer[64];snprintf(namebuffer, sizeof(namebuffer), "process-%d[%d:%d]", number++, _child_id, _write_fd);processname = namebuffer;}std::string name() const{return processname;}~EndPoint(){}
};int EndPoint::number = 0;// 子进程要执行的方法
void WaitCommand()
{while (true){int command = 0;int n = read(0, &command, sizeof(int));if (n == sizeof(int)){t.Execute(command);}else if (n == 0){std::cout << "父进程让我退出,我就退出了: " << getpid() << std::endl; break;}else{break;}}
}void createProcesses(vector<EndPoint> *end_points)
{vector<int> fds;for (int i = 0; i < gnum; i++){// 1.1 创建管道int pipefd[2] = {0};int n = pipe(pipefd);assert(n == 0);(void)n;// 1.2 创建进程pid_t id = fork();assert(id != -1);// 一定是子进程if (id == 0){for(auto &fd : fds) close(fd);//关闭其他进程的管道写端// std::cout << getpid() << " 子进程关闭父进程对应的写端:";// for(auto &fd : fds)// {// std::cout << fd << " ";// close(fd);// }// std::cout << std::endl;// 1.3 关闭不要的fdclose(pipefd[1]);// 我们期望,所有的子进程读取"指令"的时候,都从标准输入读取// 1.3.1 输入重定向,可以不做dup2(pipefd[0], 0);// 1.3.2 子进程开始等待获取命令WaitCommand();close(pipefd[0]);exit(0);}// 一定是父进程// 1.3 关闭不要的fdclose(pipefd[0]);// 1.4 将新的子进程和他的管道写端,构建对象end_points->push_back(EndPoint(id, pipefd[1]));fds.push_back(pipefd[1]);}
}int ShowBoard()
{std::cout << "##########################################" << std::endl;std::cout << "| 0. 执行日志任务 1. 执行数据库任务 |" << std::endl;std::cout << "| 2. 执行请求任务 3. 退出 |" << std::endl;std::cout << "##########################################" << std::endl;std::cout << "请选择# ";int command = 0;std::cin >> command;return command;
}void ctrlProcess(const vector<EndPoint> &end_points)
{// 2.1 我们可以写成自动化的,也可以搞成交互式的int num = 0;int cnt = 0;while(true){//1. 选择任务int command = ShowBoard();if(command == 3) break;if(command < 0 || command > 2) continue;//2. 选择进程int index = cnt++;cnt %= end_points.size();std::string name = end_points[index].name();std::cout << "选择了进程: " << name << " | 处理任务: " << command << std::endl;//3. 下发任务write(end_points[index]._write_fd, &command, sizeof(command));sleep(1);}
}void waitProcess(const vector<EndPoint> &end_points)
{// 1. 我们需要让子进程全部退出 --- 只需要让父进程关闭所有的write fd就可以了!// for(const auto &ep : end_points) // for(int end = end_points.size() - 1; end >= 0; end--)for(int end = 0; end < end_points.size(); end++){std::cout << "父进程让子进程退出:" << end_points[end]._child_id << std::endl;close(end_points[end]._write_fd);waitpid(end_points[end]._child_id, nullptr, 0);std::cout << "父进程回收了子进程:" << end_points[end]._child_id << std::endl;} sleep(10);// 2. 父进程要回收子进程的僵尸状态// for(const auto &ep : end_points) waitpid(ep._child_id, nullptr, 0);// std::cout << "父进程回收了所有的子进程" << std::endl;// sleep(10);
}// #define COMMAND_LOG 0
// #define COMMAND_MYSQL 1
// #define COMMAND_REQEUST 2
int main()
{vector<EndPoint> end_points;// 1. 先进行构建控制结构, 父进程写入,子进程读取 , bug?createProcesses(&end_points);// 2. 我们的得到了什么?end_pointsctrlProcess(end_points);// 3. 处理所有的退出问题waitProcess(end_points);return 0;
}
#pragma once#include <iostream>
#include <vector>
#include <unistd.h>
#include <unordered_map>// typedef std::function<void ()> func_t;typedef void (*fun_t)(); //函数指针void PrintLog()
{std::cout << "pid: "<< getpid() << ", 打印日志任务,正在被执行..." << std::endl;
}void InsertMySQL()
{std::cout << "执行数据库任务,正在被执行..." << std::endl;
}void NetRequest()
{std::cout << "执行网络请求任务,正在被执行..." << std::endl;
}// void ExitProcess()
// {
// exit(0);
// }//约定,每一个command都必须是4字节
#define COMMAND_LOG 0
#define COMMAND_MYSQL 1
#define COMMAND_REQEUST 2class Task
{
public:Task(){funcs.push_back(PrintLog);funcs.push_back(InsertMySQL);funcs.push_back(NetRequest);}void Execute(int command){if(command >= 0 && command < funcs.size()) funcs[command]();}~Task(){}
public:std::vector<fun_t> funcs;// std::unordered_map<std::string, fun_t> funcs;
};
命名管道
-
管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
-
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
-
命名管道是一种特殊类型的文件
什么是命名管道
mkfifo fifo//创建一个文件名为filo的命名管道
有文件属性的内存级文件。
-
不同的进程打开同一个文件不会打开新的文件属性结构体对象。
-
两个进程打开同一个磁盘文件也可以构成磁盘通信信道。
-
为了不使用磁盘文件(避免需要缓冲区刷新),直接用内存级文件——命名管道文件来做”资源“。
-
命名管道与管道的原理一样。
-
命名管道必须要有文件名,所以称为命名管道。
【总结】:让不同的进程通过文件路径+文件名看到同一个文件,并打开就是看到了同一个”资源“。
mkfifo
man 3 mkfifo#include <sys/types.h>
#include <sys/stat.h>int mkfifo(const char *pathname, mode_t mode);On success mkfifo() returns 0. In the case of an error, -1 is returned (in which case, errno is set appropriately).
strcmp/strncasecmp
man strncasecmp#include <strings.h>int strcasecmp(const char *s1, const char *s2);int strncasecmp(const char *s1, const char *s2, size_t n);man strcmp#include <string.h>int strcmp(const char *s1, const char *s2);int strncmp(const char *s1, const char *s2, size_t n);
unlink
man 2 unlink#include <unistd.h>int unlink(const char *pathname);
getch
man getch#include <curses.h>int getch(void);
简单设计
#pragma once#include <iostream>
#include <string>#define NUM 1024const std::string fifoname = "./fifo";
uint32_t mode = 0666;
#include <iostream>
#include <cerrno>
#include <cstring>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "comm.hpp"//少年们, 我刚刚写了一个基于匿名管道的进程池
// 可不可以把它改成使用命名管道呢??
int main()
{// 1. 创建管道文件,我们今天只需要一次创建umask(0); //这个设置并不影响系统的默认配置,只会影响当前进程int n = mkfifo(fifoname.c_str(), mode);if(n != 0){std::cout << errno << " : " << strerror(errno) << std::endl;return 1;}std::cout << "create fifo file success" << std::endl;// 2. 让服务端直接开启管道文件int rfd = open(fifoname.c_str(), O_RDONLY);if(rfd < 0 ){std::cout << errno << " : " << strerror(errno) << std::endl;return 2;}std::cout << "open fifo success, begin ipc" << std::endl;// 3. 正常通信char buffer[NUM];while(true){buffer[0] = 0;ssize_t n = read(rfd, buffer, sizeof(buffer) - 1);if(n > 0){buffer[n] = 0;//std::cout << "client# " << buffer << std::endl;printf("%c", buffer[0]);fflush(stdout);}else if(n == 0){std::cout << "client quit, me too" << std::endl;break;}else {std::cout << errno << " : " << strerror(errno) << std::endl;break;}}// 关闭不要的fdclose(rfd);unlink(fifoname.c_str());return 0;
}
#include <iostream>
#include <cstdio>
#include <cerrno>
#include <cstring>
#include <cassert>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
// #include <ncurses.h>
#include "comm.hpp"int main()
{//1. 不需创建管道文件,我只需要打开对应的文件即可!int wfd = open(fifoname.c_str(), O_WRONLY);if(wfd < 0){std::cerr << errno << ":" << strerror(errno) << std::endl;return 1;}// 可以进行常规通信了char buffer[NUM];while(true){// std::cout << "请输入你的消息# ";// char *msg = fgets(buffer, sizeof(buffer), stdin);//C库函数读取的是字符串,会处理'\0',系统调用处理字节流,不会处理'\0'.// assert(msg);// (void)msg;// int c = getch();// std::cout << c << std::endl;// if(c == -1) continue;system("stty raw");int c = getchar();system("stty -raw");//std::cout << c << std::endl;//sleep(1);//buffer[strlen(buffer) - 1] = 0;// abcde\n\0// 012345//if(strcasecmp(buffer, "quit") == 0) break;ssize_t n = write(wfd, (char*)&c, sizeof(char));assert(n >= 0);(void)n;}close(wfd);return 0;
}
共享内存
【system V共享内存】:
共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到\内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据。
什么是共享内存
-
让不同的进程,先看到同一份资源——内存块–共享内存。
-
虚拟进程空间的共享区映射同一块物理内存。
-
在任意一个时刻,可能有多个共享内存在被用来进行通信,所以系统中一定存在很多shm同时存在,OS要管理所有的共享内存。所以共享内存不仅仅会在内存中开辟空间,OS也会构建对应的描述共享内存的结构体对象!
-
共享内存 = 共享内存的内核数据结构(伪代码:struct shm) + 真正开辟的内存空间
shmget/ftok
man shmget#include <sys/ipc.h>
#include <sys/shm.h>int shmget(key_t key, size_t size, int shmflg);On success, a valid shared memory identifier is returned. On errir, -1 is returned, and errno is set to indicate the error.man ftok
#include <sys/types.h>
#include <sys/ipc.h>key_t ftok(const char *pathname, int proj_id);
// pathname:路径字符串 proj_id:项目ID
让两个进程映射同一个内存块。
-
传入同样的参数形成一样key_t,一个进程用key_t创建一个内存块并映射,另一个进程用key_t映射该内存块,使两个进程地址空间映射同一个内存块。
-
key_t是在内核中使用的,用数字来标识唯一的内存块。
-
key vs shmid:key类比文件inode编号,shmid类比文件的fd。所以在用户层的操作都是用shmid。
ipcs
[admin1@VM-4-17-centos linux_code]$ ipcs------ Message Queues --------
key msqid owner perms used-bytes messages
//消息队列
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status
//共享内存
------ Semaphore Arrays --------
key semid owner perms nsems
//信号量//显示ipc通信系统所支持的三种ipc通信策略
//1.key:表示共享内存的key
//2.shmid:表示共享内存的编号
//3.owner:表示创建共享内存的用户
//4.perms:表示共享内存的使用权限
//5.bytes:表示共享内存的大小
//6.nattch:表示连接到共享内存的进程数
//7.status:表示共享的状态(不显示则为正常使用,显示"dest"表示共享内存已被删除,但任有用户使用)[admin1@VM-4-17-centos linux_code]$ ipcs -m
//查看共享内存
------ Shared Memory Segments --------
key shmid owner perms bytes nattch status //-m 针对共享内存的操作
//-q 针对消息队列的操作
//-s 针对消息队列的操作
//-a 针对所有资源的操作[admin1@VM-4-17-centos linux_code]$ ipcrm -m 196611
//删除shmid为196611的共享内存
shmctl
man shmctl#include <sys/ipc.h>
#include <sys/shm.h>int shmctl(int shmid, int cmd, struct shmid_ds *buf);//删除共享内存
shmat/shmdt
man shmat/shmdt#include <sys/types.h>
#include <sys/shm.h>void *shmat(int shmid, const void *shmaddr, int shmflg);
//关联共享内存int shmdt(const void *shmaddr);
//去关联共享内存
共享内存的特点
-
创建共享内存的进程已经退出了,但我们发现共享内存一定还存在!共享内存的生命周期不随进程,随OS。
-
一旦共享内存映射到进程的地址空间,该共享内存就直接被所有的进程看到了。可以让进程通信的时候减少拷贝次数,所以共享进程是所有进程通信方式中速度最快的。
- 共享内存没有任何的保护机制(同步互斥)。因为管道通过系统接口通信,共享内存直接通信。
消息队列
msgget/msgctl
man msgget#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgget(key_t key, int msgflg);#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msgsnd/msgrcv
#include <sys/types.h>#include <sys/ipc.h>#include <sys/msg.h>int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
信号量
互斥等四个概念
我们把大家都能看到的资源称为”公共资源“。
-
互斥:任何一个时刻,都只允许一个执行流在进行共享资源的访问——加锁。
-
我们把任何一个时刻,都只允许一个执行流在进行访问的共享资源称为”临界资源“。
-
临界资源是要通过代码访问的,凡是访问临界资源的代码,叫做”临界区“。
-
原子性:要么不做,要么做完,只有两种确定状态的属性。
什么是信号量
任何计技术都有自己的应用场景。
看电影之前,先要买票。
买票的本质功能:1. 对坐位资源的预定机制 2. 确保不会因为多放出去特定的座位资源而导致冲突。
假设一个放映厅只有一个座位→互斥
信号量/信号灯:本质就是一个计数器!描述资源数量的计数器。
任何一个执行流,想访问临界资源的一个子资源的时候,不能直接访问。先申请信号量资源(–count),只要我申请信号量成功,我就一定未来能够拿到一个子资源。如果信号量用完了,即没有成功申请,就会挂起阻塞进程。—— P操作。
成功申请后进入自己的临界区,访问对应的临界资源。
释放信号量资源(++count)只要将计数器增加,就表示将我们对应的资源进行了归还。阻塞的进程就可以继续申请并执行了。—— V操作。
进程通过执行代码来申请,每个进程都要遵守这个规则。
所有的进程都得先看到信号量——共享资源——必须保证自己的操作++/–是原子的!
两个进程可以看到同一个count,即让不同的进程看到同一个”计数器“(资源)。所以信号量被归类到了进程间通信!
如果这个计数器是1呢?这就是二元信号量,完成了互斥功能!互斥的本质就是将临界资源独立使用!!!!!
semget/semctl
man semget#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semget(key_t key, int nsems, int semflg);#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semctl(int semid, int semnum, int cmd, ...);
semop
man semop#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>int semop(int semid, struct sembuf *sops, unsigned nsops);
理解IPC
“多态”:
总结
进程通信的本质是让不同进程看到同一份”资源“。
如果你真的愿意为自己的梦想去努力,最差的结果,不过是大器晚成。
相关文章:

第七章:进程间通信(IPC)——构成进程间通信的信道方案
系列文章目录 文章目录 系列文章目录前言进程间通信介绍进程间通信目的进程间通信发展进程间通信分类进程通信的原理 管道什么是管道pipe管道通信特点简单设计 命名管道什么是命名管道mkfifostrcmp/strncasecmpunlinkgetch简单设计 共享内存什么是共享内存shmget/ftokipcsshmct…...

部分常用CSS样式
目录 1.字体样式 2.文本样式 3.鼠标样式 cursor 4.背景样式 5.列表样式 6.CSS伪类 7.盒子模型 1.字体样式 font-family 字体类型:隶书” “楷体” font-size 字体大小:像素px font-weight 字体粗细:bold 定义粗体字…...

思科单臂路由、lacp链路聚合、NAT实验
实验拓扑图: 实验目的: 如图所示配置相应IP地址和VLAN,并通过在AR1上配置单臂路由,实现VLAN10和VLAN20的主机能够在VLAN间通信;在SW1和SW2的三条链路实施链路聚合,使用静态LACP模式,使一条链…...

【力扣每日一题】2023.8.5 合并两个有序链表
目录 题目: 示例: 分析: 代码: 题目: 示例: 分析: 题目给我们两个有序的链表,要我们保持升序的状态合并它们。 我们可以马上想要把两个链表都遍历一遍,把所有节点的…...
QT 驱动条码打印机(没有验证过)
这里的打印机是条码打印机,因为第一次接触这种设备,所以买了斑马的GK888t型条码打印机,据说ZPL语言就是斑马的杰作想必支持会好点。实际是,除了ZPL本身外,没有SDK,也没有DDK,所以,一…...

Kafka介绍
目录 1,kafka简单介绍 2,kafka使用场景 3,kafka基本概念 kafka集群 数据冗余 分区的写入 读取分区数据 顺序消费 顺序消费典型的应用场景: 批量消费 提交策略 kafka如何保证高并发 零拷贝技术(netty&#…...

Django使用uwsgi+nginx部署,admin没有样式解决办法
Django使用uwsginginx部署,admin没有样式解决办法 如果使用了虚拟环境则修改nginx.conf文件中的/static/路径为你虚拟环境的路径,没有使用虚拟环境则改为你python安装路径下的static server {listen 8008;server_name location; #改为自己的域名,没域名…...

穷举深搜暴搜回溯剪枝(3)
一)字母大小写全排列 784. 字母大小写全排列 - 力扣(LeetCode) 1)从每一个字符开始进行枚举,如果枚举的是一个数字字符,直接忽视 如果是字母的话,进行选择是变还是不变 2)当进行遍历到叶子结点的时候,直接将…...
Bash 脚本的参数等
bash 的 $值 $0 : 表示当前脚本的名称${BASH_SOURCE[0]} : 表示当前 Bash 脚本文件的路径,可以理解为 $0 的安全版本,防止被修改。$1 : 表示第一个参数,以此类推$ : 表示所有传入脚本的参数$UID : 表示当前用户的 ID 号。如果当前用户是 roo…...

从哪些方面学HTML技术? - 易智编译EaseEditing
学习HTML技术是前端开发的基础,它用于定义网页的结构和内容。以下是学习HTML技术时可以关注的方面: HTML基本语法: 了解HTML标签的基本语法和用法,学习如何创建HTML文档和元素。 常用HTML标签: 学习常用的HTML标签&…...

非阻塞IO
非阻塞IO fcntl 一个文件描述符, 默认都是阻塞IO。fcntl可以将某个文件描述符设置为非阻塞IO,先看一下文档介绍。 传入的cmd的值不同,后面追加的参数也不相同。 fcntl函数有5种功能: 复制一个现有的描述符(cmd F_DUPFD)。获得…...
Debian如何让multilib和交叉编译工具链共存
Debian一个槽点是gcc/g/gfortran-multilib和交叉编译工具链如gcc/g/gfortran-riscv64-linux-gnu会互相卸载,解决办法如下: 1、安装build-essential(gcc/g/libc6-dev/make/dpkg-dev)和gfortran,记下被安装的gcc版本&am…...

Flink之JDBC Sink
这里介绍一下Flink Sink中jdbc sink的使用方法,以mysql为例,这里代码分为两种,事务和非事务 非事务代码 import org.apache.flink.connector.jdbc.JdbcConnectionOptions; import org.apache.flink.connector.jdbc.JdbcExecutionOptions; import org.apache.flink.connector.…...

lifecycleScope Unresolved reference
描述 导入了lifecycle.lifecycleScope,但是在activity中使用lifecycleScope报错出现Unresolved reference找不到引用。 导包 import androidx.lifecycle.lifecycleScope使用 lifecycleScope.launch(Dispatchers.IO) {...}错误 方案 代码中的activity继承Activ…...
P5960 【模板】差分约束算法
【模板】差分约束算法 题目描述 给出一组包含 m m m 个不等式,有 n n n 个未知数的形如: { x c 1 − x c 1 ′ ≤ y 1 x c 2 − x c 2 ′ ≤ y 2 ⋯ x c m − x c m ′ ≤ y m \begin{cases} x_{c_1}-x_{c_1}\leq y_1 \\x_{c_2}-x_{c_2} \leq y_2 \\…...

VSCode---通过ctrl+鼠标滚动改变字体大小
打开设置然后在右边输editor.mouseWheelZoo勾选即可实现鼠标滚动改变字体大小 4.这种设置的字体大小是固定的...

视频监控汇聚平台EasyCVR视频分享页面WebRTC流地址播放不了是什么原因?
开源EasyDarwin视频监控TSINGSEE青犀视频平台EasyCVR能在复杂的网络环境中,将分散的各类视频资源进行统一汇聚、整合、集中管理,在视频监控播放上,TSINGSEE青犀视频安防监控汇聚平台可支持1、4、9、16个画面窗口播放,可同时播放多…...

Libevent开源库的介绍与应用
libeventhttps://libevent.org/ 一、初识 1、libevent介绍 Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:事件驱动( event-driven),高性能;轻量级,专注于网络ÿ…...

【LNMP】LNMP
LNMP:是目前成熟的企业网站的应用模式之一,指的是一套协同工作的系统和相关软件;能够提供静态页面服务,也可以提供动态web服务 L Linux系统,操作系统N Nginx网站服务,前端,提供前端的静态…...
uniapp自定义头部导航栏
有时我们需要一些特殊的头部导航栏页面,取消传统的导航栏,来增加页面的美观度。 下面我就教大家如何配置: 一、效果图 二、实现 首先在uniapp中打开pages.json配置文件,在单个路由配置style里面设置导航栏样式nav…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...

大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
css3笔记 (1) 自用
outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size:0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格ÿ…...
.Net Framework 4/C# 关键字(非常用,持续更新...)
一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...

佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...

人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...

GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...