(学习笔记-进程管理)进程间有哪些通信方式?

每个进程的用户地址空间都是独立的,一般而言是不能互相访问的,但内核空间时每个进程都共享的,所以进程之间要通信必须通过内核

 


管道

在Linux命令中 [ | ]  这个竖线就是一个管道

$ ps auxf | grep mysql

它的功能是讲前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,从这功能描述,可以看出管道传输数据时单向的,如果想相互通信,需要创建两个管道才行。

同时,上面这种管道是没有名字的,所以 [ | ] 表示的管道称为匿名管道,用完了就销毁。

管道还有另外一个类型是命名管道,先需要通过 mkfifo 命令创建,并指定管道名字:

$ mkfifo myPipe

myPipe就是这个管道的名称,基于linux一切皆文件的理念,所以管道也是以文件的方式存在的,可以用ls看一下,这个文件类型是p,也就是pipe的意思:

$ ls -l
prw-r--r--. 1 root    root         0 Jul 17 02:45 myPipe

往myPipe这个管道写入数据:

$ echo "hello" > myPipe  // 将数据写进管道// 停住了 ...

操作之后,命令执行后就会停住,这是因为管道里的内容没有被读取,只有当管道里的数据被读完后,命令才可以正常退出。

于是,我们执行另外一个命令来读取这个管道里的数据:

$ cat < myPipe  // 读取管道里的数据
hello

可以看到,管道里的内容被读取出来了,并打印在了终端上,另一方面,echo 那个命令也正常退出了。

管道这种通信方式效率低,不适合进程间频繁地交换数据。当然,它的好处是简单,同时很容易得知管道里的数据是否被另一个进程读取了。

管道的原理

匿名管道的创建,需要通过下面这个系统调用:

int pipe(int fd[2])

这里表示创建一个匿名管道,并返回了两个描述符,一个是管道的读取端描述符 fd[0] ,另一个是管道的写入端描述符 fd[1] 。注意这个匿名管道是特殊的文件,只存在于内存,不存在于文件系统中。

 其实,所谓的管道,就是内核里面的一串缓存。从管道的一段写入的数据,实际上是缓存在内核中的,另一端读取,也就是从内核中读取这段数据。另外管道传输的数据是无格式的流且大小受限。

我们可以使用 fork 创建子进程,创建的子进程会复制父进程的文件描述符,这样就做到了两个进程各有两个[ fd[0] fd[1] ],两个进程就可以通过各自的fd写入和读取同一个管道文件实现跨进程通信了。

 管道只能一端写入,另一端读出,所以上面这种模式很容易造成混乱,因为父进程和子进程都可以同时写入,也都可以读出。那么,为了避免这种情况,通常的做法是:

  • 父进程关闭读取的 fd[0],只保留写入的 fd[1]
  • 子进程关闭写入的 fd[1],只保留读取的 fd[0]

 所以,如果需要双向通信,则应该创建两个管道。

到这里,仅仅解释了使用管道进行父进程和子进程之间的通信,但是在shell里并不是这样的。

在shell里面执行 A | B 命令的时候, A进程和B进程都是 shell 创建出来的进程, A 和 B 之间不存在父子关系,他俩的父进程都是shell。

 所以说,在 shell 里通过 [ | ] 匿名管道将多个命令连接在一起,实际上也就是创建了多个子进程,那么在我们编写shell脚本的时候,能使用一个管道就不要用多个管道,这样可以减少创建子进程的系统开销。

综上,对于匿名管道,它的通信范围是存在父子关系的进程。因为管道没有实体,也就是没有管道文件,只能通过 fork 来复制父进程 fd 文件描述符,来达到通信的目的。

对于命名管道,它可以在不相关的进程间也能相互通信。因为命令管道,提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件,就可以相互通信。

不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据的时候自然也是从内核中获取,同时通信数据都遵循先进先出的原则


消息队列

前面说到管道的通信方式是效率低的,因此管道不适合进程间频繁的交换数据。

对于这个问题,消息队列的通信模块就可以解决。比如A进程要给B进程发送消息,A进程把数据放在对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据就可以了。同理,B进程要给A进程发送消息也是如此。

消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),消息体是用户自定义的数据类型,消息的发送和接收方要约定好消息体的数据类型,所以每个消息体都是固定大小的存储块,不像管道是无格式的字节流数据。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。

