并发编程之管道通讯,共享内存和套接字

 

在操作系统中,进程间通信(IPC)是多个进程之间交换数据和信息的方法。常用的IPC机制包括管道(Pipes)、共享内存(Shared Memory)和套接字(Sockets)。每种方法都有其独特的优点和适用场合。

1. 管道(Pipes

优点

  • 简单易用:管道是最简单的IPC机制之一,易于实现和使用。
  • 单向通信:管道通常用于单向数据流,适合父子进程之间的通信。
  • 自动同步:管道提供了一个简单的同步机制,读写操作自动处理数据的同步。

适用场合

  • 父子进程通信:管道特别适合用于父子进程之间的单向数据流。
  • 简单的任务分发:当一个进程生成数据,另一个进程处理数据时,管道是一种高效的解决方案。

2. 共享内存(Shared Memory

优点

  • 高速数据传输:共享内存允许进程直接访问同一块内存区域,避免了数据复制,具有极高的数据传输速度。
  • 灵活性:可以实现复杂的通信模式,支持多种数据结构的共享。

适用场合

  • 大数据量传输:当需要传输大量数据时,共享内存比其他IPC机制更高效。
  • 高性能计算:在高性能计算环境中,共享内存通常用于并行处理任务。
  • 复杂数据结构:需要共享复杂数据结构(如数组、链表等)的场合。

3. 套接字(Sockets

优点

  • 跨网络通信:套接字支持本地和远程通信,适合分布式系统中的进程间通信。
  • 灵活和强大:支持多种协议(如TCP、UDP),适用于各种复杂的通信场景。
  • 异步操作:支持异步和同步操作,适合高并发的应用场景。

适用场合

  • 分布式系统:当进程分布在不同的机器上时,套接字是唯一的选择。
  • 网络应用:用于构建客户端-服务器模型,实现复杂的网络应用。
  • 异步通信:需要异步通信或高并发处理的场景。

总结

  • 管道:适合简单的单向数据流,主要用于父子进程或相关进程之间的通信。
  • 共享内存:适合需要高速数据传输和高性能计算的场景,常用于并行处理和大数据量传输。
  • 套接字:适合跨网络通信和高并发应用场景,广泛用于分布式系统和网络应用。

选择合适的IPC机制取决于具体的应用需求,包括数据量、通信复杂度、是否需要跨网络通信等因素。

Windows环境下,使用命名管道通讯在两个进程中传递数据

在Windows环境下,使用管道(Pipe)进行进程间通信(IPC)是一种常见的技术。Windows提供了两种主要类型的管道:匿名管道(Anonymous Pipe)和命名管道(Named Pipe)。匿名管道主要用于父子进程之间的通信,而命名管道则可以用于不同进程之间的通信。
下面是一个使用命名管道在两个独立进程之间传递数据的完整示例。我们将创建一个服务器进程和一个客户端进程,并通过命名管道在它们之间传递数据。
服务器端代码(Server.cpp)
#include <windows.h>
#include <iostream>

int main() {
    // 创建命名管道
    HANDLE hPipe = CreateNamedPipe(
        TEXT("\\\\.\\pipe\\MyPipe"),   // 管道名称
        PIPE_ACCESS_DUPLEX,            // 双向通信
        PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, // 字节流、字节读取模式、阻塞模式
        1,                             // 最大实例数
        1024,                          // 输出缓冲区大小
        1024,                          // 输入缓冲区大小
        0,                             // 默认超时
        NULL                           // 默认安全属性
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "创建管道失败,错误码: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "等待客户端连接..." << std::endl;

    // 等待客户端连接
    if (!ConnectNamedPipe(hPipe, NULL)) {
        std::cerr << "连接失败,错误码: " << GetLastError() << std::endl;
        CloseHandle(hPipe);
        return 1;
    }

    std::cout << "客户端已连接,开始通信..." << std::endl;

    char buffer[1024];
    DWORD bytesRead;

    // 读取客户端发送的数据
    if (ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL)) {
        buffer[bytesRead] = '\0'; // 确保字符串以空字符结束
        std::cout << "从客户端接收到数据: " << buffer << std::endl;
    } else {
        std::cerr << "读取失败,错误码: " << GetLastError() << std::endl;
    }

    // 发送响应给客户端
    const char* response = "Hello from Server!";
    DWORD bytesWritten;
    if (WriteFile(hPipe, response, strlen(response), &bytesWritten, NULL)) {
        std::cout << "已发送响应: " << response << std::endl;
    } else {
        std::cerr << "写入失败,错误码: " << GetLastError() << std::endl;
    }

    // 关闭管道
    CloseHandle(hPipe);
    std::cout << "通信结束,管道已关闭" << std::endl;

    return 0;
}

客户端代码(Client.cpp)
#include <windows.h>
#include <iostream>

int main() {
    // 连接到命名管道
    HANDLE hPipe = CreateFile(
        TEXT("\\\\.\\pipe\\MyPipe"),  // 管道名称
        GENERIC_READ | GENERIC_WRITE, // 读写权限
        0,                            // 无共享
        NULL,                         // 默认安全属性
        OPEN_EXISTING,                // 打开已存在的管道
        0,                            // 默认属性
        NULL                          // 无模板文件
    );

    if (hPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "连接到管道失败,错误码: " << GetLastError() << std::endl;
        return 1;
    }

    std::cout << "已连接到服务器,开始通信..." << std::endl;

    // 发送数据到服务器
    const char* message = "Hello from Client!";
    DWORD bytesWritten;
    if (WriteFile(hPipe, message, strlen(message), &bytesWritten, NULL)) {
        std::cout << "已发送数据: " << message << std::endl;
    } else {
        std::cerr << "写入失败,错误码: " << GetLastError() << std::endl;
    }

    char buffer[1024];
    DWORD bytesRead;

    // 读取服务器发送的数据
    if (ReadFile(hPipe, buffer, sizeof(buffer), &bytesRead, NULL)) {
        buffer[bytesRead] = '\0'; // 确保字符串以空字符结束
        std::cout << "从服务器接收到数据: " << buffer << std::endl;
    } else {
        std::cerr << "读取失败,错误码: " << GetLastError() << std::endl;
    }

    // 关闭管道
    CloseHandle(hPipe);
    std::cout << "通信结束,管道已关闭" << std::endl;

    return 0;
}

说明和注释
	1.	命名管道名称:在CreateNamedPipe和CreateFile函数中,使用\\.\pipe\MyPipe作为管道名称。这个名称是唯一的,并且可以在同一台机器上的不同进程之间共享。
	2.	管道模式:在CreateNamedPipe中,使用PIPE_ACCESS_DUPLEX表示双向通信,PIPE_TYPE_BYTE表示字节流,PIPE_READMODE_BYTE表示字节读取模式,PIPE_WAIT表示阻塞模式。
	3.	连接和通信:服务器端使用ConnectNamedPipe等待客户端连接,客户端使用CreateFile连接到管道。连接成功后,可以使用ReadFile和WriteFile函数在两个进程之间读写数据。
	4.	错误处理:每个函数调用后都会检查返回值和错误码(通过GetLastError()),以确保操作成功。
	5.	关闭管道:通信结束后,使用CloseHandle关闭管道句柄,释放系统资源。
编译和运行
	1.	分别编译Server.cpp和Client.cpp,生成Server.exe和Client.exe。
	2.	先运行Server.exe,等待客户端连接。
	3.	再运行Client.exe,客户端将连接到服务器,并进行数据交换。
通过这种方式,可以在Windows环境下实现进程间的数据传递。


Linux环境下,使用命名管道通讯在两个进程中传递数据

在Linux环境下,管道(Pipe)是一种常见的进程间通信(IPC)机制。Linux提供了匿名管道(Anonymous Pipe)和命名管道(Named Pipe,也称为FIFO)两种类型。匿名管道主要用于父子进程之间的通信,而命名管道可以用于不同进程之间的通信。
下面是一个使用命名管道(FIFO)在两个独立进程之间传递数据的完整示例。我们将创建一个服务器进程和一个客户端进程,并通过命名管道在它们之间传递数据。
服务器端代码(server.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    const char *fifoName = "/tmp/my_fifo";  // 命名管道的路径
    mkfifo(fifoName, 0666);  // 创建命名管道,权限为0666(读写权限)

    int fd = open(fifoName, O_RDONLY);  // 以只读方式打开命名管道
    if (fd == -1) {
        perror("打开命名管道失败");
        exit(EXIT_FAILURE);
    }

    char buffer[1024];
    ssize_t bytesRead;

    printf("等待客户端连接...\n");

    // 读取客户端发送的数据
    bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';  // 确保字符串以空字符结束
        printf("从客户端接收到数据: %s\n", buffer);
    } else {
        perror("读取失败");
    }

    // 发送响应给客户端
    const char *response = "Hello from Server!";
    ssize_t bytesWritten = write(fd, response, strlen(response));
    if (bytesWritten > 0) {
        printf("已发送响应: %s\n", response);
    } else {
        perror("写入失败");
    }

    close(fd);  // 关闭文件描述符
    unlink(fifoName);  // 删除命名管道

    return 0;
}

客户端代码(client.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    const char *fifoName = "/tmp/my_fifo";  // 命名管道的路径

    int fd = open(fifoName, O_WRONLY);  // 以只写方式打开命名管道
    if (fd == -1) {
        perror("打开命名管道失败");
        exit(EXIT_FAILURE);
    }

    printf("已连接到服务器,开始通信...\n");

    // 发送数据到服务器
    const char *message = "Hello from Client!";
    ssize_t bytesWritten = write(fd, message, strlen(message));
    if (bytesWritten > 0) {
        printf("已发送数据: %s\n", message);
    } else {
        perror("写入失败");
    }

    char buffer[1024];
    ssize_t bytesRead;

    // 读取服务器发送的数据
    bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';  // 确保字符串以空字符结束
        printf("从服务器接收到数据: %s\n", buffer);
    } else {
        perror("读取失败");
    }

    close(fd);  // 关闭文件描述符

    return 0;
}

说明和注释
	1.	命名管道路径:在fifoName变量中定义了一个路径/tmp/my_fifo,这是命名管道的名称和位置。客户端和服务器都将使用这个路径来访问命名管道。
	2.	创建命名管道:在服务器端,使用mkfifo函数创建一个命名管道。权限设置为0666,表示所有用户都有读写权限。
	3.	打开命名管道:在服务器端,使用open函数以只读方式打开命名管道。在客户端,使用open函数以只写方式打开命名管道。
	4.	读写数据:使用read和write函数在两个进程之间进行数据读写。读取到的数据会存储在缓冲区中,写入的数据会通过管道传递给对方。
	5.	错误处理:每个函数调用后都会检查返回值,并在发生错误时使用perror函数输出错误信息。
	6.	关闭和删除命名管道:通信结束后,使用close函数关闭文件描述符,并使用unlink函数删除命名管道。
编译和运行
	1.	分别编译server.c和client.c,生成server和client可执行文件。gcc -o server server.c
	2.	gcc -o client client.c
	3.	
	4.	先运行server,等待客户端连接。./server
	5.	
	6.	再运行client,客户端将连接到服务器,并进行数据交换。./client
	7.	
通过这种方式,可以在Linux环境下实现进程间的数据传递。


mac环境下,使用命名管道通讯在两个进程中传递数据

在macOS环境下,命名管道(Named Pipe,也称为FIFO)同样可以用于两个进程之间的通信。命名管道可以在文件系统中创建一个特殊的文件,进程通过读写这个文件来进行通信。下面是一个使用命名管道在两个独立进程之间传递数据的完整示例。
服务器端代码(server.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    const char *fifoName = "/tmp/my_fifo";  // 命名管道的路径

    // 创建命名管道
    if (mkfifo(fifoName, 0666) == -1) {
        perror("创建命名管道失败");
        exit(EXIT_FAILURE);
    }

    int fd = open(fifoName, O_RDONLY);  // 以只读方式打开命名管道
    if (fd == -1) {
        perror("打开命名管道失败");
        exit(EXIT_FAILURE);
    }

    char buffer[1024];
    ssize_t bytesRead;

    printf("等待客户端连接...\n");

    // 读取客户端发送的数据
    bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';  // 确保字符串以空字符结束
        printf("从客户端接收到数据: %s\n", buffer);
    } else {
        perror("读取失败");
    }

    // 发送响应给客户端
    const char *response = "Hello from Server!";
    ssize_t bytesWritten = write(fd, response, strlen(response));
    if (bytesWritten > 0) {
        printf("已发送响应: %s\n", response);
    } else {
        perror("写入失败");
    }

    close(fd);  // 关闭文件描述符
    unlink(fifoName);  // 删除命名管道

    return 0;
}

