UNIX下C语言编程与实践33-UNIX 僵死进程预防:wait 法、托管法、信号忽略与捕获

从原理到实战,掌握四种核心预防方案,彻底规避僵死进程风险

一、预防僵死进程的核心逻辑

UNIX 系统中僵死进程的根源是"子进程终止后父进程未回收其退出状态"。因此,预防僵死进程的核心逻辑围绕"确保父进程正确处理子进程终止事件"展开,具体可归纳为两类思路:

  • 主动回收 :父进程主动调用 waitwaitpid 函数,获取子进程的退出状态,触发内核清理进程表项;
  • 被动托管:通过系统机制让其他进程(如 init 进程)接管子进程的回收责任,避免父进程未处理导致僵死。

基于这两类思路,衍生出四种经典的预防方法:wait 法、托管法、信号忽略法、信号捕获法。每种方法适用于不同场景,需根据父进程的并发需求、资源限制等因素选择。

二、方法一:wait 法------父进程主动等待子进程终止

核心原理

父进程在 fork 子进程后,调用 waitwaitpid 函数,主动阻塞等待子进程终止。子进程结束后,父进程通过这些函数读取其退出状态,内核随之清除子进程的进程表项,避免僵死。

关键特性:父进程会阻塞直到子进程终止,期间无法执行其他逻辑,适用于"父进程仅需创建单个子进程且无需并发"的场景。

1. wait 函数与 waitpid 函数的区别

在预防僵死进程时,waitwaitpid 均用于回收子进程,但功能灵活性差异显著,需根据场景选择:

对比维度 wait 函数 waitpid 函数 在预防僵死中的应用
函数原型 pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); -
等待对象 等待任意一个子进程终止,无法指定 可指定等待的子进程(pid 参数控制): - pid > 0:等待 PID 为 pid 的子进程; - pid = -1:等待任意子进程(同 wait); - pid = 0:等待同组的任意子进程 单子进程场景用 wait;多子进程场景用 waitpid 指定回收目标,避免遗漏
阻塞行为 始终阻塞,直到有子进程终止 可通过 options 控制是否阻塞: - 0:阻塞等待; - WNOHANG:非阻塞,若无子进程终止则立即返回 0 父进程需并发处理其他任务时,用 waitpid + WNOHANG 实现非阻塞回收
返回值 成功:终止子进程的 PID; 失败:-1(无子嗣或被信号中断) 成功:终止子进程的 PID(阻塞模式)或 0(非阻塞模式无子嗣终止); 失败:-1 多子进程场景需循环调用 waitpid,直到返回 -1(无更多子嗣),确保所有子进程均被回收

2. 实战:wait 法预防僵死进程(单子进程场景)

程序逻辑

父进程 fork 子进程 → 子进程执行任务(休眠 3 秒)→ 父进程调用 wait 阻塞等待 → 子进程终止后父进程回收,无僵死进程产生。

代码内容

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main() {
    printf("父进程:PID = %d,创建子进程...\n", getpid());
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }

    // 子进程逻辑:执行任务后终止
    if (pid == 0) {
        printf("子进程:PID = %d,开始执行任务(休眠 3 秒)...\n", getpid());
        sleep(3); // 模拟子进程执行耗时任务
        printf("子进程:PID = %d,任务完成,终止\n", getpid());
        exit(EXIT_SUCCESS);
    }

    // 父进程逻辑:调用 wait 等待子进程终止,主动回收
    printf("父进程:等待子进程(PID = %d)终止...\n", pid);
    int status;
    pid_t ret_pid = wait(&status); // 阻塞等待,直到子进程终止

    // 解析子进程退出状态
    if (ret_pid == -1) {
        perror("wait 失败");
        exit(EXIT_FAILURE);
    }

    if (WIFEXITED(status)) {
        printf("父进程:成功回收子进程(PID = %d),退出码 = %d\n", ret_pid, WEXITSTATUS(status));
    } else if (WIFSIGNALED(status)) {
        printf("父进程:子进程(PID = %d)被信号 %d 终止\n", ret_pid, WTERMSIG(status));
    }

    // 验证:此时子进程已被回收,无僵死进程
    printf("父进程:执行 ps 命令查看进程状态(无僵死进程)\n");
    system("ps aux | grep -w 'Z' | grep -v grep");

    return EXIT_SUCCESS;
}