消息队列生命周期随内核。如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,而前面提到的匿名管道的生命周期,是随进程的创建而建立,随进程的结束而摧毁

消息队列不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,同时所有队列所包含的全部消息体的总长度也是有上限的。在Linux内核中,会有两个宏定义 MSGMAX 和MSGMNB,它们以字节为单位,分别定义了一条消息的最大长度和一个队列的最大长度。

消息队列通信过程中,存在用户态和内核态之间的数据拷贝开销,因为进程写入数据到内核中的消息队列时,会发生从用户态拷贝到内核态的过程,同理另一进程读取内核中的消息数据时,会发生从内核态拷贝数据到用户态的过程。


共享内存

消息队列的读取和写入过程,都会有发生用户态与内核态之间的消息拷贝过程。那共享内存的方式,就很好的解决了这一问题。

现代操作系统,对于内存管理,采用的是虚拟内存技术,也就是每个进程都有自己独立的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。所以,即使进程 A 和 进程 B 的虚拟地址是一样的,其实访问的是不同的物理内存地址,对于数据的增删查改互不影响。

共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写入的东西,另外一个进程马上就能看到了,都不需要拷贝和传输,大大提高了进程间通信的速度。


信号量

使用内存共享通信方式,会带来新的问题:如果多个进程同时修改同一个共享内存,很有可能就冲突了。例如两个进程都同时写一个地址,那先写的那个进程会发现内容被别人覆盖了。

为了防止多进程竞争共享资源,而造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被一个进程访问。正好,信号量就实现了这一保护机制。

信号量其实是一个整型的计数器,主要用于实现进程间的互斥和同步,而不是用于缓存进程间通信的数据

信号量表示资源的数量,控制信号量的方式有两种原子操作:

  • 一个是 P操作,这个操作会把信号量减去 1 ,相减后如果信号量  < 0,则表明资源已被占用,进程需阻塞等待;相减后如果信号量 > = 0 ,则表明还有资源可使用,进程可正常继续执行。
  • 另一个是 V操作,这个操作会把信号量加上1,相加后如果信号量<=0 ,则表明当前有阻塞中的进程,于是会将该进程唤醒运行;相加后如果信号量 > 0 ,则表明当前没有阻塞中的进程;

P操作是用在进入共享资源之前,V操作是用在离开共享资源之后,这两个操作是必须成对出现的。

举个例子,如果要十点两个进程互斥访问共享内存,我们可以初始化信号量为 1

 具体的过程如下:

  • 进程 A 在访问共享内存前,先执行了 P 操作,由于信号量的初始值为 1 ,故在进程 A 执行 P操作后信号量变为 0 ,表示共享资源可用,于是进程 A 就可以访问共享内存。
  • 若此时,进程 B 也想访问共享内存,执行了 P 操作,结果信号量变为了 -1,就意味着临界资源已被占用,因此进程 B 被阻塞。
  • 直到进程 A 访问完共享内存,才会执行 V 操作,使得信号量恢复为 0 ,接着就会唤醒阻塞中的线程 B ,使得进程 B 可以访问共享内存,最后完成共享内存的访问后,执行 V 操作,使信号量恢复到初始值 1 。

可以发现,信号初始化为 1 ,就代表着互斥信号量,它可以保证共享内存在任何时刻只有一个进程在访问,这就很好的保护了共享内存。

另外,在多进程里,每个进程并不一定是顺序执行的,它们基本是以各自独立的、不可预知的速度向前推进,但有时候我们又希望多个进程能密切合作,以实现一个共同的任务。

例如,进程 A 是负责生产数据,而进程 B 是负责读取数据,这两个进程是相互合作、相互依赖的,进程 A 必须先生产了数据,进程 B 才能读取到数据,所以执行是有前后顺序的。

那么这时候,就可以用信号量来实现多进程同步的方式,我们可以初始化信号量为 0

 具体过程:

  • 如果进程B比进程A先执行了,那么执行到P操作时,由于信号量初始值为 0 ,故信号量会变为 -1,表示进程 A 还没生产数据,于是进程 B就阻塞等待;
  • 接着,当前进程 A 生产完数据后,执行了 V 操作,就会使得信号量变为 0 ,于是就会唤醒阻塞在 P 操作的进程 B;
  • 最后,进程 B 被唤醒后,意味着进程 A 已经产生了数据,于是进程 B 就可以正常读取数据了。