客户端代码(client.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>

int main() {
    const char *fifoName = "/tmp/my_fifo";  // 命名管道的路径

    int fd = open(fifoName, O_WRONLY);  // 以只写方式打开命名管道
    if (fd == -1) {
        perror("打开命名管道失败");
        exit(EXIT_FAILURE);
    }

    printf("已连接到服务器,开始通信...\n");

    // 发送数据到服务器
    const char *message = "Hello from Client!";
    ssize_t bytesWritten = write(fd, message, strlen(message));
    if (bytesWritten > 0) {
        printf("已发送数据: %s\n", message);
    } else {
        perror("写入失败");
    }

    char buffer[1024];
    ssize_t bytesRead;

    // 读取服务器发送的数据
    bytesRead = read(fd, buffer, sizeof(buffer));
    if (bytesRead > 0) {
        buffer[bytesRead] = '\0';  // 确保字符串以空字符结束
        printf("从服务器接收到数据: %s\n", buffer);
    } else {
        perror("读取失败");
    }

    close(fd);  // 关闭文件描述符

    return 0;
}

说明和注释
	1.	命名管道路径:在fifoName变量中定义了一个路径/tmp/my_fifo,这是命名管道的名称和位置。客户端和服务器都将使用这个路径来访问命名管道。
	2.	创建命名管道:在服务器端,使用mkfifo函数创建一个命名管道。权限设置为0666,表示所有用户都有读写权限。如果创建失败,程序会输出错误信息并退出。
	3.	打开命名管道:在服务器端,使用open函数以只读方式打开命名管道。在客户端,使用open函数以只写方式打开命名管道。如果打开失败,程序会输出错误信息并退出。
	4.	读写数据:服务器端使用read函数从管道读取客户端发送的数据,并使用write函数向管道写入响应数据。客户端使用write函数向管道写入数据,并使用read函数从管道读取服务器发送的数据。
	5.	错误处理:每个函数调用后都会检查返回值,并在发生错误时使用perror函数输出错误信息。
	6.	关闭和删除命名管道:通信结束后,使用close函数关闭文件描述符,并使用unlink函数删除命名管道。
编译和运行
	1.	分别编译server.c和client.c,生成server和client可执行文件。gcc -o server server.c
	2.	gcc -o client client.c
	3.	
	4.	先运行server,等待客户端连接。./server
	5.	
	6.	再运行client,客户端将连接到服务器,并进行数据交换。./client
	7.	
运行结果将显示服务器和客户端之间的数据交换。通过这种方式,可以在macOS环境下实现进程间的数据传递。


Windows环境下,使用匿名管道通讯在父子进程中传递数据

在Windows环境下,使用匿名管道(Anonymous Pipe)进行父子进程间的数据传递是一种常见的进程间通信(IPC)方式。下面是一个完整的示例,展示了如何在Windows环境下使用匿名管道在父子进程之间传递数据。
父进程代码(parent.c)
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

int main() {
    HANDLE hReadPipe, hWritePipe;  // 管道句柄
    SECURITY_ATTRIBUTES saAttr;
    PROCESS_INFORMATION piProcInfo;
    STARTUPINFO siStartInfo;
    BOOL bSuccess;

    // 设置管道安全属性
    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
    saAttr.bInheritHandle = TRUE;  // 子进程继承句柄
    saAttr.lpSecurityDescriptor = NULL;

    // 创建匿名管道
    if (!CreatePipe(&hReadPipe, &hWritePipe, &saAttr, 0)) {
        _tprintf(TEXT("管道创建失败 (%d)\n"), GetLastError());
        return 1;
    }

    // 配置 STARTUPINFO 结构以传递管道句柄
    ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
    siStartInfo.cb = sizeof(STARTUPINFO);
    siStartInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
    siStartInfo.hStdInput = hReadPipe;  // 将管道读句柄传递给子进程
    siStartInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;

    // 创建子进程
    TCHAR szCmdline[] = TEXT("child.exe");
    bSuccess = CreateProcess(
        NULL,           // 不指定可执行文件路径
        szCmdline,      // 命令行
        NULL,           // 默认进程安全性
        NULL,           // 默认线程安全性
        TRUE,           // 继承句柄
        0,              // 无特殊标志
        NULL,           // 使用父进程环境
        NULL,           // 使用父进程当前目录
        &siStartInfo,   // STARTUPINFO 指针
        &piProcInfo     // 获取 PROCESS_INFORMATION
    );

    if (!bSuccess) {
        _tprintf(TEXT("创建进程失败 (%d)\n"), GetLastError());
        return 1;
    }

    // 父进程关闭管道读端
    CloseHandle(hReadPipe);

    // 父进程向管道写入数据
    const char *message = "Hello from Parent!";
    DWORD dwWritten;
    bSuccess = WriteFile(hWritePipe, message, strlen(message), &dwWritten, NULL);
    if (!bSuccess) {
        _tprintf(TEXT("写入管道失败 (%d)\n"), GetLastError());
        return 1;
    }

    // 父进程关闭管道写端
    CloseHandle(hWritePipe);

    // 等待子进程结束
    WaitForSingleObject(piProcInfo.hProcess, INFINITE);

    // 关闭子进程句柄
    CloseHandle(piProcInfo.hProcess);
    CloseHandle(piProcInfo.hThread);

    return 0;
}

子进程代码(child.c)
#include <windows.h>
#include <stdio.h>
#include <tchar.h>

int main() {
    HANDLE hReadPipe;  // 管道读句柄
    CHAR buffer[1024];
    DWORD dwRead;
    BOOL bSuccess;

    // 获取管道读句柄
    hReadPipe = GetStdHandle(STD_INPUT_HANDLE);
    if (hReadPipe == INVALID_HANDLE_VALUE) {
        _tprintf(TEXT("获取管道读句柄失败 (%d)\n"), GetLastError());
        return 1;
    }

    // 从管道读取数据
    bSuccess = ReadFile(hReadPipe, buffer, sizeof(buffer) - 1, &dwRead, NULL);
    if (!bSuccess || dwRead == 0) {
        _tprintf(TEXT("读取管道失败 (%d)\n"), GetLastError());
        return 1;
    }

    // 确保字符串以空字符结束
    buffer[dwRead] = '\0';
    printf("子进程接收到来自父进程的数据: %s\n", buffer);

    // 子进程关闭管道读端
    CloseHandle(hReadPipe);

    return 0;
}

说明和注释
	1.	管道句柄:hReadPipe和hWritePipe是管道的读和写句柄。父进程使用hWritePipe向管道写入数据,子进程使用hReadPipe从管道读取数据。
	2.	安全属性:SECURITY_ATTRIBUTES结构用于设置管道句柄是否可继承。bInheritHandle = TRUE表示子进程可以继承这些句柄。
	3.	创建匿名管道:使用CreatePipe函数创建匿名管道。如果创建失败,程序会输出错误信息并退出。
	4.	配置 STARTUPINFO 结构:STARTUPINFO结构用于指定子进程的标准输入、输出和错误句柄。在这里,父进程将管道的读句柄传递给子进程的标准输入。
	5.	创建子进程:使用CreateProcess函数创建子进程。子进程将继承父进程传递的管道句柄。
	6.	父进程操作:
	◦	父进程关闭管道读端(hReadPipe),因为它不会从管道读取数据。
	◦	父进程使用WriteFile函数向管道写入数据。
	◦	父进程关闭管道写端(hWritePipe)。
	7.	子进程操作:
	◦	子进程使用GetStdHandle(STD_INPUT_HANDLE)获取管道读句柄。
	◦	子进程使用ReadFile函数从管道读取数据,并将其存储在缓冲区中。
	◦	子进程关闭管道读端(hReadPipe)。
	8.	错误处理:每个函数调用后都会检查返回值,并在发生错误时使用GetLastError函数获取错误代码并输出错误信息。
编译和运行
	1.	分别编译parent.c和child.c,生成parent.exe和child.exe可执行文件。cl /EHsc parent.c
	2.	cl /EHsc child.c
	3.	
	4.	运行parent.exe,它将创建子进程并进行数据交换。parent.exe
	5.	
运行结果将显示父进程发送的数据和子进程接收到的数据。通过这种方式,可以在Windows环境下实现父子进程间的数据传递。


Linux环境下,使用匿名管道通讯在父子进程中传递数据的完整示例,并加以说明和注释

在Linux环境下,使用匿名管道(Anonymous Pipe)进行父子进程间的数据传递也是一种常见的进程间通信(IPC)方式。下面是一个完整的示例,展示了如何在Linux环境下使用匿名管道在父子进程之间传递数据。
父进程代码(parent.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];  // 管道文件描述符
    pid_t child_pid;
    char buffer[1024];
    ssize_t num_read;

    // 创建匿名管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    child_pid = fork();
    if (child_pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (child_pid == 0) {
        // 子进程
        close(pipefd[1]);  // 关闭子进程的写端

        // 从管道读取数据
        num_read = read(pipefd[0], buffer, sizeof(buffer) - 1);
        if (num_read == -1) {
            perror("read");
            exit(EXIT_FAILURE);
        }

        // 确保字符串以空字符结束
        buffer[num_read] = '\0';
        printf("子进程接收到来自父进程的数据: %s\n", buffer);

        // 关闭子进程的读端
        close(pipefd[0]);

        _exit(EXIT_SUCCESS);
    } else {
        // 父进程
        close(pipefd[0]);  // 关闭父进程的读端

        // 父进程向管道写入数据
        const char *message = "Hello from Parent!";
        if (write(pipefd[1], message, strlen(message)) == -1) {
            perror("write");
            exit(EXIT_FAILURE);
        }

        // 关闭父进程的写端
        close(pipefd[1]);

        // 等待子进程结束
        wait(NULL);

        printf("父进程发送数据完毕\n");

        exit(EXIT_SUCCESS);
    }
}

说明和注释
	1.	管道文件描述符:pipefd数组用于存储管道的读(pipefd[0])和写(pipefd[1])文件描述符。父进程使用pipefd[1]向管道写入数据,子进程使用pipefd[0]从管道读取数据。
	2.	创建匿名管道:使用pipe函数创建匿名管道。如果创建失败,程序会输出错误信息并退出。
	3.	创建子进程:使用fork函数创建子进程。如果创建失败,程序会输出错误信息并退出。
	4.	子进程操作:
	◦	子进程关闭管道的写端(pipefd[1]),因为它不会向管道写入数据。
	◦	子进程使用read函数从管道读取数据,并将其存储在缓冲区中。
	◦	子进程关闭管道的读端(pipefd[0])。
	◦	子进程使用_exit函数退出,以避免调用父进程的清理函数。
	5.	父进程操作:
	◦	父进程关闭管道的读端(pipefd[0]),因为它不会从管道读取数据。
	◦	父进程使用write函数向管道写入数据。
	◦	父进程关闭管道的写端(pipefd[1])。
	◦	父进程使用wait函数等待子进程结束。
	◦	父进程输出提示信息并退出。
	6.	错误处理:每个系统调用后都会检查返回值,并在发生错误时使用perror函数获取错误信息并输出错误信息。
编译和运行
	1.	编译parent.c,生成可执行文件parent。gcc -o parent parent.c
	2.	
	3.	运行parent,它将创建子进程并进行数据交换。./parent
	4.	
运行结果将显示父进程发送的数据和子进程接收到的数据。通过这种方式,可以在Linux环境下实现父子进程间的数据传递。


mac环境下,使用管道通讯在两个进程中传递数据

在macOS环境下,管道通讯与Linux环境下的实现非常相似,因为macOS基于Unix。我们可以使用匿名管道(Anonymous Pipe)来进行进程间的数据传递。下面是一个使用匿名管道在两个进程之间传递数据的完整示例。
父进程代码(parent.c)
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/wait.h>

int main() {
    int pipefd[2];  // 管道文件描述符数组
    pid_t pid;

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("管道创建失败");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork 失败");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // 子进程
        close(pipefd[1]);  // 关闭写端

        char buffer[1024];
        ssize_t bytesRead;

        // 从管道读取数据
        bytesRead = read(pipefd[0], buffer, sizeof(buffer));
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0';  // 确保字符串以空字符结束
            printf("子进程接收到来自父进程的数据: %s\n", buffer);
        } else {
            perror("读取失败");
        }

        close(pipefd[0]);  // 关闭读端
    } else {  // 父进程
        close(pipefd[0]);  // 关闭读端

        const char *message = "Hello from Parent!";
        ssize_t bytesWritten;

        // 向管道写入数据
        bytesWritten = write(pipefd[1], message, strlen(message));
        if (bytesWritten > 0) {
            printf("父进程已发送数据: %s\n", message);
        } else {
            perror("写入失败");
        }

        close(pipefd[1]);  // 关闭写端

        // 等待子进程结束
        wait(NULL);
    }

    return 0;
}