编译与运行

bash 复制代码
gcc wait_prevent.c -o wait_prevent
./wait_prevent

运行结果示例

复制代码
父进程:PID = 1234,创建子进程...
父进程:等待子进程(PID = 1235)终止...
子进程:PID = 1235,开始执行任务(休眠 3 秒)...
子进程:PID = 1235,任务完成,终止
父进程:成功回收子进程(PID = 1235),退出码 = 0
父进程:执行 ps 命令查看进程状态(无僵死进程)
# 无任何僵死进程输出,证明预防成功

适用场景:父进程仅需创建单个子进程,且无需在子进程执行期间处理其他任务(如简单的命令执行、单任务处理)。

3. 实战:waitpid 法预防僵死进程(多子进程场景)

程序逻辑

父进程循环 fork 3 个子进程 → 子进程各自执行不同时长的任务 → 父进程用 waitpid(-1, &status, WNOHANG) 非阻塞循环回收 → 所有子进程终止后父进程退出,无僵死。

代码示例

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

#define CHILD_NUM 3 // 子进程数量

int main() {
    printf("父进程:PID = %d,开始创建 %d 个子进程...\n", getpid(), CHILD_NUM);

    // 1. 创建多个子进程
    for (int i = 0; i < CHILD_NUM; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork 失败");
            exit(EXIT_FAILURE);
        }
        if (pid == 0) {
            // 子进程:执行任务(休眠时间随序号递增)
            int sleep_sec = (i + 1) * 2; // 子进程 1 休眠 2 秒,子进程 2 休眠 4 秒...
            printf("子进程 %d:PID = %d,休眠 %d 秒后终止\n", i+1, getpid(), sleep_sec);
            sleep(sleep_sec);
            exit(EXIT_SUCCESS);
        }
    }

    // 2. 父进程:非阻塞循环回收所有子进程(避免僵死)
    printf("父进程:非阻塞回收子进程...\n");
    int remaining = CHILD_NUM; // 剩余未回收的子进程数量
    while (remaining > 0) {
        int status;
        pid_t ret_pid = waitpid(-1, &status, WNOHANG); // 非阻塞,等待任意子进程
        if (ret_pid > 0) {
            // 成功回收一个子进程
            remaining--;
            if (WIFEXITED(status)) {
                printf("父进程:回收子进程(PID = %d),剩余 %d 个\n", ret_pid, remaining);
            }
        } else if (ret_pid == 0) {
            // 无子嗣终止,父进程可处理其他任务(此处模拟)
            printf("父进程:暂无子进程终止,处理其他任务...\n");
            sleep(1); // 避免循环过快占用 CPU
        } else {
            // waitpid 失败(无更多子嗣)
            perror("waitpid 失败");
            break;
        }
    }
    printf("父进程:所有子进程回收完成,退出\n");
    return EXIT_SUCCESS;
}

编译与运行

bash 复制代码
# 1. 编译程序
gcc waitpid_prevent.c -o waitpid_prevent

# 2. 运行程序
./waitpid_prevent

输出结果

复制代码
父进程:PID = 1236,开始创建 3 个子进程...
子进程 1:PID = 1237,休眠 2 秒后终止
子进程 2:PID = 1238,休眠 4 秒后终止
子进程 3:PID = 1239,休眠 6 秒后终止
父进程:非阻塞回收子进程...
父进程:暂无子进程终止,处理其他任务...
父进程:暂无子进程终止,处理其他任务...
父进程:回收子进程(PID = 1237),剩余 2 个
父进程:暂无子进程终止,处理其他任务...
父进程:暂无子进程终止,处理其他任务...
父进程:回收子进程(PID = 1238),剩余 1 个
父进程:暂无子进程终止,处理其他任务...
父进程:暂无子进程终止,处理其他任务...
父进程:回收子进程(PID = 1239),剩余 0 个
父进程:所有子进程回收完成,退出

关键优势 :通过 waitpid(-1, &status, WNOHANG),父进程可在回收子进程的同时处理其他任务,兼顾并发与僵死预防,适合多子进程场景。

