当前位置: 首页 > article >正文

Linux之守护进程

在Linux系统中,进程一般分为前台进程、后台进程和守护进程3类。

一 守护进程

定义:

1.守护进程是在操作系统后台运行的一种特殊类型的进程,它独立于前台用户界面,不与任何终端设备直接关联。这些进程通常在系统启动时启动,并持续运行直到系统关闭,或者它们完成其任务并自行终止。守护进程通常用于服务请求、管理系统或执行周期性任务。

2.守护进程是后台运行的一种特殊进程,它通常不与用户直接交互,也不受用户登录或注销的影响。它是为了完成某种特定的任务而运行的,例如提供服务或者监控系统状态。

3.Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。

4.守护进程通常在系统启动时开始运行,并以超级用户权限运行。它们经常需要访问特殊的资源或使用特殊的端口(1-1024)。守护进程会一直运行直到系统关机,除非被强制终止。它们的父进程是init进程,因为它们的真正父进程在fork出子进程后就先于子进程exit退出了。因此,它们是由init继承的孤儿进程。由于守护进程是非交互式程序且没有控制终端,任何输出都需要特殊处理。通常,守护进程的名称以d结尾,例如sshd、xinetd和crond。

二控制终端

控制终端是与会话关联的终端设备,它是用户输入和输出的通道。进程通常通过其控制终端与用户交互,接收输入和向用户显示输出。对于守护进程来说,它必须从任何控制终端中脱离,确保其独立于任何用户会话在后台运行,这样才能保证其稳定性和安全性,不受用户直接控制和会话结束等事件的影响

三 编程规则

1.创建子进程并结束父进程

在UNIX和类UNIX系统中,进程是通过复制(使用fork())创建的。守护进程需要在后台独立运行。

2.设置会话ID

setsid()创建一个新会话,并使调用它的进程成为新会话的领导者,这样做的主要目的是让守护进程摆脱原来的控制终端。这样,守护进程就不会接收到终端发出的任何信号,例如挂断信号(SIGHUP),从而保证其运行不受前台用户操作的影响。

​​​​​​​3.第二次fork()

使得守护进程不是会话领导,没有获取控制终端的能力,避免意外获取控制终端。

​​​​​​​4.更改工作目录

将工作目录更改到根目录(/),主要是为了避免守护进程继续占用其启动时的文件系统。这对于可移动的或网络挂载的文件系统尤其重要,确保这些文件系统不需要时可以被卸载。

​​​​​​​5.重设文件权限掩码

调用umask(0)确保守护进程创建的文件权限不受继承的umask值的影响,守护进程可以更精确地控制其创建的文件和目录的权限。

​​​​​​​6.关闭文件描述符

守护进程通常不需要标准输入、输出和错误文件描述符,因为它们不与终端交互。关闭这些不需要的文件描述符可以避免资源泄露,提高守护进程的安全性和效率。

​​​​​​​7.处理信号

SIGHUP和SIGTERM信号。

SIGHUP:虽然守护进程和终端断开,但仍然有可能收到其它进程或内核发来的SIGHUP信号,守护进程不应该因为它而终止。

SIGTERM:SIGTERM信号是终止信号,用于请求守护进程优雅地终止。通过命令行执行kill <pid>命令可以发送SIGTERM信号,接收到这个信号之后,守护进程终止子进程,并清理回收资源,最后退出。

​​​​​​​​​​​​​​8.执行具体任务

这一步是守护进程的核心,它开始执行为其设计的特定功能,如监听网络请求、定期清理文件系统、执行系统备份等

四 相关系统调用和库函数

1.setsid

#include <sys/types.h>

#include <unistd.h>

/**

 * @brief 如果调用进程不是进程组的领导者,则创建一个新的会话。创建者是新会话的领导者

 *

 * @return pid_t 成功则返回调用进程的新会话ID,失败则返回(pid_t)-1,并设置errno以指明错误原因

 */

pid_t setsid(void);

2.umask

#include <sys/types.h>

#include <sys/stat.h>

/**

 * @brief 设置调用进程的文件模式创建掩码。

 *

 * @param mask 掩码。是一个八进制数,它指定哪些权限位在文件或目录创建时应被关闭。我们通过umask(0)确保守护进程创建的文件和目录具有最开放的权限设置。

 * @return mode_t 这个系统调用必然成功,返回之前的掩码值

 */

mode_t umask(mode_t mask);

3.chdir

#include <unistd.h>

/**

 * @brief 更改调用进程的工作目录

 *

 * @param path 更改后的工作路径

 * @return int 成功返回0,失败返回-1,并设置errno

 */