说明和注释
	1.	管道文件描述符数组:pipefd是一个包含两个元素的数组,pipefd[0]用于读取数据,pipefd[1]用于写入数据。
	2.	创建管道:使用pipe函数创建管道。如果创建失败,程序会输出错误信息并退出。
	3.	创建子进程:使用fork函数创建子进程。如果fork失败,程序会输出错误信息并退出。
	4.	子进程:
	◦	关闭管道的写端(pipefd[1]),因为子进程只需要从管道读取数据。
	◦	使用read函数从管道读取数据,并将读取到的数据存储在缓冲区中。
	◦	关闭管道的读端(pipefd[0])。
	5.	父进程:
	◦	关闭管道的读端(pipefd[0]),因为父进程只需要向管道写入数据。
	◦	使用write函数向管道写入数据。
	◦	关闭管道的写端(pipefd[1])。
	◦	使用wait函数等待子进程结束。
	6.	错误处理:每个函数调用后都会检查返回值,并在发生错误时使用perror函数输出错误信息。
编译和运行
	1.	编译parent.c,生成parent可执行文件。gcc -o parent parent.c
	2.	
	3.	运行parent。./parent
	4.	
运行结果将显示父进程发送的数据和子进程接收到的数据。通过这种方式,可以在macOS环境下实现进程间的数据传递。