三、方法二:托管法------父进程先终止,子进程由 init 托管

核心原理

UNIX 系统有一个特殊机制:若父进程先于子进程终止,子进程会被 init 进程(PID = 1)或 systemd 进程收养 。init 进程会周期性调用 wait 回收所有被收养的子进程,因此子进程终止后不会变为僵死进程。

关键特性:无需父进程主动调用回收函数,依赖系统托管机制,适用于"父进程无需等待子进程完成,且子进程可独立运行"的场景(如后台任务)。

实战:托管法预防僵死进程

程序逻辑

父进程 fork 子进程 → 父进程立即终止(先于子进程)→ 子进程被 init 收养 → 子进程执行任务后终止,init 自动回收,无僵死。

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main() {
    printf("父进程:PID = %d,创建子进程...\n", getpid());
    pid_t pid = fork();

    if (pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }

    // 子进程逻辑:父进程终止后,由 init 托管
    if (pid == 0) {
        printf("子进程:PID = %d,父进程 PID = %d\n", getpid(), getppid());
        // 等待父进程终止(确保被 init 收养)
        sleep(1);
        printf("子进程:父进程已终止,当前父进程 PID = %d(init 进程)\n", getppid());
        // 执行任务(休眠 5 秒,模拟后台运行)
        printf("子进程:执行后台任务(休眠 5 秒)...\n");
        sleep(5);
        printf("子进程:任务完成,终止(由 init 回收)\n");
        exit(EXIT_SUCCESS);
    }

    // 父进程逻辑:创建子进程后立即终止(先于子进程)
    printf("父进程:PID = %d,创建子进程(PID = %d)后立即终止\n", getpid(), pid);
    exit(EXIT_SUCCESS); // 父进程终止,子进程被 init 收养
}
bash 复制代码
# 1. 编译程序
gcc adopt_prevent.c -o adopt_prevent

# 2. 运行程序(后台运行,避免终端阻塞)
./adopt_prevent &

# 3. 查看子进程状态(确认被 init 收养且无僵死)
sleep 3  # 等待父进程终止,子进程被收养
ps aux | grep -E 'adopt_prevent|PID' | grep -v grep

输出示例

复制代码
[1] 1240
父进程:PID = 1240,创建子进程...
父进程:PID = 1240,创建子进程(PID = 1241)后立即终止
子进程:PID = 1241,父进程 PID = 1240
子进程:父进程已终止,当前父进程 PID = 1(init 进程)
子进程:执行后台任务(休眠 5 秒)...

USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0 168720 11648 ?        Ss   08:00   0:02 /sbin/init
bill      1241  0.0  0.0   4168   728 pts/0    S    10:00   0:00 ./adopt_prevent

# 子进程 1241 的父进程 PID 为 1(init),状态为 S(休眠),无僵死
子进程:任务完成,终止(由 init 回收)
[1]+  Done                    ./adopt_prevent

适用场景 :父进程无需等待子进程完成的场景,如启动后台服务(如 nginx -d)、执行异步任务,子进程由系统托管回收,无需父进程干预。

注意事项

  • 子进程被 init 收养后,父进程无法再获取其退出状态(如执行结果、错误码),若需监控子进程执行结果,不适合使用托管法;
  • 若子进程为"长期运行服务"(如数据库、Web 服务器),托管法是合理选择;若子进程为"短期任务"且需父进程处理结果,需优先使用 wait 法或信号捕获法。

四、方法三:信号忽略法------忽略 SIGCHLD 信号,系统自动回收

核心原理

子进程终止时,内核会向父进程发送 SIGCHLD 信号。若父进程通过 signal(SIGCHLD, SIG_IGN) 明确忽略该信号,部分 UNIX 系统(如 Linux 2.6+)会自动回收子进程,不产生僵死进程

关键特性:无需父进程调用 wait 函数,仅需设置信号处理方式,代码简洁;但兼容性依赖系统实现,且无法获取子进程的退出状态。

实战:信号忽略法预防僵死进程

程序逻辑

父进程先设置 signal(SIGCHLD, SIG_IGN) 忽略信号 → fork 子进程 → 子进程终止后,系统自动回收,无僵死进程。

