进程间通信(Inter process communication, IPC)以为这两个不同进程间可以交换数据,为了完成这一点,操作系统中应该提供两个进程可以同时访问的内存空间。因此,只要有两个进程可以同时访问的内存空间,就可以通过此空间来交换数据。但是,进程具有完全独立的内存结构,即使通过fork()函数创建的子进程,也不会与父进程共享内存,因此进程间通信需要通过其他的特殊方法完成。
1.1 利用管道实现进程间通信
为完成进程之间的通信,需要创建管道,管道不属于进程的资源(即不是fork复制的对象),而是和套接字一样,属于操作系统资源,因此通过管道首实现IPC的原理是,两个进程通过操作系统提供的内存进行通信。
创建管道的方式:
- #include
-
- /*
- 成功时返回0
- 失败时返回-1
- 参数:
- fields[0] 通过管道接收数据时使用的文件描述符
- fields[1] 通过管道传输数据时使用的文件描述符
- */
- int pipe(int fields[2]);
父进程调用pipe函数时将创建管道,同时获取对应于管道出入口的文件描述符,此时父进程可以读写同一管道,在调用fork创建子进程之后,子进程也将同时拥有管道出入口的文件描述符:
- #include
- #include
-
- #define BUFF_SIZE 30
-
- int main(int argc, char** argv)
- {
- int fds[2]; // 用于存储管道出入口的文件描述符
-
- char message[] = "Hello Process\n";
-
- char buffer[BUFF_SIZE];
-
- if (pipe(fds) == -1)
- {
- printf("Falied to create pipe.\n");
- return -1;
- }
-
- pid_t pid = fork(); // 创建进行
-
- if (pid == 0)
- {
- // 子进程 写入管道
- write(fds[1], message, sizeof(message));
- }
- else
- {
- // 父进程 读取管道
- read(fds[0], buffer, BUFF_SIZE);
- fputs(buffer, stdout);
-
- }
- return 0;
- }
进程间通信的示意图图下所示:
1.2 通过管道实现进程间双向通信
方案1:通过一个管道实现两个进程间的双向通信
代码实例:
- #include
- #include
- #include
-
- #define BUFF_SIZE 30
-
- int main(int argc, char** argv)
- {
- int fds[2]; // 用于存储管道出入口的文件描述符
-
- char message1[] = "Hello Process\n";
- char message2[] = "I am vac!\n";
-
- char buffer[BUFF_SIZE];
- memset(buffer, 0, BUFF_SIZE);
-
- if (pipe(fds) == -1)
- {
- printf("Falied to create pipe.\n");
- return -1;
- }
-
- pid_t pid = fork(); // 创建进行
-
- if (pid == 0)
- {
- // 子进程 写入管道
- write(fds[1], message1, sizeof(message1));
- sleep(2);
- read(fds[0], buffer, BUFF_SIZE);
- printf("Child proc received data: %s\n", buffer);
- memset(buffer, 0, BUFF_SIZE);
- }
- else
- {
- // 父进程 读取管道
- read(fds[0], buffer, BUFF_SIZE);
- printf("Parent proc received data: %s\n", buffer);
- write(fds[1], message2, sizeof(message2));
- sleep(3);
- memset(buffer, 0, BUFF_SIZE);
- }
- return 0;
- }
运行结果:
如果将子进程中的sleep方法注释掉,再运行代码:
- if (pid == 0)
- {
- // 子进程 写入管道
- write(fds[1], message1, sizeof(message1));
- //sleep(2);
- read(fds[0], buffer, BUFF_SIZE);
- printf("Child proc received data: %s\n", buffer);
- memset(buffer, 0, BUFF_SIZE);
- }
结果如下所示:
此时父进程无法再收到数据,因为子进程在写入数据后,未经过延时,立即又读取数据,因此管道中写入的数据被子进程提前取走。按照管道数据的读取机制,数据在写入管道之后,称为无主数据,哪个进程先通过read函数读取数据,哪个进程就先得到数据。结果导致父进程通过read函数再也读取不到数据,而将无限期等待管道数据。
注:利用一个管道进行进程间数据双向通信,程序设计者需要提前预测并控制运行流程,程序实现难度很大。
方案2:通过两个管道实现进程间相互通信
通过创建两个管道,各自在进程间负责不同的数据流,即可方便的实现进程间双向通信,采用两个管道即可避免程序运行流程的预测或控制。
代码实例:
- #include
- #include
- #include
-
- #define BUFF_SIZE 30
-
- int main(int argc, char** argv)
- {
- int fds1[2]; // 子进程向父进程写数据
- int fds2[2]; // 父进程向子进程写数据
-
- char message1[] = "Hello Process\n";
- char message2[] = "I am vac!\n";
-
- char buffer[BUFF_SIZE];
- memset(buffer, 0, BUFF_SIZE);
-
- if (pipe(fds1) == -1)
- {
- printf("Falied to create pipe.\n");
- return -1;
- }
-
- if (pipe(fds2) == -1)
- {
- printf("Falied to create pipe.\n");
- return -1;
- }
-
- pid_t pid = fork(); // 创建进行
-
- if (pid == 0)
- {
- // 子进程 写入管道
- write(fds1[1], message1, sizeof(message1));
- read(fds2[0], buffer, BUFF_SIZE);
- printf("Child proc received data: %s\n", buffer);
- memset(buffer, 0, BUFF_SIZE);
- }
- else
- {
- // 父进程 读取管道
- sleep(1); // 可以延时更短,稍微等待一下
- read(fds1[0], buffer, BUFF_SIZE);
- printf("Parent proc received data: %s\n", buffer);
- write(fds2[1], message2, sizeof(message2));
- memset(buffer, 0, BUFF_SIZE);
- }
- return 0;
- }
运行结果:
保存消息的回声服务器: 在向客户端提供服务的同时,服务端能够将接收到的数据同步保存到文件中。
客户端代码可复用《TCP/IP网络编程(8) 基于Linux的多进程服务器》中的客户端代码
- /*
- 客户端
- create_date: 2022-7-29
- */
-
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BUFF_SIZE 30
- #define ADDRESS "127.0.0.1"
- #define PORT 13100
-
- void readRoutine(int sock, char* buf);
- void writeRoutine(int sock, char* buf);
-
- int main(int argc, char** argv)
- {
- char buffer[BUFF_SIZE];
-
- struct sockaddr_in serverAddr; // 服务端地址
- memset(&serverAddr, 0, sizeof(serverAddr));
- serverAddr.sin_family = AF_INET;
- serverAddr.sin_addr.s_addr = inet_addr(ADDRESS);
- serverAddr.sin_port = htons(PORT);
-
- memset(buffer, 0, BUFF_SIZE);
-
- int socket = ::socket(PF_INET, SOCK_STREAM, 0);
-
- if (socket == -1)
- {
- printf("Failed to init socket.\n");
- return -1;
- }
-
- if (connect(socket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1)
- {
- printf("Failed to connect to server.\n");
- return -2;
- }
-
- printf("Successfully connect to the server.\n");
-
- pid_t pid = fork();
-
- if (pid == 0)
- {
- // 子进程负责发送
- writeRoutine(socket, buffer);
- }
- else
- {
- // 父进程负责接收
- readRoutine(socket, buffer);
- }
-
- close(socket);
-
- return 0;
-
- }
-
-
- void readRoutine(int sock, char* buf)
- {
- while (true)
- {
- memset(buf, 0, BUFF_SIZE);
-
- int str_len = read(sock, buf, BUFF_SIZE);
-
- if (str_len == 0) // EOF
- {
- printf("Receive EOF\n");
- return;
- }
-
- printf("\nReceive data from server: %s\n", buf);
- }
-
- }
-
- void writeRoutine(int sock, char* buf)
- {
- while (true)
- {
- fputs("Input message(Type Q(q) to quit): ", stdout);
-
- fgets(buf, BUFF_SIZE, stdin);
-
- if (strncmp(buf, "Q\n", 2) == 0 || strncmp(buf, "q\n", 2) == 0)
- {
- shutdown(sock, SHUT_WR);
- return;
- }
-
- write(sock, buf, strlen(buf));
- }
-
- }
服务端示例代码代码示例:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- void readChildProcess(int signo)
- {
- int status;
- pid_t pid = waitpid(-1, &status, WNOHANG); // 非阻塞
-
- if (WIFEXITED(status))
- {
- printf("Removed child process %d\n", WEXITSTATUS(status));
- }
- }
-
- #define BUFF_SIZE 30
- #define PORT 13100
-
- int main(int argc, char** argv)
- {
- int serverSocket;
- int clientSocket;
-
- struct sockaddr_in servAddr;
- struct sockaddr_in clientAddr;
-
- char buffer[BUFF_SIZE];
- memset(buffer, 0, BUFF_SIZE);
-
- socklen_t addrSize;
-
- int fds[2]; // 管道的文件描述符
-
- struct sigaction sigact;
- sigact.sa_handler = readChildProcess;
- sigemptyset(&sigact.sa_mask);
- sigact.sa_flags = 0;
- sigaction(SIGCHLD, &sigact, 0); // 注册信号
-
- serverSocket = socket(PF_INET, SOCK_STREAM, 0);
- if (serverSocket == -1)
- {
- printf("Failed to init server socket.\n");
- return -1;
- }
-
- memset(&servAddr, 0, sizeof(servAddr));
- servAddr.sin_family = AF_INET;
- servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servAddr.sin_port = htons(PORT);
-
- if (bind(serverSocket, (struct sockaddr*)&servAddr, sizeof(servAddr)) == -1)
- {
- printf("Failed to bind the server socket.\n");
- return -1;
- }
-
- if (listen(serverSocket, 5) == -1)
- {
- printf("Failed to listen client.\n");
- return -1;
- }
-
- // 创建管道
- pipe(fds);
-
- // 创建写文件进程
- pid_t pid = fork();
-
- if (pid == 0)
- {
- // 写文件子进程
- FILE* fp = fopen("data.txt", "a");
-
- char msg[100];
-
- while (true)
- {
- /* code */
- usleep(1000*40);
-
- memset(msg, 0, 100);
-
- int len = read(fds[0], msg, 100);
-
- // 解析读取到的内容
- if (len <= 5)
- {
- break;
- }
-
- char magicChar1 = msg[0];
- char magicChar2 = msg[1];
-
- if (magicChar1 != 'M' || magicChar2 != 'F')
- {
- break;
- }
-
- if (msg[2] == 1)
- {
- break;
- }
-
- ushort dataLen = msg[3] | (msg[4] << 8);
-
- if (dataLen <= 0)
- {
- continue;
- }
-
- // 将解析到的内容写入文件进行保存
- fwrite(&msg[5], dataLen, 1, fp);
- }
-
- fclose(fp);
- return 0;
- }
- else
- {
- // 父进程
- while (true)
- {
- printf("Waiting connect from client...\n");
-
- addrSize = sizeof(clientAddr);
-
- clientSocket = accept(serverSocket, (sockaddr*)&clientAddr, &addrSize);
-
- if (clientSocket == -1)
- continue;
-
- printf("Accept new connection from %s:%d.\n", inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));
-
- pid_t processId = fork();
-
- if (processId == 0)
- {
- // 子进程
- close(serverSocket);
- // 接受消息
-
- int recvLen = 0;
-
- // 构造报文
- char message[100];
-
- usleep(1000*100);
-
- while ((recvLen = read(clientSocket, buffer, BUFF_SIZE)) != 0)
- {
- /* code */
- write(clientSocket, buffer, recvLen);
-
- // 向管道写数据
- memset(message, 0, 100);
-
- message[0] = 'M';
- message[1] = 'F';
- message[2] = 0;
- message[3] = recvLen & 0xFF;
- message[4] = (recvLen >> 8) & 0xFF;
-
- memcpy(&message[5], buffer, recvLen); // 注意:memcpy里面的指针不要转换成void*
-
-
- write(fds[1], message, recvLen + 5);
-
-
- memset(buffer, 0, BUFF_SIZE);
- }
-
- close(clientSocket);
-
-
- memset(message, 0, 100);
-
- // 发送结束标志
- message[0] = 'M';
- message[1] = 'F';
- message[2] = 1;
- message[3] = 0;
- message[4] = 0;
-
- write(fds[1], (void*)message, 5);
-
-
- printf("Disconnecting from server...\n");
-
- sleep(2);
-
- return 0;
-
- }
- else if (processId == -1)
- {
- close(clientSocket);
- continue;
- }
- else
- {
- // 主进程
- printf("Create new process %d for client.\n", processId);
-
- close(clientSocket);
-
- memset(&clientAddr, 0, sizeof(clientAddr));
-
- continue;
- }
- }
-
- }
-
- close(serverSocket);
-
- return 0;
- }
-
-
- /*
- 服务器进程和文件进程之间设计通信格式
- 0 M // 报文头
- 1 F // 报文头
- 2 0/1 // 报文类型 0数据 / 1 结束
- 3 len // 数据长度 低位
- 4 len // 数据长度 高位
- 5 x // 数据
- 6 x // 数据
- 7 x // 数据
- . // .
- .
- .
- */
客户端运行结果:
服务端运行结果:
通过管道进行进程间通信,将服务端收到的消息发送给写文件进程,将收到的消息保存至文件:
-----------------------------------------//end//---------------------------------------------