int chdir(const char *path);

4.openlog

#include <syslog.h>

/**

 * @brief 为程序开启一个面向系统日志的连接

 *

 * @param ident 每条消息的字符串前缀,按照惯例通常设置为程序名称

 * @param option option:指定控制 openlog 和后续 syslog 调用的标志。常见标志包括:

 *  LOG_PID:在每条日志消息中包含进程ID

 *  LOG_CONS:如果无法将消息发送到日志守护进程,则直接将消息写入控制台。

 *  LOG_NDELAY:立即打开与系统日志守护进程的连接。

 *  LOG_ODELAY:延迟打开与系统日志守护进程的连接,直到实际写入日志时。

 *  LOG_PERROR:将日志消息同时输出到标准错误输出。

 * @param facility facility:指定日志消息的来源类别,用于区分系统不同部分的日志消息。包括:

 *  LOG_AUTH:认证系统

 *  LOG_CRONcron at 调度守护进程

 *  LOG_DAEMON:系统守护进程

 *  LOG_KERN:内核消息

 *  LOG_LOCAL0 LOG_LOCAL7:本地使用

 *  LOG_MAIL:邮件系统

 *  LOG_SYSLOGsyslog 自身的消息

 *  LOG_USER:用户进程

 *  LOG_UUCPUUCP 子系统

 */

void openlog(const char *ident, int option, int facility);

5.syslog

#include <syslog.h>

/**

 * @brief 生成一条日志消息

 *

 * @param priority 由一个facility和一个level值或操作得到,如果未指定facility,则使用openlog指定的默认值,如果上文没有调用openlog(),则将使用默认值LOG_USERlevel取值如下

 *  LOG_EMERG(系统无法使用)表示系统已经不可用,通常用于严重的紧急情况。例如:系统崩溃或关键硬件故障。

 *  LOG_ALERT(必须立即采取行动)表示必须立即采取措施解决的问题。例如:磁盘空间用尽或数据库崩溃。

 *  LOG_CRIT(严重条件)表示严重的错误或问题,但不需要立即采取行动。例如:应用程序的某个重要功能失败。

 *  LOG_ERR(错误条件)表示一般错误情况,需要注意和修复。例如:无法打开文件或网络连接失败。

 *  LOG_WARNING(警告条件)表示潜在问题或警告,建议检查,但不会立即影响系统功能。例如:磁盘空间接近用尽或配置文件缺失。

 *  LOG_NOTICE(正常但重要的情况)表示正常运行过程中需要特别注意的事件。例如:系统启动或关闭成功。

 *  LOG_INFO(信息性消息)表示一般信息,用于记录正常操作的事件。例如:用户登录或定时任务完成。

 *  LOG_DEBUG(调试级别消息)表示详细的调试信息,通常用于开发和调试阶段。例如:函数调用跟踪或变量值变化。

 * @param format 类似于printf()的格式化字符串

 * @param ... 可变参数,可以传递给格式化字符串

 */

void syslog(int priority, const char *format, ...);

6.closelog

#include <syslog.h>

/**

 * @brief 关闭用于写入系统日志的文件描述符

 *

 */

void closelog(void);

7.sysconf

#include <unistd.h>

/**

 * @brief 获取运行时配置信息

 *

 * @param name 配置名称,取值太多,可以通过 man 3 sysconf 自行查阅,我们只用到_SC_OPEN_MAX,记录了当前进程可以打开的文件描述符的最大数量

 * @return long 配置的值

 */

long sysconf(int name);

五.举例

1.创建一个守护进程

//创建一个守护进程#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>int main(void)
{pid_t pid;// 步骤一:创建一个新的进程pid = fork();//父进程直接退出if (pid > 0){exit(0);}if (pid == 0){// 步骤二:调用setsid函数摆脱控制终端setsid();// 步骤三:更改工作目录chdir("/");// 步骤四:重新设置umask文件源码umask(0);// 步骤五:0 1 2 三个文件描述符for (int i = 1; i < 4; i++){close(i);}while (1){}}return 0;
}

2 创建daemon_test.c