Windows环境下,使用共享内存在两个进程中传递数据

在Windows环境下,使用共享内存(Shared Memory)进行进程间通信(IPC)是一种常见的方法。Windows提供了CreateFileMapping和MapViewOfFile等函数来创建和访问共享内存。下面是一个完整的示例,展示了如何在两个独立的进程(进程A和进程B)之间使用共享内存传递数据。
进程A(sender.c)
#include <windows.h>
#include <stdio.h>
#include <string.h>

#define SHARED_MEMORY_NAME "MySharedMemory"
#define BUFFER_SIZE 1024

int main() {
    HANDLE hMapFile;
    LPVOID pSharedMemory;
    const char *message = "Hello from Process A!";

    // 创建一个文件映射对象(共享内存)
    hMapFile = CreateFileMapping(
        INVALID_HANDLE_VALUE,  // 使用系统页面文件
        NULL,                  // 默认安全属性
        PAGE_READWRITE,        // 可读可写
        0,                     // 最大大小的高32位
        BUFFER_SIZE,           // 最大大小的低32位
        SHARED_MEMORY_NAME);   // 共享内存对象的名称

    if (hMapFile == NULL) {
        printf("创建文件映射对象失败 (错误代码: %d)\n", GetLastError());
        return 1;
    }

    // 将共享内存映射到当前进程的地址空间
    pSharedMemory = MapViewOfFile(
        hMapFile,              // 文件映射对象的句柄
        FILE_MAP_ALL_ACCESS,   // 可读可写
        0,                     // 偏移量的高32位
        0,                     // 偏移量的低32位
        BUFFER_SIZE);          // 映射区域的大小

    if (pSharedMemory == NULL) {
        printf("映射文件视图失败 (错误代码: %d)\n", GetLastError());
        CloseHandle(hMapFile);
        return 1;
    }

    // 向共享内存写入数据
    memcpy(pSharedMemory, message, strlen(message) + 1);

    printf("进程A已向共享内存写入数据: %s\n", message);

    // 等待用户按下回车键,以便进程B有时间读取数据
    printf("按下回车键继续...\n");
    getchar();

    // 释放共享内存映射
    UnmapViewOfFile(pSharedMemory);

    // 关闭文件映射对象句柄
    CloseHandle(hMapFile);

    return 0;
}

