【Linux】进程控制2——进程等待(waitwaitpid)

1. 进程等待必要性

  • 我们知道,子进程退出,父进程如果不管不顾,就可能造成"僵尸进程”的问题,进而造成内存泄漏
  • 另外,进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经死去的进程。
  • 最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

2. 进程等待的方法

2.1 wait方法

在Linux中,wait函数是一个系统调用用于等待子进程的终止并获取其终止状态。该函数的原型如下所示:

#include<sys/types.h>
#include<sys/wait.h>pid_t wait(int*status);返回值:成功返回被等待进程pid,失败返回-1。参数:输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

        函数功能是:父进程一旦调用了wait就立即阻塞自己,由wait自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,wait就会一直阻塞在这里,直到有一个出现为止。

        当子进程终止后,wait函数会返回子进程的进程ID(PID),并将子进程的终止状态存储在指针status指向的变量中。

        status参数是一个指向整型变量的指针,用于存储子进程的终止状态。通过status可以获取子进程的退出状态、终止信号等信息如果不关心终止状态,可以将status设置为NULL。

wait函数返回的PID有以下几种可能的取值:

  • 如果成功等待到一个子进程的终止,返回子进程的PID。
  • 如果调用进程没有子进程,wait函数会返回-1
  • 如果调用进程被一个信号中断,wait函数会返回-1

 如何使用wait进行等待?

  1. 调用wait函数,进程等待子进程的退出。
  2. 当子进程退出后,会变成一个僵尸进程(短暂的存在不会造成什么影响),然后通过wait函数,进程状态从僵尸状态(Z)变成死亡状态(X)。
  3. 如果子进程没有退出,父进程必须阻塞等待,直到子进程变成Z,wait自动回收返回。
#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{pid_t fd = fork();if (fd == 0){sleep(5);std::cout << "child: " << getpid() << std::endl;exit(0);}if (fd > 0){sleep(1);std::cout << "parent: " << getpid() << std::endl;}sleep(50);return 0;
}

子进程退出后由于父进程没有等待回收,子进程变成僵尸进程: 

调用wait后,子进程就会回收释放了:

#include <iostream>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdlib>
int main()
{pid_t fd = fork();if (fd == 0){sleep(5);std::cout << "child: " << getpid() << std::endl;exit(0);}if (fd > 0){sleep(1);std::cout << "parent: " << getpid() << std::endl;wait(NULL);}sleep(50);return 0;
}

我们再深入理解一下

注:

  当父进程忘了用wait()函数等待已终止的子进程时,子进程就会进入一种无父进程的状态,此时子进程就是僵尸进程.

  wait()要与fork()配套出现,如果在使用fork()之前调用wait(),wait()的返回值则为-1,正常情况下wait()的返回值为子进程的PID.

  如果先终止父进程,子进程将继续正常进行,只是它将由init进程(PID为1)继承,当子进程终止时,init进程捕获这个状态.

  参数status用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。但如果我们对这个子进程是如何死掉毫不在意,只想把这个僵尸进程消灭掉,(事实上绝大多数情况下,我们都会这样想),我们就可以设定这个参数为NULL,就像下面这样:

pid = wait(NULL);

如果成功,wait会返回被收集的子进程的进程ID,如果调用进程没有子进程,调用就会失败,此时wait返回-1,同时errno被置为ECHILD。

  如果参数status的值不是NULL,wait就会把子进程退出时的状态取出并存入其中, 这是一个整数值(int),指出了子进程是正常退出还是被非正常结束的,以及正常结束时的返回值,或被哪一个信号结束的等信息。

        由于这些信息 被存放在一个整数的不同二进制位中,所以用常规的方法读取会非常麻烦,人们就设计了一套专门的宏(macro)来完成这项工作,下面我们来学习一下其中最常用的两个:

  • 1,WIFEXITED(status) :这个宏用来指出子进程是否为正常退出的,如果是,它会返回一个非零值。

        (请注意,虽然名字一样,这里的参数status并不同于wait唯一的参数——指向整数的指针status,而是那个指针所指向的整数,切记不要搞混了。)

  • 2, WEXITSTATUS(status): 当WIFEXITED返回非零值时,我们可以用这个宏来提取子进程的返回值,如果子进程调用exit(5)退出,WEXITSTATUS(status) 就会返回5;如果子进程调用exit(7),WEXITSTATUS(status)就会返回7。

        请注意,如果进程不是正常退出的,也就是说, WIFEXITED返回0,这个值就毫无意义。

