【Linux】进程间通信——system V 共享内存、消息队列、信号量
需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦)
文章目录
- 写在前面
- 1. 共享内存
- 1.1 共享内存的概念
- 1.2 共享内存的原理
- 1.3 共享内存的使用
- 1.3.1 创建
- 1.3.2 控制
- 1.3.3 关联
- 1.3.4 去关联
- 1.3.6 实战——实现server和client端的通信
- 1.3.7 共享内存的特点
- 2. 消息队列(不常用)
- 2.1 消息队列的概念
- 2.2 消息队列的使用
- 2.2.1 获取消息队列
- 2.2.2 控制消息队列
- 2.2.3 发送和接收数据
- 3. 信号量
- 3.1 信号量的概念扫盲
- 3.2 内核中信号量的相关数据结构
- 3.3 信号量的PV操作
- 3.4 信号量相关函数
- 3.4.1 申请信号量
- 3.4.2 控制信号量
- 3.4.3 信号量的操作PV
写在前面
在上一篇文章中我们讲了一种进程间通信的方式管道,管道通信的本质是基于文件的,也就是说OS没有为此做过多的设计,但是system V IPC是操作系统特地设置的一种通信方式。提供的通信方式有以下三种
- system V 共享内存
- system V 消息队列
- system V 信号量
1. 共享内存
1.1 共享内存的概念
我们在上一篇文章中讲到:要实现进程间通信,就一定要让不同的进程看到同一份资源。
在匿名管道/命名管道中,我们让不同进程看到同一份资源的方式是让不同进程打开同一个文件,使用对文件的读写来实现进程间通信,那么除此之外,我们还有其他方法
让不同进程能够使用同一块物理内存,就是共享内存的核心思想
1.2 共享内存的原理
由于进程具有独立性,内核数据结构包括对应的代码、数据和页表都是独立的,为了实现进程间通信需要以下过程:
1. OS申请一段空间
2. 将申请好的物理内存空间映射到一个进程地址空间
3. 将同一块物理内存空间映射到另一个需要通信的进程中
4. 通信结束之后取消进程和物理内存的映射关系,然后释放内存
- 我们把OS申请的空间叫做共享内存
- 进程和共享内存建立映射关系叫做挂接
- 取消进程和共享内存之间的映射关系叫做去关联
- 释放内存叫做释放共享内存
对共享内存的理解
在C语言中,我们可以使用malloc
在物理内存上申请空间,并把申请的空间经过页表映射到进程地址空间中,返回进程地址空间的指定地址,但是对于共享内存的通信方式,需要被专门设计。因为在同一时间,可能会有很多进程需要使用这种方式进行通信,所以一定会同时存在很多的共享内存,所以需要被管理起来,因此需要被专门设计
1.3 共享内存的使用
1.3.1 创建
我们使用shmget
系统调用来创建共享内存
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型: int shmget(key_t key, size_t size, int shmflg);
参数解释:key:是一个保证共享内存编号唯一性的标识符,为了让相同的进程能够看到同一个共享内存size:创建的共享内存的大小shmflg:创建共享内存的选项,通常我们使用两个:IPC_CREAT和IPC_EXCL
返回值:如果调用成功就返回一个合法的共享内存描述符shmid,如果调用失败就返回-1同时设置错误码
shmflg的选项含义:
IPC_CREAT
:如果对应key的共享内存不存在就创建,如果存在就获取对应的shmid
IPC_EXCL
:这个选项不能单独使用,和IPC_CREAT
配合使用,如果不存在就创建,存在就出错返回
key的形成方式
我们使用一个特定的函数ftok
来形成一个唯一的key
头文件:
#include <sys/type.h>
#include <sys/ipc.h>
函数原型:
key_t ftok(const char *pathname, int proj_id);
参数解释:pathname:这是一个指向用于生成键值的路径名的C字符串指针。通常会选择一个已经存在的文件作为这个路径名,因为它可以确保唯一性。通常情况下,可以选择程序中的某个文件作为路径名,这样就可以确保不同的程序使用不同的路径名生成不同的键值。proj_id:这是一个整数值,用于进一步区分不同的 IPC 对象。这个值在给定路径名的范围内必须唯一。通常情况下,可以使用与程序相关的整数值作为 proj_id,以确保不同的程序使用不同的 proj_id 生成不同的键值。
返回值: 如果调用成功就返回对应的key值,调用失败就返回-1,同时设置错误码
对key和shmid的理解
key是在OS层面的,给OS看的标定共享内存的标识符,shmid是应用层的,是给我们看的,标定共享内存的标识。key和shmid的关系就像是inode和fd的关系
举个例子:
/*comm.hpp*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <cstring>
#include <cerrno>
#include <iostream>#define PATHNAME "." // 使用当前目录作为项目目录
#define PROJ_ID 0x66 // 随机的项目id
#define MAX_SIZE 4096 // 创建的共享内存大小key_t getKey() // 封装获取key的函数
{key_t k = ftok(PATHNAME, PROJ_ID);if (k == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;exit(1);}return k;
}int getShmHelper(key_t key, int flags) // 封装通过key来获取shmid的函数
{int shmid = shmget(key, MAX_SIZE, flags);if(shmid < 0){std::cerr << errno << " : " << strerror(errno) << std::endl;exit(2);}return shmid;
}int getShm(key_t key) // 用于找到已经创建的共享内存的shmid,所以传入的选项只有IPC_CREAT,不关心以前是否创建
{return getShmHelper(key, IPC_CREAT);
}int createShm(key_t key) // 用于创建,所以传入的选项中有IPC_EXCL,表示如果遇到冲突就创建失败
{return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0666); // 0666表示创建的共享内存的权限
}
/*server.cc*/
#include "comm.hpp"int main()
{key_t k = getKey();printf("key:0x%x\n", k);int shmid = createShm(k);printf("%d\n", shmid);return 0;
}/*client.cc*/
#include "comm.hpp"int main()
{key_t k = getKey();printf("key:0x%x\n", k);int shmid = getShm(k);printf("%d\n", shmid);return 0;
}
1.3.2 控制
上述的代码编译出来的程序第一次运行没有任何问题,但是如果再次运行server就会发现:
这是因为创建的共享内存没有被释放,所以我们在使用完共享内存后需要释放
补充:查看IPC资源
我们可以通过命令
ipcs
系列指令来查看进程间通信相关信息
删除共享内存
ipcrm -m + shmid
当然除了命令删除之外,还可以使用系统调用来对共享内存进行删除/控制:shmctl
头文件:
#include <sys/ipc.h>
#include <sys/shm.h>
函数原型:
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数解释:shmid:要控制的共享内存的shmidcmd:要执行的命令,包括:IPC_STAT,IPC_SET,IPC_RMID,IPC_INFO,SHM_INFO,SHM_STAT,SHM_LOCK,SHM_UNLOCK.这里我们先不关注其他的,只关注IPC_RMID指令,这个指令是用来释放对应的shmid的buf:在其他指令中,有一些是需要获取到一些信息的,buf作为输出型参数来保存相关信息
返回值:对于释放共享内存来说,0表示成功,-1表示失败
使用shell脚本监视共享内存的情况
while :; do ipcs -m ; echo "##########################################"; sleep 1; done
#include "comm.hpp"int main()
{key_t k = getKey();printf("key:0x%x\n", k);int shmid = createShm(k);printf("%d\n", shmid);sleep(5);shmctl(shmid, IPC_RMID, nullptr); // 这里不需要获取信息,传入nullptr即可return 0;
}
1.3.3 关联
在本节开始,我们说过有一个过程叫做把物理内存和进程地址空间关联起来,我们会使用一个系统调用关联:shmat
,这里的at取attach的意义
头文件:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数解释:shmid:需要关联的shmidshmaddr:关联到进程地址空间的地址,我们绝大多数时间是不指定的,所以传nullptr即可shmflg:关联的选项,默认为0,表示读写权限
返回值:关联成功返回共享内存映射到进程地址空间中的起始地址,失败返回-1并设置错误码
void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if((long long)mem == -1L) // 这里由于我们的机器是64位的,所以一个地址占8个字节,所以需要转成long long类型判断是否正确关联{std::cout << errno << " : " << strerror(errno) << std::endl;exit(3);}return mem;
}
1.3.4 去关联
有关联,那么对应的就有去关联的操作,去关联使用的系统调用是shmdt
头文件:
#include <sys/types.h>
#include <sys/shm.h>
函数原型:
int shmdt(const void *shmaddr);
参数解释:shmaddr:需要去关联的进程地址空间
返回值:如果调用成功就返回0,否则就返回-1,同时设置错误码
void detachShm(void* start)
{if(shmdt(start) == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;}
}
1.3.6 实战——实现server和client端的通信
/*comm.hpp*/
#pragma once
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
#include <cstring>
#include <cerrno>
#include <iostream>#define PATHNAME "." // 使用当前目录作为项目目录
#define PROJ_ID 0x66 // 随机的项目id
#define MAX_SIZE 4096 // 创建的共享内存大小key_t getKey() // 封装获取key的函数
{key_t k = ftok(PATHNAME, PROJ_ID);if (k == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;exit(1);}return k;
}int getShmHelper(key_t key, int flags) // 封装通过key来获取shmid的函数
{int shmid = shmget(key, MAX_SIZE, flags);if(shmid < 0){std::cerr << errno << " : " << strerror(errno) << std::endl;exit(2);}return shmid;
}int getShm(key_t key) // 用于找到已经创建的共享内存的shmid,所以传入的选项只有IPC_CREAT,不关心以前是否创建
{return getShmHelper(key, IPC_CREAT);
}int createShm(key_t key) // 用于创建,所以传入的选项中有IPC_EXCL,表示如果遇到冲突就创建失败
{return getShmHelper(key, IPC_CREAT | IPC_EXCL | 0666); // 0666表示创建的共享内存的权限
}void delShm(int shmid)
{if(shmctl(shmid, IPC_RMID, nullptr) == -1)// 这里不需要获取信息,传入nullptr即可{std::cerr << errno << " : " << strerror(errno) << std::endl;}
}void *attachShm(int shmid)
{void *mem = shmat(shmid, nullptr, 0);if((long long)mem == -1L) // 这里由于我们的机器是64位的,所以一个地址占8个字节,所以需要转成long long类型判断是否正确关联{std::cerr << errno << " : " << strerror(errno) << std::endl;exit(3);}return mem;
}void detachShm(void* start)
{if(shmdt(start) == -1){std::cerr << errno << " : " << strerror(errno) << std::endl;}
}
/*server.cc*/
#include "comm.hpp"int main()
{key_t k = getKey(); // 通过共同的pathname和proj_id构建一个相互通信的进程之间的keyint shmid = createShm(k); // 通过创建的key创建一段共享内存char *start = (char *)attachShm(shmid); // 将这段共享内存和当前进程地址空间关联// 使用共享内存通信while (true){std::cout << "client say# " << start << std::endl; // 这里可以直接读取通信信息,因为地址相同struct shmid_ds ds;shmctl(shmid, IPC_STAT, &ds); // 获取shmid的相关信息printf("获取属性:size:%d,pid:%d,myself:%d", ds.shm_segsz, ds.shm_cpid);sleep(1);}delShm(shmid); // 使用完之后去关联delShm(shmid); // 谁创建的共享内存谁来释放return 0;
}
/*client*/
#include "comm.hpp"int main()
{key_t k = getKey(); // 通过共同的pathname和proj_id构建一个相互通信的进程之间的keyint shmid = getShm(k); // 通过创建的key获取指定的共享内存char *start = (char *)attachShm(shmid); // 将这段共享内存和当前进程地址空间关联// 使用共享内存通信const char *message = "hello server,我是另一个进程,正在和你通信"; // 通信信息pid_t id = getpid();int count = 1;while (true){sleep(5);snprintf(start, MAX_SIZE, "%s[pid:%d][消息编号:%d]", message, id, count++); // 直接讲通信信息写到start中即可}delShm(shmid); // 使用完之后去关联return 0;
}
1.3.7 共享内存的特点
共享内存的生命周期是随OS的,而不是随进程的,这是所有System V进程间通信的共性
共享内存的优点:共享内存是所有进程间通信速度是最快的,因为共享内存是被双方所共享,只要写入对方就能立即看到,能大大减少数据的拷贝次数。
但是综合考虑管道和共享内存,考虑键盘输入,和显示器输出,对于同一份数据:共享内存有几次数据拷贝,管道有几次数据拷贝
管道:需要通过键盘输入到自己定义的缓冲区char buffer[],将数据拷贝到buffer中,调用write接口在把buffer里的数据拷贝到管道里,
另一进程也有定义buffer缓冲区,调用read读取把数据从管道里读取到buffer里,在把数据显示到显示器上:
共享内存:通过映射关系
共享内存的缺点:不给我们进行同步和互斥的操作,没有对数据做任何保护。客户端和服务端没做保护,如果想做保护要用到信号量,对共享内存进行保护,写完通过读端进行读取。
2. 消息队列(不常用)
2.1 消息队列的概念
消息队列是OS提供的内核级队列,消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法,每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值
struct msqid_ds {struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messagesin queue */msglen_t msg_qbytes; /* Maximum number of bytesallowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */};
消息队列数据结构的第一个成员是msg_perm
,它和shm_perm
是同一个类型的结构体变量,ipc_perm
结构体的定义如下:
struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
2.2 消息队列的使用
2.2.1 获取消息队列
msgget
2.2.2 控制消息队列
msgctl
2.2.3 发送和接收数据
msgsnd
和msgrcv
3. 信号量
关于信号量的知识,我们将会在后面多线程的地方详细讲解,这里先进行一些概念的扫盲
3.1 信号量的概念扫盲
-
信号量的本质是一个计数器**,通常用来表示公共资源中,资源数的多少问题。信号量主要用于同步和互斥的。
-
公共资源:能被多个进程同时访问的资源,访问没有保护的公共资源可能会导致数据不一致问题。要让不同的进程看到同一份资源是为了通信,通信是为了让进程间实现协同,而进程之间具有独立性,所以为了解决独立性问题要让进程看到同一份资源,但是会导致数据不一致的问题。
-
临界资源:被保护起来的公共资源
-
临界区:进程要使用资源一定是该进程有对应的代码来访问这部分临界资源,这段代码就是临界区,但是多个进程看到同一份资源是少数情况,大部分申请自己的资源用自己的代码区访问。
-
非临界区:不访问公共资源的代码。
如何保护公共资源:互斥&&同步
互斥:由于各进程要求共享资源,而且有些资源需要互斥使用,因此各进程间竞争使用这些资源,进程的这种关系为进程的互斥
原子性:要么不做、要么做完两态的这种情况。比如支付转账
如果用全局的整数来替代信号量?
全局的整数在父子关系的进程上都看不到,要发生写时拷贝,而不同的进程更看不到,所以进程间想看到同一个计数器得让进程看到同一个计数器。
为什么要信号量?
当我们想要某种资源的时候可以通过信号量进行预;,共享资源被使用的方式:作为一个整体使用;划分成为一个一个的资源部分
3.2 内核中信号量的相关数据结构
struct semid_ds {struct ipc_perm sem_perm; /* Ownership and permissions */time_t sem_otime; /* Last semop time */time_t sem_ctime; /* Last change time */unsigned long sem_nsems; /* No. of semaphores in set */
};
信号量数据结构的第一个成员也是ipc_perm
类型的结构体变量,ipc_perm
结构体的定义如下:
struct ipc_perm {key_t __key; /* Key supplied to semget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */
};
3.3 信号量的PV操作
我们知道信号量本质上就是一个临界资源的计数器,有进程要使用这个临界资源,就会导致可用的临界资源数量减少,使用完之后归还临界资源会导致可用的临界资源增多
- P操作:向OS预定临界资源,会导致现有临界资源减少
- V操作:向OS归还临界资源,会导致现有临界资源增加
假设信号量为sem
,那么P操作相当于sem++
,V操作相当于sem--
,注意这里的++ 和 - -操作都是原子的
如果信号量的初始值是1就代表了访问公共资源作为一个整体来使用。二元信号量提供互斥功能
3.4 信号量相关函数
3.4.1 申请信号量
semget
3.4.2 控制信号量
semctl
3.4.3 信号量的操作PV
semop
关于system V标准的进程间通信的思考
我们可以发现,共享内存、消息队列、信号量接口相似度非常高,获取与删除,都是system V标准的进程间通信。
OS如何管理:先描述,在组织,对相关资源的内核数据结构做管理,对于共享内存、消息队列、信号量的第一个成员都是ipc_perm
:
struct ipc_perm {key_t __key; /* Key supplied to shmget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions + SHM_DEST andSHM_LOCKED flags */unsigned short __seq; /* Sequence number */
};
虽然内部的属性差别很大,但是维护它们的数据结构的第一个成员确实一样的,都是ipc_perm类型的成员变量,都可以通过key来标识唯一性。这样设计的好处:在操作系统内可以定义一个struct ipc_perm
类型的数组,此时每当我们申请一个IPC资源,就在该数组当中开辟一个这样的结构。((struct shmid_ds*)perms[0]
,强转,此时就可以访问其他剩下的属性)
本节完…
相关文章:

【Linux】进程间通信——system V 共享内存、消息队列、信号量
需要云服务器等云产品来学习Linux的同学可以移步/–>腾讯云<–/官网,轻量型云服务器低至112元/年,优惠多多。(联系我有折扣哦) 文章目录 写在前面1. 共享内存1.1 共享内存的概念1.2 共享内存的原理1.3 共享内存的使用1.3.1 …...

网络卡问题排查手段
问题 对后端来说,网络卡了问题,本身很难去排查,因为是 App 通过互联网连接服务 总结下,以往经验,网络卡,通常会有以下情况造成: 某地区网络问题某地区某运营商问题后端服务超载前端网络模块 …...

20240119-子数组最小值之和
题目要求 给定一个整数数组 arr,求 min(b) 的总和,其中 b 的范围涵盖 arr 的每个(连续)子数组。由于答案可能很大,因此返回答案模数 Example 1: Input: arr [3,1,2,4] Output: 17 Explanation: Subarrays are [3]…...

c# 释放所有嵌入资源, 到某个本地文件夹
版本号 .net 8 代码 using System.Reflection;namespace Demo;internal class Program {static void Main(string[] args){// 获取当前 执行exe 的目录 / 当前命令行所在的目录 var currentDir Directory.GetCurrentDirectory();Console.WriteLine(currentDir);Extract…...

Unity SnapScrollRect 滚动 匹配 列表 整页
展示效果 原理: 当停止滑动时 判断Contet的horizontalNormalizedPosition 与子Item的缓存值 相减,并得到最小值,然后将Content horizontalNormalizedPosition滚动过去 使用方式: 直接将脚本挂到ScrollRect上 注意:在创建Content子物体时…...

网络命令ping和telnet
1. 请解释ping和telnet的工作原理。 ping和telnet是两种常用的网络工具,其工作原理分别如下: ping: 目的:ping主要用于检查网络是否通畅以及测量网络连接速度。工作原理:ping是基于ICMP(Internet Control …...

ros2学习笔记-CLI工具,记录命令对应操作。
目录 环境变量turtlesim和rqt以初始状态打开rqt node启动节点查看节点列表查看节点更多信息命令行参数 --ros-args topic话题列表话题类型话题列表,附加话题类型根据类型查找话题名查看话题发布的数据查看话题的详细信息查看类型的详细信息给话题发布消息࿰…...

自然语言处理的发展
自然语言处理的发展大致经历了四个阶段:萌芽期、快速发展期、低谷的发展期和复苏融合期。 萌芽期(1956年以前):这个阶段可以看作自然语言处理的基础研究阶段。人类文明经过了几千年的发展,积累了大量的数学、语言学和…...

flink operator 拉取阿里云私有镜像(其他私有类似)
创建 k8s secret kubectl --namespace flink create secret docker-registry aliyun-docker-registry --docker-serverregistry.cn-shenzhen.aliyuncs.com --docker-usernameops_acr1060896234 --docker-passwordpasswd --docker-emailDOCKER_EMAIL注意命名空间指定你使用的 我…...

C语言算法赛——蓝桥杯(省赛试题)
一、十四届C/C程序设计C组试题 十四届程序C组试题A#include <stdio.h> int main() {long long sum 0;int n 20230408;int i 0;// 累加从1到n的所有整数for (i 1; i < n; i){sum i;}// 输出结果printf("%lld\n", sum);return 0; }//十四届程序C组试题B…...

【文本到上下文 #2】:NLP 的数据预处理步骤
一、说明 欢迎阅读此文,NLP 爱好者!当我们继续探索自然语言处理 (NLP) 的广阔前景时,我们已经在最初的博客中探讨了它的历史、应用和挑战。今天,我们更深入地探讨 NLP 的核心——数据预处理的复杂世界。 这篇文章是我们的“完整 N…...

Minio文件分片上传实现
资源准备 MacM1Pro 安装Parallels19.1.0请参考 https://blog.csdn.net/qq_41594280/article/details/135420241 MacM1Pro Parallels安装CentOS7.9请参考 https://blog.csdn.net/qq_41594280/article/details/135420461 部署Minio和整合SpringBoot请参考 https://blog.csdn.net/…...

C语言总结十一:自定义类型:结构体、枚举、联合(共用体)
本篇博客详细介绍C语言最后的三种自定义类型,它们分别有着各自的特点和应用场景,重点在于理解这三种自定义类型的声明方式和使用,以及各自的特点,最后重点掌握该章节常考的考点,如:结构体内存对齐问题&…...

解决Spring Boot应用打包后文件访问问题
在Spring Boot项目的开发过程中,一个常见的挑战是如何有效地访问和操作资源文件。这一挑战尤其显著当应用从IDE环境(如IntelliJ IDEA)迁移到被打包成JAR文件后的生产环境。开发者经常遇到的问题是,在IDE中运行正常的代码ÿ…...

循环神经网络的变体模型-LSTM、GRU
一.LSTM(长短时记忆网络) 1.1基本介绍 长短时记忆网络(Long Short-Term Memory,LSTM)是一种深度学习模型,属于循环神经网络(Recurrent Neural Network,RNN)的一种变体。…...

视频图像的color range简介
介绍 研究FFmpeg发现,在avcodec.h中有关于color的解释,主要有四个属性,primaries、transfer、space和range。 color primaries: 基于RGB空间对应的绝对颜色XYZ的变换,决定了最终三原色RGB分别是什么颜色;…...

tcp的三次握手
http 和 https 都是是基于 TCP 的请求,https 是 http 加上 tls 连接。TCP 是面向连接的协议。 对于 http1.1 协议chrome 限制在同一个域名下最多可以建立 6 个 tcp 连接,所以如果在同一个域名下,同时有超过 6 个请求发生,那么多余…...

unity 矩阵探究
public void MatrixTest1(){ ///Matrix4x4 是列矩阵,就是一个vector4表示一列,所以在c#中矩阵和Vector4只能矩阵右乘坐标。但是在shader中是矩阵左乘坐标,所以在shader中是行矩阵 Matrix4x4 moveMatrix1 new Matrix4x4(new Vector4(1,0,0,0)…...

MySQL---单表查询综合练习
创建emp表 CREATE TABLE emp( empno INT(4) NOT NULL COMMENT 员工编号, ename VARCHAR(10) COMMENT 员工名字, job VARCHAR(10) COMMENT 职位, mgr INT(4) COMMENT 上司, hiredate DATE COMMENT 入职时间, sal INT(7) COMMENT 基本工资, comm INT(7) COMMENT 补贴, deptno INT…...

Python项目——搞怪小程序(PySide6+Pyinstaller)
1、介绍 使用python编写一个小程序,回答你是猪吗。 点击“是”提交,弹窗并退出。 点击“不是”提交,等待5秒,重新选择。 并且隐藏了关闭按钮。 2、实现 新建一个项目。 2.1、设计UI 使用Qt designer设计一个UI界面,…...

MySQL练习题
参考:https://blog.csdn.net/paul0127/article/details/82529216 数据表介绍 --1.学生表 Student(SId,Sname,Sage,Ssex) --SId 学生编号,Sname 学生姓名,Sage 出生年月,Ssex 学生性别 --2.课程表 Course(CId,Cname,TId) --CId 课程编号,Cname 课程名称,TId 教师编号…...

vue-项目打包、配置路由懒加载
1. 简介 在现代前端开发中,Vue.js因其简洁、灵活和高效的特点,已经成为许多开发者的首选框架。 在Vue项目中,打包部署和路由懒加载是两个非常重要的环节。 打包Vue项目是为了将源代码转换为浏览器可以解析的JavaScript文件,以便…...

词语的魔力:语言在我们生活中的艺术与影响
Words That Move Mountains: The Art and Impact of Language in Our Lives 词语的魔力:语言在我们生活中的艺术与影响 Hello there, wonderful people! Today, I’d like to gab about the magical essence of language that’s more than just a chatty tool in o…...

android List,Set,Map区别和介绍
List 元素存放有序,元素可重复 1.LinkedList 链表,插入删除,非线性安全,插入和删除操作是双向链表操作,增加删除快,查找慢 add(E e)//添加元素 addFirst(E e)//向集合头部添加元素 addList(E e)//向集合…...

Mysql 编译安装部署
Mysql 编译安装部署 环境: 172.20.26.198(Centos7.6) 源码安装Mysql-5.7 大概步骤如下: 1、上传mysql-5.7.28.tar.gz 、boost_1_59_0.tar 到/usr/src 目录下 2、安装依赖 3、cmake 4、make && make install 5、…...

【目标检测】YOLOv5算法实现(九):模型预测
本系列文章记录本人硕士阶段YOLO系列目标检测算法自学及其代码实现的过程。其中算法具体实现借鉴于ultralytics YOLO源码Github,删减了源码中部分内容,满足个人科研需求。 本系列文章主要以YOLOv5为例完成算法的实现,后续修改、增加相关模…...

centos宝塔远程服务器怎么链接?
要远程连接CentOS宝塔服务器,可以按照以下步骤操作: 打开终端或远程连接工具,比如PuTTY。输入服务器的IP地址和SSH端口号(默认为22),点击连接。输入用户名和密码进行登录。 如果你已经安装了宝塔面板&…...

C语言练习day8
变种水仙花 变种水仙花_牛客题霸_牛客网 题目: 思路:我们拿到题目的第一步可以先看一看题目给的例子,1461这个数被从中间拆成了两部分:1和461,14和61,146和1,不知道看到这大家有没有觉得很熟…...

蓝凌OA-sysuicomponent-任意文件上传_exp-漏洞复现
0x01阅读须知 技术文章仅供参考,此文所提供的信息只为网络安全人员对自己所负责的网站、服务器等(包括但不限于)进行检测或维护参考,未经授权请勿利用文章中的技术资料对任何计算机系统进行入侵操作。利用此文所提供的信息而造成的…...

C#,入门教程(38)——大型工程软件中类(class)修饰词partial的使用方法
上一篇: C#,入门教程(37)——优秀程序员的修炼之道https://blog.csdn.net/beijinghorn/article/details/125011644 一、大型(工程应用)软件倚重 partial 先说说大型(工程应用)软件对源代码的文件及函数“…...