可以发现,信号初始化为 0 ,就表示这同步信号量,它可以保证进程A应在进程B之前执行。


信号

上面说的进程间的通信,都是常规状态下的工作模式。对于异常情况下的工作模式,就需要用 信号 的方式来通知进程

在Linux操作系统重,为了响应各种各样的事件,提供了几十种信号,分别代表不同的意义。我们可以通过 kill -l 命令,查看所有的信号:

$ kill -l1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX

运行在shell终端的进程,我们可以直接通过键盘输入某些组合键给进程发送信号。例如:

  • Ctrl + C 产生 SIGINT 信号,表示终止进程
  • Ctrl + Z 产生 SIGTSTP 信号,表示停止进程,但未结束

如果进程在后台运行,可以通过 kill 命令的方式给进程发送信号,但提前需要知道运行中的进程 PID 号,例如:

  • kill -9 1050,表示给 PID 为1050的进程发送 SIGKILL 信号,用来立即结束该进程;

所以,信号事件的来源主要有硬件来源 (如键盘 Ctrl +C)和软件来源(如 kill 命令)

信号是进程间通信机制中唯一的异步通信机制,因为可以在任何时候发送信号给某一程序,一旦有信号产生,我们就有下面几种,用户进程对信号的处理方式:

  • 执行默认操作。Linux对每种信号都规定了默认操作,例如,上面列表中的SIGTERM信号,就是终止进程的意思
  • 捕捉信号。我们可以为信号定义一个信号处理函数。当信号发生时,我们就执行相应的信号处理函数。
  • 忽略信号。当我们不希望处理某些信号的时候,就可以忽略该信号,不做任何处理。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL SIGSTOP ,它们用于在任何时候中断或结束某一进程。

Socket

前面提到的管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,那要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信。

实际上,Socket通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。

创建socket的系统调用:

int socket(int domain , int type ,int protocal)

三个参数分别代表:

  • domain参数用来指定协议族,比如 AF_INET 用于 IPV4、AF_INET6用于IPV6、AF_LOCAL/AF_UNIX用于本机
  • type 参数用来指定通信特性,比如 SOCK_STREAM 表示的是字节流,对应TCP、SOCK_DGRAM 表示的是数据报,对应UDP、SOCK_RAW表示的是原始套接字;
  • protocal 参数原本是用来指定通信协议的,但现在基本废弃。因为协议已通过前面两个参数指定完成,protocol 目前一般写成 0 即可

根据创建socket类型的不同,通信的方式页不同:

  • 实现 TCP 字节流通信: socket 类型是 AF_INET 和 SOCK_STREAM;
  • 实现 UDP 数据报通信:socket 类型是 AF_INET 和 SOCK_DGRAM;
  • 实现本地进程间通信: 「本地字节流 socket 」类型是 AF_LOCAL 和 SOCK_STREAM,「本地数据报 socket 」类型是 AF_LOCAL 和 SOCK_DGRAM。另外,AF_UNIX 和 AF_LOCAL 是等价的,所以 AF_UNIX 也属于本地 socket

针对TCP协议通信的socket编程模型

  •  服务器和客户端初始化 socket ,得到文件描述符;
  • 服务端调用 bind ,将绑定在 IP地址和端口
  • 服务器调用 listen ,进行监听
  • 服务器调用 accept ,等待客户端连接
  • 客户端调用 connect , 向服务器端的地址和端口发起连接请求
  • 服务端 accept 返回用于传输的 socket 的文件描述符
  • 客户端调用 write 写入数据;服务端调用 read 读取数据
  • 客户端断开连接时,会调用 close ,那么服务端 read 读取数据的时候,就会读取到 EOF ,待处理完数据后,服务器调用 close ,表示连接关闭。

需要注意的是,服务端调用 accpet 时,连接成功了会返回一个已完成连接的socket,后续用来传输数据

所以,监听的socket和真正用来传输数据的socket,是两个socket,一个叫做监听socket,一个叫做已完成连接socket

成功建立连接后,双方开始通过 read 和 write 函数读写数据,就像往一个文件流里面写东西一样