以下是规范格式后的代码和说明,已按要求调整缩进和结构:

代码内容

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>

int main() {
    // 关键步骤:忽略 SIGCHLD 信号,触发系统自动回收
    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
        perror("signal 设置失败");
        exit(EXIT_FAILURE);
    }
    printf("父进程:已忽略 SIGCHLD 信号,子进程将自动回收\n");

    // 创建子进程
    printf("父进程:PID = %d,创建子进程...\n", getpid());
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }

    // 子进程逻辑:执行任务后终止
    if (pid == 0) {
        printf("子进程:PID = %d,执行任务(休眠 3 秒)...\n", getpid());
        sleep(3);
        printf("子进程:PID = %d,终止(系统自动回收)\n", getpid());
        exit(EXIT_SUCCESS);
    }

    // 父进程逻辑:无需调用 wait,继续执行其他任务
    printf("父进程:子进程 PID = %d,父进程继续处理其他任务...\n", pid);
    sleep(5); // 确保子进程已终止
    printf("父进程:执行 ps 命令查看(无僵死进程)\n");
    system("ps aux | grep -w 'Z' | grep -v grep");
    printf("父进程:退出\n");
    return EXIT_SUCCESS;
}

编译与运行

bash 复制代码
gcc sigign_prevent.c -o sigign_prevent
./sigign_prevent

预期输出

复制代码
父进程:已忽略 SIGCHLD 信号,子进程将自动回收
父进程:PID = 1242,创建子进程...
父进程:子进程 PID = 1243,父进程继续处理其他任务...
子进程:PID = 1243,执行任务(休眠 3 秒)...
子进程:PID = 1243,终止(系统自动回收)
父进程:执行 ps 命令查看(无僵死进程)
# 无僵死进程输出
父进程:退出

兼容性与局限性

  • 兼容性问题:Linux 2.6+、FreeBSD 8.0+ 支持该特性,但早期 UNIX 系统(如 Solaris 10)和部分嵌入式系统不支持,忽略信号后仍会产生僵死进程;
  • 无法获取退出状态:忽略 SIGCHLD 后,父进程无法通过 wait 函数获取子进程的退出码、终止信号等信息,若需监控子进程执行结果,不适合使用;
  • 谨慎用于多子进程:虽支持多子进程自动回收,但无法确保所有系统均能稳定处理,多子进程场景优先选择信号捕获法。

五、方法四:信号捕获法------捕获 SIGCHLD 信号,在信号处理函数中回收

核心原理

父进程注册 SIGCHLD 信号的处理函数,当子进程终止时,内核发送 SIGCHLD 信号触发处理函数执行,父进程在处理函数中调用 waitpid 回收子进程。该方法兼顾"非阻塞"与"结果获取",是多子进程并发场景的最优选择。

关键特性:父进程无需主动阻塞,子进程终止时自动触发回收;可获取子进程退出状态;支持多子进程并发回收,兼容性好(所有 UNIX 系统支持)。

实战:信号捕获法预防僵死进程(多子进程并发场景)

程序逻辑

父进程注册 SIGCHLD 信号处理函数 → 循环 fork 3 个子进程 → 子进程各自执行任务后终止 → 触发信号处理函数,调用 waitpid(-1, &status, WNOHANG) 回收 → 所有子进程终止后父进程退出,无僵死。

代码示例

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>

#define CHILD_NUM 3

// SIGCHLD 信号处理函数:回收所有终止的子进程
void sigchld_handler(int sig) {
    int status;
    pid_t pid;
    // 循环回收所有终止的子进程(WNOHANG 非阻塞)
    while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
        if (WIFEXITED(status)) {
            printf("信号处理函数:回收子进程(PID = %d),退出码 = %d\n", pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("信号处理函数:子进程(PID = %d)被信号 %d 终止\n", pid, WTERMSIG(status));
        }
    }
}