我们看个例子 

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {pid_t id = fork();if (id == 0) {// 子进程printf("子进程开始执行\n");sleep(3);printf("子进程执行完毕\n");exit(0);} else if (id > 0) {// 父进程printf("父进程等待子进程终止\n");int status;pid_t child_pid = wait(&status);if (child_pid == -1){perror("wait");exit(1);}if (WIFEXITED(status)) {printf("子进程正常终止,退出状态:%d\n", WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("子进程被信号终止,信号编号:%d\n", WTERMSIG(status));}printf("父进程继续执行\n");} else {perror("fork");exit(1);}return 0;
}

在上面的示例中,父进程通过fork创建了一个子进程。

子进程会执行一段耗时的操作(这里使用sleep模拟),然后退出。

父进程调用wait函数等待子进程的终止,并获取子进程的终止状态。

最后,父进程继续执行。 

2.2.参数status

2.2.1、status 参数是位图结构 

wait 和 waitpid,都有这个 status 参数,如果传递 NULL,则表示不关心子进程的退出状态信息。否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。 

该参数是一个 输出型参数 (即通过调用该函数,从函数内部拿出来特定的数据)。

什么叫输出型参数?就是函数调用结束以后,会将参数的值写到这个变量里。

        换句话说,这个status是用来接收的,本质上不是用来传参的。

        我们把我们的status定义好了之后,放到该函数里,作为参数传递过去,函数调用完后,操作系统就会把status的值自动填充好,然后还给我们。实现的原理很简单,因为其用的是指针,传递的是变量的地址。倘若我们不关心这个status状态,那么直接传递NULL即可。

并且,status 参数是由操作系统填充的!是一个整数,该整数就是下面我们要详细研究的。

也就是说我们下面说的参数status不是wait唯一的参数——指向整数的指针status,而是那个指针所指向的整数

它虽然是一个 int 型整数,但是不能简单地将其看作整型,而是被当作一个 位图结构 看待。

不过,关于 status 我们只需要关心该整数的 低 16 个比特位!

我们不必去关心它的高 16 位,因为凭借低 16 位就足以判断了。

然而,整数的低 16 位,其中又可以分为 最低八位 和 次低八位(具体细节看图):

我们之研究status的低16比特位

最低八位(包括core dump)存储的是终止信号,次低八位存储的是退出状态

 2、次低八位:拿子进程退出码

重点:通过提取 status 的次低八位,就可以拿到子进程的退出码。 

我们需要使用下面的代码来解析:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>int main () 
{pid_t id = fork();if (id == 0) {int cnt = 5;   // 循环5次// childwhile (cnt--) {// 五秒之内运行状态printf("我是子进程,我正在运行... Pid: %d\n", getpid());sleep(1);// 五秒之后子进程终止}exit(233);   // 方便辨识,退出码我们设置为233,这是我们的预期结果}else {printf("我是父进程: pid: %d,我将耐心地等待子进程!\n", getpid());// ***** 使用waitpid进行进程等待int status = 0;  // 接收 waitpid 的 status 参数pid_t ret = waitpid(id, &status, 0);if (ret > 0) {   // 等待成功printf ("等待成功,ret: %d, 我所等待的子进程退出码: %d\n", ret,(status>>8)&0xFF);}}
}

我们说了,status 并不是整体使用的,而是区域性使用的,我们要取其次低八位。

我们可以用 位操作 来完成,将 status 右移八位再按位与上 \textrm{0xFF},即 (status>>8)&0xFF ,就可以提取到 status 的次低八位了。

 

3、 最低七位:提取子进程的退出信号

重点:通过提取 status 的最低七位,就可以拿到子进程的退出信号。

我们的 status 的低八位用于表示处理异常的地方,其中有 1 位是 core dump,我们下面讲。

除去 core dump,剩余七位用于进程中的退出信号,这就是 最低七位。

进程退出,如果异常退出,是因为这个进程收到了特定的信号。

我们虽然还没有开始讲解信号,但是我们前几张就介绍了 kill -9 这样的杀进程操作。

这个 -9 我们当时说了,就是一个信号,发送该信号也确实可以终止进程。

刚才我们讲的 wait/waitpid 和次低八位的时侯,都是关于进程的 正常退出。

如果进程 异常退出 呢?

我们来模拟一下进程的异常退出。

结果:

因为子进程是个死循环,父进程又调了 waitpid,导致父进程一直在 "阻塞式" 地等待子进程。

父进程在等待子进程期间什么都没有干,就搬了张板凳坐在那等子进程死。

信号是可以杀掉进程的,我们现在主动输入 kill -9:

此时我们就成功拿到了子进程的退出信号,9 是因为我们输入的信号就是 9。

此时父进程看到子进程寄了,终于可以不用等了,可以给子进程收尸了

还是那句话,代码跑完结果是什么已经不重要了,我们最关心的是因为什么原因退出的。

当进程收到信号时,就代表进程异常了。进程程出,如果是异常退出,是因为该进程收到了特定的信号。其实除了 9 号信号还有很多信号,输入 kill -l 就可以查看这些;

总结:退出信号代表进程是否异常,退出码代表进程在退出之时代码对还是不对。

4、进程退出的宏

我们今天写的代码,是通过位操作去截 status 得到退出码和退出信号的。

实际上,你也可以不用位操作,因为  已经给我们提供了一些宏供我们直接调用。

它们是 WEXITSTATUS 和 WIFEXITED,在这之前,我们再思考一个问题:

思考:一个进程退出时,可以拿到退出码和退出信号,我们先看谁?

        一旦程序发现异常,我们只关心退出信号,退出码没有任何意义。

        所以,我们先关注退出信号,如果有异常了我们再去关注退出码

WEXITSTATUS 宏用于查看进程的退出码,若非 0,提取子进程退出码。

WEXITSTATUS(status)

WIFEXITED 宏用于查看进程是否正常退出,如果是正常终止的子进程返回状态,则为真。

WIFEXITED(status)

结果是

当然了,如果你压根就不关注推出信息和退出码,你直接把 status 设置为 NULL 就行。

2.3. waitpid方法

waitpid函数是Linux中用于等待指定子进程终止的系统调用

        与wait函数类似,waitpid函数也可以用于获取子进程的终止状态。

#include <sys/types.h>
#inlclude <sys/wait.h>pid_ t waitpid(pid_t pid, int *status, int options);

        函数功能是:父进程一旦调用了waitpid就立即阻塞自己,由waitpid自动分析是否当前进程的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,waitpid就会收集这个子进程的信息,并把它彻底销毁后返回;如果没有找到这样一个子进程,waitpid就会一直阻塞在这里,直到有一个出现为止。 

从本质上讲,系统调用waitpid和wait的作用是完全相同的,但waitpid多出了两个可由用户控制的参数pid和options,从而为我们编程提供了另一种更灵活的方式。

下面我们就来详细介绍一下这两个参数:

pid:

从参数的名字pid和类型pid_t中就可以看出,这里需要的是一个进程ID。但当pid取不同的值时,在这里有不同的意义。

  1.   pid>0时,只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
  2.   pid=-1时,等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。
  3.   pid=0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
  4.   pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。

options:

options提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项,这是两个常数,可以用"|"运算符把它们连接起来使用,比如:

  ret = waitpid(-1,  NULL,  WNOHANG | WUNTRACED);

如果我们不想使用它们,也可以把options设为0,如:

     ret = waitpid(-1,  NULL,  0);

如果使用了WNOHANG参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。

而WUNTRACED参数,由于涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多费笔墨了,有兴趣的读者可以自行查阅相关材料。

看到这里,聪明的读者可能已经看出端倪了:wait不就是经过包装的waitpid吗?

没错,察看<内核源码目录>/include/unistd.h文件349-352行就会发现以下程序段:

static inline pid_t wait(int * wait_stat){return waitpid(-1,wait_stat,0);}

返回值和错误

waitpid的返回值比wait稍微复杂一些,一共有3种情况:

  1. 当正常返回的时候,waitpid返回收集到的子进程的进程ID;
  2. 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  3. 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

当pid所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid就会出错返回,这时errno被设置为ECHILD;

#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{pid_t pc, pr;pc = fork();if (pc < 0) /* 如果fork出错 */{printf("Error occured on forking.\n");}else if (pc == 0) /* 如果是子进程 */{sleep(10); /* 睡眠10秒 */exit(0);}/* 如果是父进程 */do {pr = waitpid(pc, NULL, WNOHANG); /* 使用了WNOHANG参数,waitpid不会在这里等待 */if (pr == 0) /* 如果没有收集到子进程 */{printf("No child exited\n");sleep(1);}} while (pr == 0); /* 没有收集到子进程,就回去继续尝试 */if (pr == pc){printf("successfully get child %d\n", pr);}elseprintf("some error occured\n");}

父进程经过10次失败的尝试之后,终于收集到了退出的子进程。

因为这只是一个例子程序,不便写得太复杂,所以我们就让父进程和子进程分别睡眠了10秒钟和1秒钟,代表它们分别作了10秒钟和1秒钟的工作。父子进程都有工作要做,父进程利用工作的简短间歇察看子进程的是否退出,如退出就收集它。

提示:可以尝试在最后一个例子中把pr=waitpid(pc, NULL, WNOHANG); 改为pr=waitpid(pc, NULL, 0);或者pr=wait(NULL);看看运行结果有何变化?(修改后的结果使得父进程将自己阻塞,直到有子进程退出为止!)

3.进程的阻塞等待方式

3.1.阻塞等待

  •  如果子进程没有退出,而父进程在进行执行waitpid进行等待,阻塞等待。
  • 大部分IO类的函数例如scanf各种各样的接口,只要涉及IO的或多或少会可能出现阻塞的状态。
  • 现在所用的大部分接口都是阻塞接口(逻辑简单,容易实现)
  •  **阻塞等待(Blocking Wait)**在编程中通常指的是一个线程或进程在等待某个条件满足或某个操作完成之前,会暂停执行其他任务,处于等待状态。这种状态会一直持续,直到等待的条件满足或操作完成,线程或进程才会继续执行后续的任务。在Java中,阻塞等待常用于多线程编程中,用于线程之间的同步和通信。

3.2.进程阻塞:

  1. 把进程的R状态设置为S状态
  2. 把进程的PCB从运行队列移动到等待队列中,不再被调度,而是等待
  3. 本质上是等待某种条件发生。
  • 软件条件满足(子进程退出)
  • 硬件资源就绪(scanf键盘输入数据发生)
#include <sys/wait.h>                                                                                                                  
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("子进程已运行, pid is : %d\n", getpid());sleep(5);exit(257);}else {int status = 0;pid_t ret = waitpid(-1, &status, 0);//父进程在这里阻塞式等待,等待5Sprintf("这是等待的测试\n");if (WIFEXITED(status) && ret == pid){printf("等待子进程5秒成功,子进程返回代码为:%d.\n", WEXITSTATUS(status));}else {printf("等待失败, return.\n");return 1;}}return 0;
}

4.进程的非阻塞等待方式: 

在子进程运行期间,父进程除了等待子进程或者是休眠,能不能干点其他的事情❓

        当然可以,在父进程等待,阻塞状态。可以通过设置options来让父进程干点事情。不阻塞等待而是非阻塞等待。

4.1.什么又是非阻塞等待呢?用代码该怎么去实现呢?

        **非阻塞等待(Non-blocking Wait)**则与阻塞等待相反。当线程或进程在等待某个条件满足或某个操作完成时,它不会暂停执行其他任务,而是会继续执行后续的任务。也就是说,即使等待的条件还没有满足或操作还没有完成,线程或进程也不会被阻塞,而是会继续执行其他的操作。

        通过设置options的宏值WNOHANG(wait no hang 等待没有阻塞 = 非阻塞等待)

在计算机中,"HANG" 通常指的是程序或系统出现无响应或停顿的状态,也就是常说的“卡住”或“死机”。

        当程序或系统由于某种原因(如资源锁定、死循环、死锁或外部系统交互问题等)而无法继续正常执行时,就可能会出现"HANG"的情况。这种情况下,用户可能无法与程序或系统进行交互,需要等待程序或系统恢复正常或进行重启操作。

        另外,在一些特定的语境下,"HANG" 也可能被用来描述服务器或数据库的某些服务出现故障或无法访问的情况,这也可以被视为一种"宕机"现象。在这种情况下,"HANG" 指的是服务器或数据库的服务因为某种原因而停止响应或无法提供服务。

具体操作

  1. options这个参数只要一设置就会出现非阻塞等待。
  2. 设置waitpid的WNOHANG本质上是检测一次进程的状态变化。
  3. 调用一次waipid就检测一次。每次调用都是检测,多次调用多次检测。
  4. 非阻塞等待调用多次waitpid,调用waitpid检测是否退出等待过程无问题,只是子进程还未终止,需要等待下次等待。

综上:非阻塞等待的时候 + 循环 = 非阻塞轮询

#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include<stdlib.h>
int main()
{pid_t pid;pid = fork();if (pid < 0) {printf("%s fork error\n", __FUNCTION__);return 1;}else if (pid == 0) { //childprintf("child is run, pid is : %d\n", getpid());sleep(5);exit(1);}else {int status = 0;pid_t ret = 0;do{ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待if (ret == 0) {printf("child is running\n");}sleep(1);} while (ret == 0);if (WIFEXITED(status) && ret == pid) {printf("wait child 5s success, child return code is :%d.\n", WEXITS    TATUS(status));}else {printf("wait child failed, return.\n");return 1;}}return 0;
}

5.阻塞等待VS非阻塞等待

场景:张三找李四求助帮他复习期末考试。张三在李四的楼下等待李四就绪。

5.1.非阻塞等待:

张三每隔几分钟就给李四打电话询问他是否就绪了,张三在没有打电话的时间看书/游戏/抖音

  1. 就绪的过程本质就是非阻塞等待。
  2. 张三非阻塞等待李四过程 == 函数调用
  3. 张三给李四打电话 == 函数传参
  4. 李四说等着没好 == 函数的返回值
  5. 每次函数调用的本质是检测李四的状态(是否就绪)
  6. 立刻有返回值,多次等待,多次返回。
  • pid_ t waitpid(pid_t pid, int *status, WNOHANG);
  • pit_t == 0 :检测是成功的,只不过子进程还没退出,需要你下一次进行重复等待。
  • pit_t > 0 :等待成功,子进程退出了,并且父进程回收成功。
  • pit_t < 0 :等待失败。

5.2.阻塞等待:

        张三一直给李四打着电话,直到李四就绪,期间张三一直等待李四就绪,不敢别的事情。一直检测李四的状态(不就绪,就不返回)
      一直等待。直到子进程终止才返回。

  1. pid_ t waitpid(pid_t pid, int *status, 0);
  2. pit_t > 0 :等待成功,子进程退出了,并且父进程回收成功。
  3. pit_t < 0 :等待失败。

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

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

相关文章

香港户口需要什么条件?有学历要求吗?最新香港落户途径详解!

香港户口需要什么条件&#xff1f;有学历要求吗&#xff1f;最新香港落户途径详解&#xff01; 由于香港放开“落户”窗口&#xff0c;想去香港发展或者想拿香港身份的朋友都想抓住这个机会赶紧申请。 只是&#xff0c;香港户口办理是有条件的&#xff0c;而且有学历要求&…

VScode中连接并使用docker容器

前提条件&#xff1a; 1.在windows下安装Docker Desktop(方法可见下面的教程) Docker Desktop 安装使用教程-CSDN博客 2.在vscode安装3个必备的插件 3.先在ubuntu中把docker构建然后运行 4.打开vscode&#xff0c;按下图顺序操作 调试好之后上传到git上&#xff0c;然后后面…

《人人都是产品经理》笔记1:什么是产品?怎么入行?

《人人都是产品经理》笔记1&#xff1a;什么是产品&#xff1f;怎么入行&#xff1f; 产品是什么&#xff1f;产品经理、产品管理&#xff1f;真的想做产品经理吗&#xff1f;全书结构示意图 从写这篇文章开始&#xff0c;是个人第二次对该书进行阅读&#xff0c;在此进行个人的…

[Python学习篇] Python输入

关键字 input 语法&#xff1a;input("提示信息") 特点 当程序执行到input&#xff0c;等待用户输入&#xff0c;输入完成之后才能继续向下执行。input接收用户输入后&#xff0c;一般存储到变量中&#xff0c;方便使用。input会把接收到的任意用户输入的数据都当做…

老杨说运维 | 基于数据驱动的智观能力建设(文末附现场视频)

本期回顾来自擎创科技创始人兼CEO杨辰的现场演讲 青城山脚下的滔滔江水奔涌而过&#xff0c;承载着擎创一往无前的势头&#xff0c;共同去向未来。2024年6月&#xff0c;双态IT成都用户大会擎创科技“数智化可观测赋能双态运维”专场迎来了完满的收尾。 “没有2200年前李冰率众…

Java集合自测题

文章目录 一、说说 List , Set , Map 三者的区别&#xff1f;二、List , Set , Map 在 Java 中分别由哪些对应的实现类&#xff1f;底层的数据结构&#xff1f;三、有哪些集合是线程不安全的&#xff1f;怎么解决呢&#xff1f;四、HashMap 查询&#xff0c;删除的时间复杂度五…

word怎么单页横向设置(页码不连续版)

打开word&#xff0c;将光标放在第一页的最后位置。 然后点击布局下的分隔符&#xff0c;选择下一页。 将光标放在第二页的开头&#xff0c;点击布局下的纸张方向&#xff0c;选择横向即可。 效果展示。 PS&#xff1a;如果那一页夹在两页中间&#xff0c;那么在…

Python发送Outlook邮件的步骤流程有哪些?

Python发送Outlook邮件的技巧&#xff1f;如何使用Python发信&#xff1f; 在Python中使用SMTP协议发送邮件到Outlook邮箱是一项常见的任务。AokSend将介绍如何通过Python编程语言实现这一过程&#xff0c;从准备工作到实际发送邮件的具体步骤。 Python发送Outlook邮件&#…

构建汛期智慧水利新生态:EasyCVR视频汇聚监控综合管理方案解析

一、项目背景与目标 随着我国水利事业的不断发展&#xff0c;水利设施的管理与维护工作愈发重要。随着夏季汛期的到来&#xff0c;水利管理工作面临着巨大的挑战。为确保水利设施的安全运行&#xff0c;及时应对可能出现的汛情&#xff0c;建设一套高效、智能的视频监控可视化…

wms海外仓系统排名分析:哪个才更适合中小海外仓

对中小型海外仓来说&#xff0c;想在竞争激烈的市场下生存&#xff0c;关键就在于是否能改变自己落后的仓储管理模式&#xff0c;提升客户满意度和业务流畅度。 wms海外仓系统作为这一领域的关键工具&#xff0c;可以说在很大程度上决定了海外仓的业务标准化程度发展。不过现在…

MFC为什么说文档在数据的保存和给用户提供数据之间划分了清晰的界限?

MFC MFC&#xff08;Microsoft Foundation Classes&#xff09;是微软为Windows应用程序开发提供的一套C类库&#xff0c;它在设计上强调了"文档-视图"&#xff08;Document-View&#xff09;架构。这种架构将文档&#xff08;Document&#xff09;与用户界面&#…

六西格玛培训都培训哪些内容 ?

天行健六西格玛培训的内容通常涵盖多个方面&#xff0c;旨在帮助学员全面理解和应用六西格玛管理方法。以下是详细的培训内容概述&#xff1a; 一、六西格玛基础知识 引入六西格玛的概念、原理和历史&#xff0c;包括DMAIC&#xff08;定义、测量、分析、改进、控制&#xff0…

方法分享 |公网IP怎么指定非433端口实现https访问

公网IP可以通过指定非443端口实现HTTPS访问。在网络配置中&#xff0c;虽然HTTPS协议默认使用443端口&#xff0c;但没有规定不能在其他端口上实施HTTPS服务。使用非标准端口进行HTTPS通信需要正确配置服务器和SSL证书&#xff0c;并确保客户端能够连接到指定的端口。下面说明如…

本地生活服务电商平台小程序源码系统 含完整的安装代码包+搭建教程

系统概述 本地生活服务电商平台小程序源码系统&#xff0c;是一款集成了商品展示、在线交易、服务预约、优惠券发放、会员管理、订单处理、即时通讯等多种功能于一体的综合性解决方案。它旨在为本地商家提供一个高效、便捷的线上经营平台&#xff0c;同时为消费者带来流畅的使…

⌈ 传知代码 ⌋ MonoCon解读与复现

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

uni-date-picker 禁用日期功能

在uni-datetime-picker组件中 calendar.vue <template><view class"uni-calendar" mouseleave"leaveCale"><view v-if"!insert && show" class"uni-calendar__mask" :class"{uni-calendar--mask-show:an…

在k8s中部署Logstash多节点示例(超详细讲解)

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《洞察之眼&#xff1a;ELK监控与可视化》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、Logstash简介 2、在K8s中部署Logstash多节点实例…

ubuntu的home内存不足的解决办法(win和ubuntu双系统)

这种解决办法前提是windows和ubuntu双系统 首先在windows系统上创建一个空的硬盘分区 然后在ubuntu系统上把这个空的硬盘放在主目录里 然后可以把东西存在这个文件夹中 如下图&#xff0c;但实际主目录的内存没有变&#xff0c;以后存东西就在这个文件夹里面就好了 具体操作…

资源不是问题,极空间全自动小雅Alist以及Emby全家桶部署教程,同时实现自动更新

资源不是问题&#xff0c;极空间全自动小雅Alist以及Emby全家桶部署教程&#xff0c;同时实现自动更新 哈喽小伙伴&#xff0c;我是Stark-C~ 在上次更新了极空间虚拟机教程之后&#xff0c;终于有小伙伴催更了小雅Alist的搭建方案。作为当前市面最强、最大、最全的影视资源合…

基于springboot高校就业招聘系统的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;就业咨询管理&#xff0c;毕业去向管理&#xff0c;简历管理&#xff0c;管理员管理&#xff0c;基础数据管理 辅导员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;就业咨询管理…