进程B(receiver.c)
#include <windows.h>
#include <stdio.h>
#include <string.h>

#define SHARED_MEMORY_NAME "MySharedMemory"
#define BUFFER_SIZE 1024

int main() {
    HANDLE hMapFile;
    LPVOID pSharedMemory;
    char buffer[BUFFER_SIZE];

    // 打开已存在的文件映射对象(共享内存)
    hMapFile = OpenFileMapping(
        FILE_MAP_ALL_ACCESS,  // 可读可写
        FALSE,                // 不继承句柄
        SHARED_MEMORY_NAME);  // 共享内存对象的名称

    if (hMapFile == NULL) {
        printf("打开文件映射对象失败 (错误代码: %d)\n", GetLastError());
        return 1;
    }

    // 将共享内存映射到当前进程的地址空间
    pSharedMemory = MapViewOfFile(
        hMapFile,              // 文件映射对象的句柄
        FILE_MAP_ALL_ACCESS,   // 可读可写
        0,                     // 偏移量的高32位
        0,                     // 偏移量的低32位
        BUFFER_SIZE);          // 映射区域的大小

    if (pSharedMemory == NULL) {
        printf("映射文件视图失败 (错误代码: %d)\n", GetLastError());
        CloseHandle(hMapFile);
        return 1;
    }

    // 从共享内存读取数据
    memcpy(buffer, pSharedMemory, BUFFER_SIZE);

    printf("进程B从共享内存读取数据: %s\n", buffer);

    // 释放共享内存映射
    UnmapViewOfFile(pSharedMemory);

    // 关闭文件映射对象句柄
    CloseHandle(hMapFile);

    return 0;
}