int main() {
    // 关键步骤:注册 SIGCHLD 信号处理函数
    struct sigaction sa;
    sa.sa_handler = sigchld_handler;
    sa.sa_flags = 0;
    sigemptyset(&sa.sa_mask); // 清空信号掩码
    if (sigaction(SIGCHLD, &sa, NULL) == -1) {
        perror("sigaction 设置失败");
        exit(EXIT_FAILURE);
    }
    printf("父进程:已注册 SIGCHLD 信号处理函数\n");

    // 创建多个子进程
    printf("父进程:PID = %d,创建 %d 个子进程...\n", getpid(), CHILD_NUM);
    for (int i = 0; i < CHILD_NUM; i++) {
        pid_t pid = fork();
        if (pid == -1) {
            perror("fork 失败");
            exit(EXIT_FAILURE);
        }
        if (pid == 0) {
            int sleep_sec = (i + 1) * 2;
            printf("子进程 %d:PID = %d,休眠 %d 秒后终止\n", i+1, getpid(), sleep_sec);
            sleep(sleep_sec);
            exit(EXIT_SUCCESS);
        }
    }

    // 父进程:无需阻塞,正常处理其他任务
    printf("父进程:继续处理其他任务(休眠 10 秒)...\n");
    sleep(10);
    // 确保所有子进程均被回收
    printf("父进程:所有子进程已回收,退出\n");
    return EXIT_SUCCESS;
}

编译与运行

bash 复制代码
gcc sigcatch_prevent.c -o sigcatch_prevent
./sigcatch_prevent

预期输出

复制代码
父进程:已注册 SIGCHLD 信号处理函数
父进程:PID = 1244,创建 3 个子进程...
子进程 1:PID = 1245,休眠 2 秒后终止
子进程 2:PID = 1246,休眠 4 秒后终止
子进程 3:PID = 1247,休眠 6 秒后终止
父进程:继续处理其他任务(休眠 10 秒)...
信号处理函数:回收子进程(PID = 1245),退出码 = 0
信号处理函数:回收子进程(PID = 1246),退出码 = 0
信号处理函数:回收子进程(PID = 1247),退出码 = 0
父进程:所有子进程已回收,退出

关键优势

  • 非阻塞并发:父进程可正常处理其他任务,子进程终止时自动触发回收,不影响主逻辑;
  • 多子进程兼容:通过 while (waitpid(...) > 0) 循环回收,确保所有子进程均被处理,无遗漏;
  • 结果可监控:可在信号处理函数中解析子进程的退出状态,满足"需监控子进程执行结果"的场景。

六、常见错误与解决方法

常见错误 问题现象 原因分析 解决方法
wait 函数调用时机不当 父进程先执行 wait 后 fork 子进程,导致 wait 无子嗣可回收,返回 -1,子进程终止后变为僵死 wait 函数需在 fork 子进程后调用,若先调用,父进程此时无子嗣,wait 立即返回失败,后续子进程终止后无人回收 严格遵循"fork → wait"的顺序,确保 wait 调用时子进程已创建;多子进程场景用 waitpid 循环回收,避免依赖单次调用
信号处理函数中未循环调用 waitpid 多子进程同时终止时,仅部分子进程被回收,剩余子进程变为僵死 内核在发送 SIGCHLD 信号时,若多个子进程同时终止,仅会发送一次信号;信号处理函数中若仅调用一次 waitpid,只能回收一个子进程,剩余子进程无人处理 在信号处理函数中用 while ((pid = waitpid(-1, &status, WNOHANG)) > 0) 循环回收,直到 waitpid 返回 <= 0,确保所有终止的子进程均被回收
忽略 SIGCHLD 信号后仍调用 wait wait 函数返回 -1,errno 设为 ECHILD(无子嗣),但子进程已被系统自动回收,无僵死 部分系统(如 Linux)忽略 SIGCHLD 后会自动回收子进程,此时子进程已不存在,wait 无子嗣可回收,返回失败 忽略 SIGCHLD 信号后,无需再调用 wait/waitpid;若需兼容多系统,避免同时使用"信号忽略"和"主动回收",选择一种方案即可
托管法中子进程依赖父进程资源 父进程终止后,子进程因依赖父进程的文件描述符、共享内存等资源,执行失败 父进程终止时会关闭其持有的文件描述符、释放共享内存等资源,若子进程依赖这些资源(如父进程打开的文件),会导致子进程执行异常 若子进程需依赖父进程资源,不适合使用托管法;改用 wait 法或信号捕获法,确保父进程在子进程终止后再释放资源;或子进程独立打开/创建所需资源
多线程环境中信号处理函数不安全 多线程程序中,信号处理函数调用 waitpid 导致其他线程的系统调用被中断,或数据竞争 信号处理函数在多线程环境中属于"全局事件",会中断任意线程的执行;若信号处理函数中调用非线程安全函数(如 printf)或操作共享数据,会引发数据竞争 多线程环境中预防僵死进程: 1. 仅在主线程注册信号处理函数; 2. 信号处理函数中仅执行简单的"标记子进程终止"操作,不直接调用 waitpid; 3. 单独创建一个线程,循环调用 waitpid(-1, &status, WNOHANG) 回收子进程,避免信号处理函数的线程安全问题