//创建daemon_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <syslog.h>
#include <string.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <errno.h>pid_t pid;int is_shutdown = 0;/*** @brief 信号处理函数* * 该函数根据接收到的信号类型执行不同的操作。它主要用于处理SIGHUP和SIGTERM信号,* 并在接收到未处理的信号时记录信息。当接收到SIGTERM信号时,它会设置一个全局标志以启动退出流程,* 并向某个特定的子进程发送SIGTERM信号要求其终止。* * @param sig 接收到的信号编号,用于决定执行哪种处理逻辑*/
void signal_handler(int sig)
{switch (sig){case SIGHUP:// 当接收到SIGHUP信号时,记录警告信息syslog(LOG_WARNING, "收到SIGHUP信号...");break;case SIGTERM:// 当接收到SIGTERM信号时,记录通知信息并启动退出流程syslog(LOG_NOTICE, "接收到终止信号,准备退出守护进程...");// 记录发送终止信号给子进程的操作syslog(LOG_NOTICE, "向子进程发送SIGTERM信号...");// 设置全局变量以启动退出流程is_shutdown = 1;// 向指定的子进程发送SIGTERM信号kill(pid, SIGTERM);break;default:// 当接收到未处理的信号时,记录信息syslog(LOG_INFO, "Received unhandled signal");}
}/*** 将当前进程转化为守护进程的函数*/
void my_daemonize()
{pid_t pid;// fork() 调用会在父进程中返回子进程的 PID,在子进程中返回 0。如果发生错误,则返回 -1。//1.守护进程第一步创建子进程,并结束父进程pid = fork();if (pid < 0)exit(EXIT_FAILURE);if (pid > 0)exit(EXIT_SUCCESS);//2设置会话IDif (setsid() < 0)exit(EXIT_FAILURE);// 处理 SIGHUP、SIGTERM 信号signal(SIGHUP, signal_handler);signal(SIGTERM, signal_handler);//3 第二次fork()pid = fork();if (pid < 0)exit(EXIT_FAILURE);if (pid > 0)exit(EXIT_SUCCESS);// 4.重置umask,调用umask(0)确保守护进程创建的文件权限不受继承的umask值的影响,守护进程可以更精确地控制其创建的文件和目录的权限umask(0);// 5.将工作目录切换为根目录,chdir("/");// 6.关闭所有打开的文件描述符for (int x = 0; x <= sysconf(_SC_OPEN_MAX); x++){close(x);}openlog("this is our daemonize process: ", LOG_PID, LOG_DAEMON);
}int main()
{my_daemonize();while (1){pid = fork();if (pid > 0){syslog(LOG_INFO, "守护进程正在监听服务端进程...");// 等待任何子进程终止,不挂起父进程waitpid(-1, NULL, 0);if (is_shutdown) {syslog(LOG_NOTICE, "子进程已被回收,即将关闭syslog连接,守护进程退出");closelog();exit(EXIT_SUCCESS);}syslog(LOG_ERR, "服务端进程终止,3s后重启...");sleep(3);}else if (pid == 0){syslog(LOG_INFO, "子进程fork成功");syslog(LOG_INFO, "启动服务端进程");char *path = "/home/atguigu/daemon_and_multiplex/tcp_server";char *argv[] = {"my_tcp_server", NULL};errno = 0;/*** 执行新的程序* * 使用execve函数来替换当前进程的执行程序* * @param path 指向新程序的路径字符串* @param argv 指向参数的指针数组,这些参数将传递给新程序* @param NULL 表示环境变量的数组为空* * 注意:这个函数不会返回错误码,因为如果新程序成功启动,当前进程的执行流将被新程序取代*/execve(path, argv, NULL);execve(path, argv, NULL);char buf[1024];sprintf(buf, "errno: %d", errno);syslog(LOG_ERR, "%s", buf);syslog(LOG_ERR, "服务端进程启动失败");exit(EXIT_FAILURE);}else{syslog(LOG_ERR, "子进程fork失败");}}return EXIT_SUCCESS;
}

3. ​​​​​​​创建tcp_server.c

#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include <signal.h>
#include <syslog.h>// 声明服务器的socket文件描述符,用于监听客户端连接
int sockfd;/*** 处理僵尸进程的信号处理函数* 当接收到SIGCHLD信号时,调用本函数回收退出的子进程*/
void zombie_dealer(int sig)
{pid_t pid;int status;char buf[1024];memset(buf, 0, 1024);// 一个SIGCHLD可能对应多个子进程的退出// 使用while循环回收所有退出的子进程,避免僵尸进程的出现while ((pid = waitpid(-1, &status, WNOHANG)) > 0){if (WIFEXITED(status)){sprintf(buf, "子进程: %d 以 %d 状态正常退出,已被回收\n", pid, WEXITSTATUS(status));syslog(LOG_INFO, "%s", buf);}else if (WIFSIGNALED(status)){sprintf(buf, "子进程: %d 被 %d 信号杀死,已被回收\n", pid, WTERMSIG(status));syslog(LOG_INFO, "%s", buf);}else{sprintf(buf, "子进程: %d 因其它原因退出,已被回收\n", pid);syslog(LOG_WARNING, "%s", buf);}}
}/*** 处理SIGTERM信号的函数* 当服务端接收到SIGTERM信号时,调用本函数进行清理工作并退出*/
void sigterm_handler(int sig) {syslog(LOG_NOTICE, "服务端接收到守护进程发出的SIGTERM,准备退出...");syslog(LOG_NOTICE, "释放sockfd");close(sockfd);syslog(LOG_NOTICE, "释放syslog连接,服务端进程终止");closelog();// 退出exit(EXIT_SUCCESS);
}/*** 从客户端读取数据然后写回的函数* @param argv 客户端的文件描述符*/
void read_from_client_then_write(void *argv)
{int client_fd = *(int *)argv;ssize_t count = 0, send_count = 0;char *read_buf = NULL;char *write_buf = NULL;char log_buf[1024];memset(log_buf, 0, 1024);read_buf = malloc(sizeof(char) * 1024);// 判断内存是否分配成功if (!read_buf){sprintf(log_buf, "服务端pid: %d: 读缓存创建异常,断开连接\n", getpid());syslog(LOG_ERR, "%s", log_buf);shutdown(client_fd, SHUT_WR);close(client_fd);return;}// 判断内存是否分配成功write_buf = malloc(sizeof(char) * 1024);if (!write_buf){sprintf(log_buf, "服务端pid: %d: 写缓存创建异常,断开连接\n", getpid());syslog(LOG_ERR, "%s", log_buf);free(read_buf);shutdown(client_fd, SHUT_WR);close(client_fd);return;}while ((count = recv(client_fd, read_buf, 1024, 0))){if (count < 0){syslog(LOG_ERR, "server recv error");}sprintf(log_buf, "服务端pid: %d: reveive message from client_fd: %d: %s", getpid(), client_fd, read_buf);syslog(LOG_INFO, "%s", log_buf);memset(log_buf, 0, 1024);sprintf(write_buf, "服务端pid: %d: reveived~\n", getpid());send_count = send(client_fd, write_buf, 1024, 0);}sprintf(log_buf, "服务端pid: %d: 客户端client_fd: %d请求关闭连接......\n", getpid(), client_fd);syslog(LOG_NOTICE, "%s", log_buf);sprintf(write_buf, "服务端pid: %d: receive your shutdown signal\n", getpid());send_count = send(client_fd, write_buf, 1024, 0);sprintf(log_buf, "服务端pid: %d: 释放client_fd: %d资源\n", getpid(), client_fd);syslog(LOG_NOTICE, "%s", log_buf);shutdown(client_fd, SHUT_WR);close(client_fd);free(read_buf);free(write_buf);return;
}/*** 主函数* 负责初始化服务器socket,绑定端口,监听客户端连接* 并为每个客户端连接创建一个子进程进行处理*/
int main(int argc, char const *argv[])
{int temp_result;struct sockaddr_in server_addr, client_addr;memset(&server_addr, 0, sizeof(server_addr));memset(&client_addr, 0, sizeof(client_addr));// 声明IPV4通信协议server_addr.sin_family = AF_INET;// 我们需要绑定0.0.0.0地址,转换成网络字节序后完成设置server_addr.sin_addr.s_addr = htonl(INADDR_ANY);// 端口随便用一个,但是不要用特权端口server_addr.sin_port = htons(6666);// 创建server socketsockfd = socket(AF_INET, SOCK_STREAM, 0);// 绑定地址temp_result = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));// 进入监听模式temp_result = listen(sockfd, 128);socklen_t cliaddr_len = sizeof(client_addr);// 注册信号处理函数,处理SIGCHLD信号,避免僵尸进程出现signal(SIGCHLD, zombie_dealer);// 处理SIGTERM函数,以优雅退出signal(SIGTERM, sigterm_handler);char log_buf[1024];memset(log_buf, 0, 1024);// 接受client连接while (1){int client_fd = accept(sockfd, (struct sockaddr *)&client_addr, &cliaddr_len);pid_t pid = fork();if (pid > 0){sprintf(log_buf, "this is father, pid is %d, continue accepting...\n", getpid());syslog(LOG_INFO, "%s", log_buf);memset(log_buf, 0, 1024);// 父进程不需要处理client_fd,释放文件描述符,使其引用计数减一,以便子进程释放client_fd后,其引用计数可以减为0,从而释放资源close(client_fd);}else if (pid == 0){// 子进程不需要处理sockfd,释放文件描述符,使其引用计数减一close(sockfd);sprintf(log_buf, "与客户端 from %s at PORT %d 文件描述符 %d 建立连接\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), client_fd);syslog(LOG_INFO, "%s", log_buf);memset(log_buf, 0, 1024);sprintf(log_buf, "新的服务端pid为: %d\n", getpid());syslog(LOG_INFO, "%s", log_buf);memset(log_buf, 0, 1024);// 读取客户端数据,并打印到 stdoutread_from_client_then_write((void *)&client_fd);// 释放资源并终止子进程close(client_fd);exit(EXIT_SUCCESS);}}return 0;
}

4​​​​​​​.创建tcp_client.c

#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>// 定义错误处理宏
#define handle_error(cmd, result) \if (result < 0)               \{                             \perror(cmd);              \return -1;                \}/*** 从服务器读取数据的线程函数* @param argv 线程参数,这里是一个指向socket文件描述符的指针* @return NULL*/
/*
/*
该函数是用于多线程编程中,作为线程的入口函数。函数名为 read_from_server,是一个返回值为 void* 类型的函数,接受一个 void* 类型参数(通常用于传递线程所需数据)。
常用于创建线程时指定的执行函数,实现从服务器读取数据的功能。
*/
void *read_from_server(void *argv)
{/*这段代码将 `argv` 指针指向的内容解释为一个整型指针,并将其解引用后赋值给 `sockfd`。  通俗来说,就是从 `argv` 中取出一个整数作为套接字文件描述符。  简要说明:  
- `argv` 是一个 `void*` 类型的指针(通常用于传递参数)  
- `(int *)argv` 将其转换为整型指针  
- `*(int *)argv` 取出该指针指向位置的整数值  
- `sockfd` 保存了这个值,表示一个 socket 文件描述符*/int sockfd = *(int *)argv;/*这段代码定义了一个名为 `read_buf` 的字符指针变量,并将其初始化为 `NULL`。表示该指针当前不指向任何有效的内存空间,常用于后续动态分配内存或作为缓冲区读取字符串数据前的准备工作。*/char *read_buf = NULL;ssize_t count = 0;/*这段代码动态分配了一个大小为 1024 字节的内存空间,用于存储字符数据。- `malloc(sizeof(char) * 1024)`:申请 1024 个 `char` 类型大小的连续内存空间;
- `read_buf =`:将分配到的内存首地址赋值给指针 `read_buf`。*/read_buf = malloc(sizeof(char) * 1024);if (!read_buf){perror("malloc client read_buf");return NULL;}while (count = recv(sockfd, read_buf, 1024, 0)){if (count < 0){perror("recv");}fputs(read_buf, stdout);}printf("收到服务端的终止信号......\n");free(read_buf);return NULL;
}/*** 向服务器写入数据的线程函数* @param argv 线程参数,这里是一个指向socket文件描述符的指针* @return NULL*/
void *write_to_server(void *argv)
{int sockfd = *(int *)argv;char *write_buf = NULL;ssize_t send_count;write_buf = malloc(sizeof(char) * 1024);if (!write_buf){printf("写缓存申请异常,断开连接\n");/*```c
shutdown(sockfd, SHUT_WR);
```**功能解释:**该函数调用关闭套接字 `sockfd` 的写入端,表示本端不再发送数据,但仍可接收数据。  - `SHUT_WR`:表示关闭写操作  
- `sockfd`:是待操作的套接字描述符*/shutdown(sockfd, SHUT_WR);perror("malloc client write_buf");return NULL;}/*这段代码的功能是:  
从标准输入(如键盘)读取一行文本,最多读取 `1023` 个字符(加上自动添加的 `\0`),并存储到 `write_buf` 指向的缓冲区中。  简要说明如下:  
- `fgets` 是用于读取字符串的标准库函数  
- `write_buf` 是目标缓冲区  
- `1024` 表示最大读取长度(包括结束符 `\0`)  
- `stdin` 表示输入来源为标准输入流*/while (fgets(write_buf, 1024, stdin) != NULL){send(sockfd, write_buf, 1024, 0);if (send_count < 0){perror("send");}}printf("接收到命令行的终止信号,不再写入,关闭连接......\n");shutdown(sockfd, SHUT_WR);free(write_buf);return NULL;
}/*** 主函数* @param argc 命令行参数个数* @param argv 命令行参数列表* @return 程序退出状态*/
/*
这段代码是C语言程序的主函数定义,表示程序的入口点。  - `int main(int argc, char const *argv[])` 表示主函数带有两个参数:  - `argc` 是命令行参数的数量(argument count)  - `argv` 是命令行参数的数组(argument vector),每个元素是一个字符串  
- 函数返回一个整型值,通常用于表示程序退出状态(0 表示成功,非0表示错误)
*/
int main(int argc, char const *argv[])
{int sockfd, temp_result;pthread_t pid_read, pid_write;struct sockaddr_in server_addr;memset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;// 连接本机 127.0.0.1server_addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);// 连接端口 6666server_addr.sin_port = htons(6666);// 创建socketsockfd = socket(AF_INET, SOCK_STREAM, 0);handle_error("socket", sockfd);// 连接servertemp_result = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));handle_error("connect", temp_result);// 启动一个子线程,用来读取服务端数据,并打印到 stdoutpthread_create(&pid_read, NULL, read_from_server, (void *)&sockfd);// 启动一个子线程,用来从命令行读取数据并发送到服务端pthread_create(&pid_write, NULL, write_to_server, (void *)&sockfd);// 主线程等待子线程退出pthread_join(pid_read, NULL);pthread_join(pid_write, NULL);printf("关闭资源\n");close(sockfd);return 0;
}

相关文章:

Linux之守护进程

在Linux系统中&#xff0c;进程一般分为前台进程、后台进程和守护进程3类。 一 守护进程 定义: 1.守护进程是在操作系统后台运行的一种特殊类型的进程&#xff0c;它独立于前台用户界面&#xff0c;不与任何终端设备直接关联。这些进程通常在系统启动时启动&#xff0c;并持…...

LiquiGen流体导入UE

导出ABC 导出贴图 ABC导入Houdini UE安装SideFX_Labs插件 C:\Users\Star\Documents\houdini20.5\SideFXLabs\unreal\5.5 参考: LiquiGenHoudiniUE血液流程_哔哩哔哩_bilibili...

使用react进行用户管理系统

今天通了一遍使用react进行用户管理系统的文档&#xff0c;以及跟随步骤实现了一遍&#xff0c;我大概梳理一下实现思路。 首先我们构建基本用户管理应用&#xff0c;需要数据库存储个人资料&#xff0c;我们先去supabase注册然后创建自己的数据库然后设置密码&#xff0c;然后…...

SpringBoot的java应用中,慢sql会导致CPU暴增吗

是的&#xff0c;在 Spring Boot 的 Java 应用中&#xff0c;慢 SQL 同样可能导致 CPU 暴增。虽然数据库服务器的 CPU 通常是主要压力点&#xff0c;但应用服务器&#xff08;Java 进程&#xff09;的 CPU 也可能间接受到影响&#xff0c;具体原因和机制如下&#xff1a; 1. 数…...

Ubuntu下编译mininim游戏全攻略

目录 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09;二、编译mininim 软件三、将mininim打包给另一个Ubuntu系统使用四、安卓手机运行mininim 一、安装mininim 软件所依赖的库&#xff08;重点是allegro游戏引擎库&#xff09; 1. 用apt-get…...

uniapp uni-id Error: Invalid password secret

common文件夹下uni-config-center文件夹下新建uni-id,新建config.json文件 复制粘贴以下代码&#xff0c;不要自己改&#xff0c;格式容易错 {"passwordSecret": [{"type": "hmac-sha256","version": 1}], "passwordStrength&qu…...

用 Appuploader,让 iOS 上架流程真正“可交接、可记录、可复用”:我们是这样实现的

你可能听说过这样一类人&#xff1a;上线必找他&#xff0c;证书只有他有&#xff0c;Transporter 密码在他电脑上&#xff0c;描述文件什么时候过期&#xff0c;只有他知道。 如果你团队里有这样一位“发布大师”&#xff0c;他可能是个英雄——但也是个单点风险源。 我们团…...

第十二节:第三部分:集合框架:List系列集合:特点、方法、遍历方式、ArrayList集合的底层原理

List系列集合特点 List集合的特有方法 List集合支持的遍历方式 ArrayList集合的底层原理 ArrayList集合适合的应用场景 代码&#xff1a;List系列集合遍历方式 package com.itheima.day19_Collection_List;import java.util.ArrayList; import java.util.Iterator; import jav…...

【办公类-18-07】20250527屈光检查PDF文件拆分成多个pdf(两页一份,用幼儿班级姓名命名文件)

背景需求&#xff1a; 今天春游&#xff0c;上海海昌公园。路上保健老师收到前几天幼儿的屈光视力检查单PDF。 她说&#xff1a;所有孩子的通知都做在一个PDF里&#xff0c;我没法单独发给班主任。你有什么办法拆开来&#xff1f; 我说&#xff1a;“没问题&#xff0c;问deep…...

AI Agent的“搜索大脑“进化史:从Google API到智能搜索生态的技术变革

AI Agent搜索革命的时代背景 2025年agent速度发展之快似乎正在验证"2025年是agent元年"的说法&#xff0c;而作为agent最主要的应用工具之一(另外一个是coding)&#xff0c;搜索工具也正在呈现快速的发展趋势。Google在2024年12月推出Gemini Deep Research&#xff0…...

Arduino学习-跑马灯

1、效果 2、代码 /**** 2025-5-30 跑马灯的小程序 */ //时间间隔 int intervaltime200; //初始化函数 void setup() {// put your setup code here, to run once://设置第3-第7个引脚为输出模式for(int i3;i<8;i){pinMode(i,OUTPUT);} }//循环执行 void loop() {// put you…...

python创建args命令行分析

这段代码是一个使用 Python 的 argparse 模块创建命令行界面的示例。它定义了一系列的命令行参数和子命令&#xff0c;通常用于构建和管理软件项目或版本控制系统中的操作。以下是对代码的逐行分析&#xff1a; 1初始化 ArgumentParser parser argparse.ArgumentParser(forma…...

2. 手写数字预测 gui版

2. 手写数字预测 gui版 背景1.界面绘制2.处理图片3. 加载模型4. 预测5.结果6.一点小问题 背景 做了手写数字预测的模型&#xff0c;但是老是跑模型太无聊了&#xff0c;就配合pyqt做了一个可视化界面出来玩一下 源代码可以去这里https://github.com/Leezed525/pytorch_toy拿 …...

js数据类型有哪些?它们有什么区别?

js数据类型共有8种,分别是undefined,null,boolean,number,string,Object,symbol,bigint symbol和bigint是es6中提出来的数据类型 symbol创建后独一无二不可变的数据类型,它主要是为了解决出现全局变量冲突的问题 bigint 是一种数字类型的数据,它可以表示任意精度格式的整数,…...

大模型应用开发第五讲:成熟度模型:从ChatGPT(L2)到未来自主Agent(L4)

大模型应用开发第五讲&#xff1a;成熟度模型&#xff1a;从ChatGPT&#xff08;L2&#xff09;到未来自主Agent&#xff08;L4&#xff09; 资料取自《大模型应用开发&#xff1a;动手做AI Agent 》。 查看总目录&#xff1a;学习大纲 关于DeepSeek本地部署指南可以看下我之…...

特别篇-产品经理(三)

一、市场与竞品分析—竞品分析 1. 课后总结 案例框架&#xff1a;通过"小新吃蛋糕"案例展示行业分析方法&#xff0c;包含四个关键步骤&#xff1a; 明确目标行业调研确定竞品分析竞争策略输出结论 1&#xff09;行业背景分析方法 PEST分析法&#xff1a;从四个…...

IP地址扫描 网络状态监测 企业网络管理 免安装,企业级 IP 监控防未授权接入

各位网络小卫士们&#xff01;今天咱来聊聊一款超厉害的局域网IP地址扫描工具——IPScaner V1.22。这玩意儿就像网络世界的大侦探&#xff0c;能快速识别网络里设备的状态和资源分布。下面咱就好好唠唠它的那些事儿。 软件获取夸克网盘下载 先说说它的核心功能。第一个是IP…...

【unity游戏开发——编辑器扩展】AssetDatabase公共类在编辑器环境中管理和操作项目中的资源

注意&#xff1a;考虑到编辑器扩展的内容比较多&#xff0c;我将编辑器扩展的内容分开&#xff0c;并全部整合放在【unity游戏开发——编辑器扩展】专栏里&#xff0c;感兴趣的小伙伴可以前往逐一查看学习。 文章目录 前言一、AssetDatabase常用API1、创建资源1.1 API1.2 示例 …...

BLE协议全景图:从0开始理解低功耗蓝牙

BLE(Bluetooth Low Energy)作为一种针对低功耗场景优化的通信协议,已经广泛应用于智能穿戴、工业追踪、智能家居、医疗设备等领域。 本文是《BLE 协议实战详解》系列的第一篇,将从 BLE 的发展历史、协议栈结构、核心机制和应用领域出发,为后续工程实战打下全面认知基础。 …...

【机器学习基础】机器学习入门核心算法:GBDT(Gradient Boosting Decision Tree)

机器学习入门核心算法&#xff1a;GBDT&#xff08;Gradient Boosting Decision Tree&#xff09; 1. 算法逻辑2. 算法原理与数学推导2.1 目标函数2.2 负梯度计算2.3 决策树拟合2.4 叶子权重计算2.5 模型更新 3. 模型评估评估指标防止过拟合 4. 应用案例4.1 金融风控4.2 推荐系…...

基于开源AI大模型AI智能名片S2B2C商城小程序源码的销售环节数字化实现路径研究

摘要&#xff1a;在数字化浪潮下&#xff0c;企业销售环节的转型升级已成为提升竞争力的核心命题。本文基于清华大学全球产业研究院《中国企业数字化转型研究报告&#xff08;2020&#xff09;》提出的“提升销售率与利润率、打通客户数据、强化营销协同、构建全景用户画像、助…...

Spring Cache核心原理与快速入门指南

文章目录 前言一、Spring Cache核心原理1.1 架构设计思想1.2 运行时执行流程1.3 核心组件协作1.4 关键机制详解1.5 扩展点设计1.6 与Spring事务的协同 二、快速入门实战三、局限性3.1 多级缓存一致性缺陷3.2 分布式锁能力缺失3.3 事务集成陷阱 总结 前言 在当今高并发、低延迟…...

Redisson学习专栏(四):实战应用(分布式会话管理,延迟队列)

文章目录 前言一、为什么需要分布式会话管理&#xff1f;1.1 使用 Redisson 实现 Session 共享 二、订单超时未支付&#xff1f;用延迟队列精准处理2.1 RDelayedQueue 核心机制2.2 订单超时处理实战 总结 前言 在现代分布式系统中&#xff0c;会话管理和延迟任务处理是两个核心…...

java程序从服务器端到Lambda函数的迁移与优化

source&#xff1a;https://www.jfokus.se/jfokus24-preso/From-Serverful-to-Serverless-Java.pdf 从传统的服务器端Java应用&#xff0c;到如今的无服务器架构。这不仅仅是技术名词的改变&#xff0c;更是开发模式和运维理念的一次深刻变革。先快速回顾一下我们熟悉的“服务…...

使用yocto搭建qemuarm64环境

环境 yocto下载 # 源码下载 git clone git://git.yoctoproject.org/poky git reset --hard b223b6d533a6d617134c1c5bec8ed31657dd1268 构建 # 编译镜像 export MACHINE"qemuarm64" . oe-init-build-env bitbake core-image-full-cmdline 运行 # 跑虚拟机 export …...

Vue 3前沿生态整合:WebAssembly与TypeScript深度实践

一、Vue 3 WebAssembly&#xff1a;突破性能天花板 01、WebAssembly&#xff1a;浏览器中的原生性能 WebAssembly&#xff08;Wasm&#xff09;是一种可在现代浏览器中运行的二进制指令格式&#xff0c;其性能接近原生代码。结合Vue 3的响应式架构&#xff0c;我们可以在前端…...

Linux系统下安装配置 Nginx

Windows Nginx https://nginx.org/en/download.htmlLinux Nginx https://nginx.org/download/nginx-1.24.0.tar.gz解压 tar -zxvf tar -zxvf nginx-1.18.0.tar.gz #解压安装依赖&#xff08;如未安装&#xff09; yum groupinstall "Development Tools" -y yum…...

Kotlin 中集合遍历有哪几种方式?

1 for-in 循环&#xff08;最常用&#xff09; val list listOf("A", "B", "C") for (item in list) {print("$item ") }// A B C 2 forEach 高阶函数 val list listOf("A", "B", "C") list.forEac…...

图像卷积OpenCV C/C++ 核心操作

图像卷积&#xff1a;OpenCV C 核心操作 图像卷积是图像处理和计算机视觉领域最基本且最重要的操作之一。它通过一个称为卷积核&#xff08;或滤波器&#xff09;的小矩阵&#xff0c;在输入图像上滑动&#xff0c;并对核覆盖的图像区域执行元素对应相乘后求和的运算&#xff…...

LiveGBS作为下级平台GB28181国标级联2016|2022对接海康大华宇视华为政务公安内网等GB28181国标平台查看级联状态及会话

LiveGBS作为下级平台GB28181国标级联2016|2022对接海康大华宇视华为政务公安内网等GB28181国标平台查看级联状态及会话 1、GB/T28181级联概述2、搭建GB28181国标流媒体平台3、获取上级平台接入信息3.1、向下级提供信息3.2、上级国标平台添加下级域3.3、接入LiveGBS示例 4、配置…...