针对UDP协议通信的socket编程模型

 UDP是没有连接的,所以不需要三次握手,也就不需要像TCP调用listen和connect,但是UDP的交互仍然需要ip地址和端口号,因此也需要bind。

对于UDP而言,不需要维护连接,那么也就没有所谓的发送方和接收方,甚至都不存在客户端和服务端的概念,只要有一个socket多台机器就可以任意通信,因此每一个UDP的socket都需要bind。

另外,每次通信时,调用sendto和recvfrom,都要传入目标主机的IP地址和端口。

针对本地进程间通信的socket编程模型

本地socket被用于在同一台主机上进程间通信的场景:

  • 本地socket的编程接口和IPV4、IPV6套接字编程接口是一致的,可以支持 [字节流] 和 [数据报] 两种协议;
  • 本地 socket 的实现效率大大高于IPV4和IPV6的字节流、数据报socket实现;

对于本地字节流socket,其socket类型是AF_LOCAL 和 SOCK_STREAM

对于本地数据报socket,其socket类型是AF_LOCAL和SOCK_DGRAM

本地字节流socket 和本地数据报socket在bind的时候,不像TCP和UDP要绑定IP地址和端口号,而是绑定一个本地文件,这也是它们之间最大区别


总结

由于每个进程的用户空间都是独立的,不能互相访问,这时就需要借助内核空间来实现进程间通信,原因很简单,每个进程都是共享同一个内核空间

Linux内核提供了不少进程间的通信方式,其中最简单的就是管道,管道分为 匿名管道 和 命名管道。

匿名管道:他没有名字标识,匿名管道是特殊文件只存在于内存,没有存在于文件系统中,shell命令中的 [ | ]竖线就是匿名管道,通信的数据是无格式的流并且大小受限,通信的方式是单向的,数据只能在一个方向上流动,如果要双向通信,需要创建两个管道,再来匿名管道是只能用于存在父子关系的进程间通信,匿名管道的生命周期随着进程而建立,随着进程终止而消失。

命名管道:突破了匿名管道只能在亲缘关系进程间的通信限制,因为使用命名管道的前提,是需要在文件系统中创建一个类型为p的设备文件,那么毫无关系的进程就是通过这个设备文件进行通信。另外,不管是匿名管道还是命名管道,进程写入的数据都是缓存在内核中,另一个进程读取数据的时候自然也是从内核中获取,同时通信数据都遵循先进先出原则,不支持lseek之类的文件定位操作。

消息队列:克服了管道通信的数据是无格式的字节流的问题,消息队列实际上是保存在内核的 [消息链表] ,消息队列的消息体是可以用户自定义的数据类型,发送数据时,会被分成一个一个独立的消息体,当然接收数据时,也要与发送方发送的消息体的数据类型一致,这样才能保证读取的数据是正确的。消息队列通信的速度不是最及时的,毕竟每次数据的写入和读取都需要经过用户态与内核态之间的拷贝过程

共享内存:可以解决消息队列通信中用户态和内核态之间数据拷贝过程中带来的开销,它直接分配一个共享空间,每个进程都可以直接访问,就像访问自己的空间一样快捷方便,不需要陷入内核态或者系统调用,大大提高了通信的速度,享有最快的进程间通信方式之名。但是便捷高效的共享内存通信,带来新的问题:多进程竞争同一个资源会造成数据的错乱

所以,就需要信号量来维护共享资源,以确保任何时刻只能有一个进程访问共享资源,这种方式就是互斥访问。信号量不仅可以实现访问的互斥性,还可以实现进程间的同步,信号量其实是一个计数器,表示的是资源的个数,其值可以通过两个原子操作来控制,分别是 P 操作 和 V 操作

信号:与信号量名字相似但毫无关系,信号是异步通信机制,信号可以在应用进程和内核之间直接交互,内核也可以利用信号来通知用户空间的进程发生了哪些系统事件,信号事件的来源主要有硬件来源(如键盘 Ctrl + C)和软件来源(如 kill 命令),一旦有信号发生,进程有三种方式响应信号 1:执行默认操作、2:捕捉信号、3:忽略信号。有两个信号是应用进程无法捕捉和忽略的,即 SIGKILL 和 SIGSTOP ,这时为了方便我们能在任何时候结束或停止某个进程