说明和注释
	1.	创建共享内存:
	◦	在进程A中,使用CreateFileMapping函数创建一个文件映射对象(共享内存)。INVALID_HANDLE_VALUE表示使用系统页面文件,PAGE_READWRITE表示可读可写,BUFFER_SIZE表示共享内存的大小,SHARED_MEMORY_NAME是共享内存对象的名称。
	◦	如果创建失败,程序会输出错误信息并退出。
	2.	映射共享内存:
	◦	使用MapViewOfFile函数将共享内存映射到当前进程的地址空间。FILE_MAP_ALL_ACCESS表示可读可写,0和BUFFER_SIZE表示映射的起始位置和大小。
	◦	如果映射失败,程序会输出错误信息并关闭文件映射对象句柄。
	3.	写入共享内存:
	◦	进程A使用memcpy函数将数据写入共享内存。
	4.	读取共享内存:
	◦	进程B使用OpenFileMapping函数打开已存在的文件映射对象(共享内存)。
	◦	使用MapViewOfFile函数将共享内存映射到当前进程的地址空间。
	◦	使用memcpy函数从共享内存读取数据。
	5.	释放资源:
	◦	使用UnmapViewOfFile函数释放共享内存映射。
	◦	使用CloseHandle函数关闭文件映射对象句柄。
编译和运行
	1.	编译sender.c,生成可执行文件sender.exe。
cl /EHsc sender.c
	2.	




	3.	编译receiver.c,生成可执行文件receiver.exe。
cl /EHsc receiver.c
	4.	




	5.	先运行sender.exe,它会创建共享内存并写入数据。
sender.exe
	6.	




	7.	在sender.exe等待用户按下回车键时,运行receiver.exe,它会读取共享内存中的数据。
receiver.exe
	8.	




通过这种方式,可以在Windows环境下实现两个进程之间的数据传递。sender.exe和receiver.exe通过共享内存进行数据交换。

Linux环境下,使用共享内存在两个进程中传递数据的完整示例,并加以说明和注释