七、方法对比与场景选择指南

四种预防方法各有优劣,需根据父进程的并发需求、结果监控需求、系统兼容性等因素选择,以下是详细对比与选择建议:

1. 四种方法核心对比

对比维度 wait 法 托管法 信号忽略法 信号捕获法
父进程阻塞 是(阻塞等待子进程) 否(父进程先终止) 否(父进程正常执行) 否(信号触发回收,非阻塞)
支持多子进程 需循环调用 waitpid,支持 支持(均由 init 托管) 支持(系统自动回收) 支持(信号处理函数循环回收)
获取子进程退出状态 能(通过 status 参数) 不能(父进程先终止,无法获取) 不能(系统自动回收,无接口获取) 能(信号处理函数中解析 status)
系统兼容性 所有 UNIX 系统支持 所有 UNIX 系统支持 依赖系统(Linux 2.6+ 支持,部分系统不支持) 所有 UNIX 系统支持
代码复杂度 低(单进程)→ 中(多进程) 低(父进程仅需 fork 后终止) 极低(仅需设置信号忽略) 中(需注册信号处理函数,循环回收)

2. 场景选择指南

  • 单子进程 + 父进程需等待结果 :选择 wait 法,代码简洁,能获取退出状态;
  • 子进程为后台任务 + 父进程无需等待 :选择 托管法,依赖系统回收,无需父进程干预;
  • 单/多子进程 + 无需获取结果 + 仅 Linux 环境 :选择 信号忽略法,代码最简单,系统自动回收;
  • 多子进程 + 父进程需并发 + 需获取结果 + 跨系统兼容 :选择 信号捕获法,兼顾并发、结果监控与兼容性,是最通用的方案;
  • 多线程环境:选择"单独线程 + waitpid 循环回收",避免信号处理函数的线程安全问题。

UNIX 僵死进程的四种核心预防方法,从原理、实战到场景选择,覆盖了单/多子进程、阻塞/非阻塞、结果监控等不同需求。预防僵死进程的核心是"确保子进程终止后有进程负责回收",无论是父进程主动回收,还是系统托管回收,本质都是履行这一责任。

在实际开发中,需根据具体场景选择合适的方法:简单场景用 wait 法或信号忽略法,复杂并发场景用信号捕获法,后台任务用托管法。同时,需规避"wait 调用时机不当""信号处理函数未循环回收"等常见错误,确保预防方案稳定可靠。

相关推荐
韧竹、3 小时前
详解指针2
c语言
夜月yeyue3 小时前
多级流水线与指令预测
linux·网络·stm32·单片机·嵌入式硬件
xxtzaaa3 小时前
抖音私密账号显示IP属地吗?能更改IP么?
网络·网络协议·tcp/ip
qq_479875434 小时前
systemd-resolved.service实验实战2
linux·服务器·网络
YoungLime6 小时前
DVWA靶场之十二:储存型 XSS(Stored Cross Site Scripting (XSS))
网络·安全·web安全
一念&10 小时前
每日一个C语言知识:C 数据类型
c语言·开发语言
数据与人工智能律师10 小时前
AI的法治迷宫:技术层、模型层、应用层的法律痛点
大数据·网络·人工智能·云计算·区块链
小秋学嵌入式-不读研版10 小时前
C49-函数指针(通过指针调用函数)
c语言·笔记
板鸭〈小号〉12 小时前
Socket网络编程(1)——Echo Server
开发语言·网络·php