Socket通信:用于不同主机或本地主机的进程间通信。可根据创建Socket的类型不同,分为三种常见的通信方式,一个是基于TCP协议的通信方式,一个是基于UDP协议的通信方式,一个是本地进程间通信方式。

同个进程下的线程之间都是共享进程的资源,只要是共享变量都可以做到线程间通信,比如全局变量,所以对于线程间关注的不是通信方式,而是关注多线程竞争共享资源的问题,信号量也同样可以在线程间实现互斥与同步:

  • 互斥的方式,可保证任意时刻只有一个线程访问共享资源;
  • 同步的方式,可保证线程 A 应在线程 B 之前执行

 

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

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

相关文章

如何使用 AT+WEBSERVER 指令实现自定义的 Webserver html 网页配网

开启 AT 固件中的 Webserver 指令和 FS 指令支持 乐鑫官网发布的默认通用 AT 固件不支持 webserver 配网功能&#xff0c; 需要用户自己搭建 esp-at 环境&#xff0c;并在 sdkconfig 中开启 webserver AT 指令 和 FS 指令的支持&#xff0c; 如下图所示&#xff1a; 测试 AT 固…

java中try-with-resources自动关闭io流

文章目录 java中try-with-resources自动关闭io流0 简要说明try-with-resources java中try-with-resources自动关闭io流 0 简要说明 在传统的输入输出流处理中&#xff0c;我们一般使用的结构如下所示&#xff0c;使用try - catch - finally结构捕获相关异常&#xff0c;最后不…

Mybatis-plus的使用

1、介绍 2、开发方式 3、MyBatisPlus-01-MybatisPlus入门案例_哔哩哔哩_bilibili&#xff0c;105,3.55分钟 Mybatis使用流程可以看这&#xff1a; 4、創建一張表&#xff1a; ​ 5、创建一个SpringBoot工程 ​ ​ 6、版本调成8 ​ 7、这里之只做数据层&#xff0c;不弄we…

SpringBoot 依赖管理

Spring Boot 依赖管理 在 Spring Boot 中&#xff0c;依赖管理是通过 Maven 或 Gradle 进行管理的。Spring Boot 提供了一种简化的方式来管理和引入依赖项&#xff0c;使得构建和管理项目变得更加容易。下面是一些关于 Spring Boot 依赖管理的基本信息和示例&#xff1a; 使用…

如何系统的学习单片机?

一、学习单片机需要的一些基础知识 &#xff08;1&#xff09;要具有一些模电、数电的知识&#xff08;不一定要精通&#xff0c;但基本至少要知道&#xff09; &#xff08;2&#xff09;具备C语言基础&#xff0c;有基础就可以入门了。数据结构、设计模式、汇编这些&#x…

管理类联考——逻辑——论证逻辑——汇总篇——真题和典例——假设

通用方法/没有特点 方法关系 199-2013-1-41——方法关系——方法有效或方法可行 新近一项研究发现&#xff0c;海水颜色能够让飓风改变方向&#xff0c;也就是说&#xff0c;如果海水变色&#xff0c;飓风的移动路径也会变向。这也就意味着科学家可以根据海水的“脸色”判断…

node.js

什么是Node.js Node.js 是一个免费的、开源的、跨平台的 JavaScript 运行时环境,使开发者可以搭建服务器端的JavaScript应用程序 概念: 使用Node.js编写后端程序 // 支持前端工程化 ​ 后端程序&#xff1a;提供接口和数据 &#xff0c;网页资源 ​ 前端工程化:对代码压缩&…

成员变量和局部变量的区别

局部变量成员变量 1、定义的位置不一样 在方法的内部&#xff0c;方法申明上&#xff08;形参&#xff09;。 声明在方法内、方法形参、代码块内、构造器形参、构造器内部的变量 在方法的外部&#xff0c;直接写在类当中 &#xff08;类中方法外的变量&#xff09; 2、作用范…

《孤注一掷》现实版:29万打水漂,华为程序员也躲不过的诈骗!!!

明天周五&#xff0c;约吗&#xff1f; 不管怎样&#xff0c;反正播妞已经订好了《孤注一掷》的电影票。不为别的&#xff0c;《孤注一掷》太敢拍了&#xff01;&#xff01;&#xff01; 美女荷官在线发牌&#xff0c;高知程序员在线养“猪”&#xff0c;诈骗头目“虔诚”拜佛…