在Linux环境下,使用共享内存(Shared Memory)进行进程间通信(IPC)也是一种常见的方法。Linux提供了shmget、shmat、shmdt和shmctl等函数来创建、附加、分离和控制共享内存段。下面是一个完整的示例,展示了如何在两个独立的进程(进程A和进程B)之间使用共享内存传递数据。
进程A(sender.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SHM_SIZE 1024  // 共享内存的大小
#define SHM_KEY 1234   // 共享内存的键值

int main() {
    int shmid;
    char *shmaddr;
    const char *message = "Hello from Process A!";

    // 创建共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到当前进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 向共享内存写入数据
    strncpy(shmaddr, message, SHM_SIZE);

    printf("进程A已向共享内存写入数据: %s\n", message);

    // 等待用户按下回车键,以便进程B有时间读取数据
    printf("按下回车键继续...\n");
    getchar();

    // 从当前进程分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        perror("shmctl");
        exit(1);
    }

    return 0;
}

进程B(receiver.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SHM_SIZE 1024  // 共享内存的大小
#define SHM_KEY 1234   // 共享内存的键值

int main() {
    int shmid;
    char *shmaddr;
    char buffer[SHM_SIZE];

    // 获取共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
    if (shmid < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到当前进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 从共享内存读取数据
    strncpy(buffer, shmaddr, SHM_SIZE);

    printf("进程B从共享内存读取数据: %s\n", buffer);

    // 从当前进程分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

说明和注释
	1.	创建共享内存段:
	◦	在进程A中,使用shmget函数创建一个共享内存段。SHM_KEY是共享内存的键值,SHM_SIZE是共享内存的大小,IPC_CREAT | 0666表示如果共享内存不存在则创建它,并设置权限为0666(所有用户可读写)。
	◦	如果创建失败,程序会输出错误信息并退出。
	2.	附加共享内存:
	◦	使用shmat函数将共享内存附加到当前进程的地址空间。NULL表示让系统自动选择一个合适的地址,0表示默认的读写权限。
	◦	如果附加失败,程序会输出错误信息并退出。
	3.	写入共享内存:
	◦	进程A使用strncpy函数将数据写入共享内存。strncpy用于确保不会溢出共享内存。
	4.	读取共享内存:
	◦	进程B使用shmget函数获取已存在的共享内存段。
	◦	使用shmat函数将共享内存附加到当前进程的地址空间。
	◦	使用strncpy函数从共享内存读取数据。
	5.	分离和删除共享内存:
	◦	使用shmdt函数从当前进程分离共享内存。
	◦	进程A在数据传递完成后使用shmctl函数删除共享内存段,其中IPC_RMID表示删除共享内存段。
编译和运行
	1.	编译sender.c,生成可执行文件sender。
gcc -o sender sender.c
	2.	




	3.	编译receiver.c,生成可执行文件receiver。
gcc -o receiver receiver.c
	4.	




	5.	先运行sender,它会创建共享内存并写入数据。
./sender
	6.	




	7.	在sender等待用户按下回车键时,运行receiver,它会读取共享内存中的数据。
./receiver
	8.	




通过这种方式,可以在Linux环境下实现两个进程之间的数据传递。sender和receiver通过共享内存进行数据交换。

MacOS环境下,使用共享内存在两个进程中传递数据

在macOS环境下,使用共享内存进行进程间通信(IPC)的方式与Linux类似。macOS同样提供了shmget、shmat、shmdt和shmctl等函数来创建、附加、分离和控制共享内存段。下面是一个完整的示例,展示了如何在两个独立的进程(进程A和进程B)之间使用共享内存传递数据。
进程A(sender.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SHM_SIZE 1024  // 共享内存的大小
#define SHM_KEY 1234   // 共享内存的键值

int main() {
    int shmid;
    char *shmaddr;
    const char *message = "Hello from Process A!";

    // 创建共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, IPC_CREAT | 0666);
    if (shmid < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到当前进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 向共享内存写入数据
    strncpy(shmaddr, message, SHM_SIZE);

    printf("进程A已向共享内存写入数据: %s\n", message);

    // 等待用户按下回车键,以便进程B有时间读取数据
    printf("按下回车键继续...\n");
    getchar();

    // 从当前进程分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    // 删除共享内存段
    if (shmctl(shmid, IPC_RMID, 0) == -1) {
        perror("shmctl");
        exit(1);
    }

    return 0;
}

进程B(receiver.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>

#define SHM_SIZE 1024  // 共享内存的大小
#define SHM_KEY 1234   // 共享内存的键值

int main() {
    int shmid;
    char *shmaddr;
    char buffer[SHM_SIZE];

    // 获取共享内存段
    shmid = shmget(SHM_KEY, SHM_SIZE, 0666);
    if (shmid < 0) {
        perror("shmget");
        exit(1);
    }

    // 将共享内存附加到当前进程的地址空间
    shmaddr = (char *)shmat(shmid, NULL, 0);
    if (shmaddr == (char *)-1) {
        perror("shmat");
        exit(1);
    }

    // 从共享内存读取数据
    strncpy(buffer, shmaddr, SHM_SIZE);

    printf("进程B从共享内存读取数据: %s\n", buffer);

    // 从当前进程分离共享内存
    if (shmdt(shmaddr) == -1) {
        perror("shmdt");
        exit(1);
    }

    return 0;
}

说明和注释
	1.	创建共享内存段:
	◦	在进程A中,使用shmget函数创建一个共享内存段。SHM_KEY是共享内存的键值,SHM_SIZE是共享内存的大小,IPC_CREAT | 0666表示如果共享内存不存在则创建它,并设置权限为0666(所有用户可读写)。
	◦	如果创建失败,程序会输出错误信息并退出。
	2.	附加共享内存:
	◦	使用shmat函数将共享内存附加到当前进程的地址空间。NULL表示让系统自动选择一个合适的地址,0表示默认的读写权限。
	◦	如果附加失败,程序会输出错误信息并退出。
	3.	写入共享内存:
	◦	进程A使用strncpy函数将数据写入共享内存。strncpy用于确保不会溢出共享内存。
	4.	读取共享内存:
	◦	进程B使用shmget函数获取已存在的共享内存段。
	◦	使用shmat函数将共享内存附加到当前进程的地址空间。
	◦	使用strncpy函数从共享内存读取数据。
	5.	分离和删除共享内存:
	◦	使用shmdt函数从当前进程分离共享内存。
	◦	进程A在数据传递完成后使用shmctl函数删除共享内存段,其中IPC_RMID表示删除共享内存段。
编译和运行
	1.	编译sender.c,生成可执行文件sender。
gcc -o sender sender.c
	2.	




	3.	编译receiver.c,生成可执行文件receiver。
gcc -o receiver receiver.c
	4.	




	5.	先运行sender,它会创建共享内存并写入数据。
./sender
	6.	




	7.	在sender等待用户按下回车键时,运行receiver,它会读取共享内存中的数据。
./receiver
	8.	




通过这种方式,可以在macOS环境下实现两个进程之间的数据传递。sender和receiver通过共享内存进行数据交换。
使用asio库实现服务器/客户端通讯

使用Asio库实现一个支持多客户端连接的服务器,服务端支持最大连接数为256个,并且为每个客户连接提供一个线程进行通讯,需要使用Boost.Asio库。Boost.Asio是一个跨平台的C++库,用于网络和低级I/O编程。下面是一个简单的示例,展示了如何使用Boost.Asio实现一个多线程的服务器和客户端。
服务器代码(server.cpp)
#include <iostream>
#include <thread>
#include <memory>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class Session : public std::enable_shared_from_this<Session> {
public:
    Session(tcp::socket socket) : socket_(std::move(socket)) {}

    void start() {
        do_read();
    }

private:
    void do_read() {
        auto self(shared_from_this());
        socket_.async_read_some(boost::asio::buffer(data_, max_length),
            [this, self](boost::system::error_code ec, std::size_t length) {
                if (!ec) {
                    do_write(length);
                }
            });
    }

    void do_write(std::size_t length) {
        auto self(shared_from_this());
        boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
            [this, self](boost::system::error_code ec, std::size_t /*length*/) {
                if (!ec) {
                    do_read();
                }
            });
    }

    tcp::socket socket_;
    enum { max_length = 1024 };
    char data_[max_length];
};

class Server {
public:
    Server(boost::asio::io_context& io_context, short port)
        : acceptor_(io_context, tcp::endpoint(tcp::v4(), port)) {
        do_accept();
    }

private:
    void do_accept() {
        acceptor_.async_accept(
            [this](boost::system::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<Session>(std::move(socket))->start();
                }
                do_accept();
            });
    }

    tcp::acceptor acceptor_;
};

int main(int argc, char* argv[]) {
    try {
        if (argc != 2) {
            std::cerr << "Usage: server <port>\n";
            return 1;
        }

        boost::asio::io_context io_context;

        Server s(io_context, std::atoi(argv[1]));

        std::vector<std::thread> threads;
        for (int i = 0; i < 256; ++i) {
            threads.emplace_back([&io_context] { io_context.run(); });
        }

        for (auto& t : threads) {
            t.join();
        }
    }
    catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

客户端代码(client.cpp)
#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main(int argc, char* argv[]) {
    try {
        if (argc != 3) {
            std::cerr << "Usage: client <host> <port>\n";
            return 1;
        }

        boost::asio::io_context io_context;

        tcp::resolver resolver(io_context);
        auto endpoints = resolver.resolve(argv[1], argv[2]);

        tcp::socket socket(io_context);
        boost::asio::connect(socket, endpoints);

        for (;;) {
            std::cout << "Enter message: ";
            char request[1024];
            std::cin.getline(request, 1024);

            size_t request_length = std::strlen(request);
            boost::asio::write(socket, boost::asio::buffer(request, request_length));

            char reply[1024];
            size_t reply_length = boost::asio::read(socket, boost::asio::buffer(reply, request_length));
            std::cout << "Reply is: ";
            std::cout.write(reply, reply_length);
            std::cout << "\n";
        }
    }
    catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

说明和注释
	1.	服务器端:
	◦	Session类:每个客户端连接由一个Session对象处理。Session通过shared_ptr管理其生命周期,并使用异步操作async_read_some和async_write来进行读写操作。
	◦	Server类:Server类负责接受客户端连接,并为每个连接创建一个Session对象。服务器使用acceptor异步接受客户端连接。
	◦	多线程支持:服务器使用256个线程来运行io_context,以支持并发处理多个客户端连接。
	2.	客户端端:
	◦	客户端通过io_context和socket与服务器建立连接。
	◦	客户端读取用户输入,并使用write函数将数据发送到服务器。
	◦	客户端通过read函数从服务器接收数据,并将其打印到控制台。
编译和运行
	1.	编译服务器:
g++ -o server server.cpp -lboost_system -lpthread
	2.	




	3.	编译客户端:
g++ -o client client.cpp -lboost_system
	4.	




	5.	运行服务器:
./server <port>
	6.	




	7.	运行客户端:
./client <host> <port>
	8.	




通过这种方式,服务器可以处理多个客户端连接,并为每个连接提供一个线程进行通讯。客户端可以发送消息到服务器,服务器会原样返回消息给客户端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值