UGUI组件EventTrigger用法

一.Unity编辑器中EventTrigger组件用法 1.添加事件类型 2.绑定gameObject指定组件的方法 3.方法执行逻辑 public class NewBehaviourScript : MonoBehaviour {public void PointerDown(){Debug.Log("Trigger PointerDown");} } 4.按下鼠标&#xff0c;绑定方法成功…

集成测试最全详解,看完必须懂了

目录 什么是集成测试 测试关注的重点 集成测试的三个级别 集成测试的模式 集成测试策略 自顶向下集成&#xff08;Top-Down Integration&#xff09; 三明治集成&#xff08;Sandwich Integration&#xff09; 基干集成&#xff08;Backbone Integration&#xff09; …

解决Windows:Call to undefined function exif_imagetype()

很明显,是php安装时没有打开某些扩展,以致不能执行exif_imagetype()这个方法,因此需要打开。 网上很多人说需要打开下面这两个扩展: extension=php_exif.dll extension=php_mbstring.dll 但只说对了一半,我一开始也按照网上文章说的打开这两个扩展,但是还是同样错误。…

[Vulnhub] matrix-breakout-2-morpheus

目录 <1> 信息收集 <2> getshell <3> Privilege Escalation&#xff08;提权&#xff09; <1> 信息收集 nmap -sP 192.168.236.0/24 扫描一下靶机ip 靶机ip: 192.168.236.154 nmap -A -p 1-65535 192.168.236.154 扫描一下靶机开放哪些服务 开放…

PyQt学习笔记-Windows系统版本兼容问题踩坑记录

1 Pyinstaller打包的exe在Win10上可以使用&#xff0c;在Win7上缺提示找不到dll。 错误信息&#xff1a; Traceback (most recent call last): File "main.py", line 4, in <module> ImportError: DLL load failed while importing QtWidgets: 找不到指定的…

2023全新UI好看的社区源码下载/反编译版

2023全新UI好看的社区源码下载/反编译版 这次分享一个RuleAPP二开美化版&#xff08;尊重每个作者版权&#xff09;&#xff0c;无加密可反编译版本放压缩包了&#xff0c;自己弄吧&#xff01;&#xff01;&#xff01; RuleAPP本身就是一款免费开源强大的社区&#xff0c;基…

海外应用商店优化实用指南之元数据的迭代更新

随着每天都有新应用程序加入App Store和Google Play商店&#xff0c;许多应用程序都会针对与我们相同的关键词&#xff0c;虽然我们的元数据保持不变&#xff0c;但竞争对手的应用会重新编入索引&#xff0c;最终导致我们的关键词排名随着时间的推移稳步下降。 1、迭代的重要性…

C语言——九九乘法表

//九九乘法表 //用程序做一个九九乘法表 #include<stdio.h> int main() {int i,j,result;printf("\n");for(i1;i<10;i){for(j1;j<i;j){resulti*j;printf(" %d*%d%-d",i,j,result);}printf(" \n");}}

【数据库】P0 创建数据库环境 MySQL + DataGrip

创建数据库环境 下载安装 MySQL下载安装 DataGrip 下载安装 MySQL Windows版本_MySQL 下载地址&#xff1a; https://dev.mysql.com/downloads/mysql/ 下载后依照默认顺序安装即可&#xff0c;本博文将讲述简约安装步骤&#xff1b; 如需详细安装步骤可见&#xff1a;https:/…

【设计模式】观察者模式

什么是观察者模式&#xff1f; 观察者模式&#xff08;又被称为发布-订阅&#xff08;Publish/Subscribe&#xff09;模式&#xff0c;属于行为型模式的一种&#xff0c;它定义了一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态…

STM32 CubeMX USB_MSC(存储设备U盘)

STM32 CubeMX STM32 CubeMX USB_MSC(存储设备U盘&#xff09; STM32 CubeMX前言 《使用内部Flash》——U盘一、STM32 CubeMX 设置USB时钟设置USB使能UBS功能选择FATFS功能 二、代码部分修改代码"usbd_storage_if.c"修改代码"user_diskio.c"main函数初始化插…