這裏想要討論的是多個子進程之間的通信.
以前若是有多個子進程之間通信的需要, 那麼我可能會選擇使用共享內存? 使用消息隊列? 或者使用一系列進程間通信機制
但這些同樣適用於沒有親緣關係的進程之間的通信. 這樣做似乎沒有必要.
這裏將要實現的子進程之間的通信, 使用了兩個技術:
一個是子進程繼承父進程的描述符特性(這裏是socketpair產生的管道描述符)
一個是利用socketpair傳遞描述符特性.
大致步驟如下:
父進程調用sokcetpair, 產生一個管道, fork產生第一個子進程, 這樣, 父子進程就能通過這個管道進行通信了
父進程再次調用socketpair, 之後fork出第二個子進程, 父進程與第二個子進程也能通過這個管道進行通信了
第二個子進程同時繼承了父進程與第一個子進程之間通信的管道, 說明此時第二個子進程能透過這個管道口向第一個子進程發送消息
只是這是單向的. 第一個子進程卻完全不瞭解第二個子進程的管道口, 甚至其是否存在都一無所知
父進程 通過 其與第一個子進程之間的管道口 將 父進程與第二個子進程通信的管道口 傳遞給第一個子進程
這時子進程記錄下這個管道口, 就能向第二個子進程發送數據了
(要注意的是, 描述符並不是一個單純的數字, 其在內核中對應着一組信息, 所以傳遞描述符需要特殊的方法)
第三個子進程被fork出來, 同樣, 它知道如何向第一和第二個子進程發送數據, 父進程則需要分別告訴第一和第二個子進程與第三個子進程通信的描述符
這樣, 產生的多個子進程之間就能兩兩通信了.
要注意的是, 每個新fork出來的子進程都要關閉不使用的之前進程的另一個管道描述符.
頭文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#ifndef CPC_H
#define CPC_H
#define DEBUG
#define MAX_EVENT 64
//消息分兩種, 一種是傳遞描述符; 一種在驗證的時候我們會發送字符串驗證
enum message_type{FD_TRANS = 0, MSG_TRANS};
//做個規定, socketpair的數組, 0給父進程用. 1給子進程用
//所需的關於每個進程的結構體
typedef struct {
pid_t pid;
int index;
int channel[2];
}process_t;
typedef struct {
enum message_type type;
//消息是來自哪個進程的
int sourceIndex;
}info_t;
typedef struct{
//傳遞描述符用這個
int fd;
//傳遞字符串用這個
char str[64];
}content_t;
typedef struct {
info_t info;
content_t data;
}message_t;
//每個子進程主函數體
void child_run(int index, process_t *processes);
//消息讀寫函數
int write_channel_fd(int fd, message_t *data);
int recv_channel(int fd, message_t *data);
//添加事件到epoll中
void add_fd_to_epoll_in(int epollfd, int fd);
#endif
頭文件實現:
#include "CPC.h"
int write_channel_fd(int fd, message_t *data){
struct msghdr msg_wr;
struct iovec iov[1];
struct cmsghdr *ptr = NULL;
union{
//使用這個結構體與真正要發的數據放在一起是爲了使輔助數據的地址對齊
struct cmsghdr cm;
char ctl[CMSG_SPACE(sizeof(int))];
}ctl_un;
switch(data->info.type) {
case FD_TRANS:
msg_wr.msg_control = ctl_un.ctl;
msg_wr.msg_controllen = sizeof(ctl_un.ctl);
ptr = CMSG_FIRSTHDR(&msg_wr);
ptr->cmsg_len = CMSG_LEN(sizeof(int));
ptr->cmsg_level = SOL_SOCKET;
ptr->cmsg_type = SCM_RIGHTS;
*((int *)CMSG_DATA(ptr)) = data->data.fd;
iov[0].iov_base = (void *)(&(data->info));
iov[0].iov_len = sizeof(info_t);
break;
case MSG_TRANS:
msg_wr.msg_control = NULL;
msg_wr.msg_controllen = 0;
iov[0].iov_base = data;
iov[0].iov_len = sizeof(message_t);
break;
}
msg_wr.msg_name = NULL;
msg_wr.msg_namelen = 0;
msg_wr.msg_iov = iov;
msg_wr.msg_iovlen = 1;
return (sendmsg(fd, &msg_wr, 0));
}
int recv_channel(int fd, message_t *data){
struct msghdr msg_rc;
struct iovec iov[1];
ssize_t n;
union{
struct cmsghdr cm;
char ctl[CMSG_SPACE(sizeof(int))];
}ctl_un;
struct cmsghdr *ptr = NULL;
msg_rc.msg_control = ctl_un.ctl;
msg_rc.msg_controllen = sizeof(ctl_un.ctl);
msg_rc.msg_name = NULL;
msg_rc.msg_namelen = 0;
iov[0].iov_base = (void *)data;
iov[0].iov_len = sizeof(message_t);
msg_rc.msg_iov = iov;
msg_rc.msg_iovlen = 1;
if((n = recvmsg(fd, &msg_rc, 0)) < 0){
perror("recvmsg error");
return n;
}
else if(n == 0){
//如果子進程收到0字節數據, 表明另一端已經關閉了.
//這裏的另一端指的是所有其他進程, 包括父進程
//如果某一個進程關閉了, 但還有其他進程持有該管道另一端, 則不會有0字節數據出現
//所以我們這裏如果其它進程都沒了, 此進程也結束.
fprintf(stderr, "peer close the socket\n");
exit(1);
}
if( ( ptr = CMSG_FIRSTHDR( &msg_rc ) ) != NULL //!> now we need only one,
&& ptr->cmsg_len == CMSG_LEN( sizeof( int ) ) //!> we should use 'for' when
) //!> there are many fds
{
if( ptr->cmsg_level != SOL_SOCKET )
{
fprintf(stderr, "Ctl level should be SOL_SOCKET\n");
exit(EXIT_FAILURE);
}
if( ptr->cmsg_type != SCM_RIGHTS )
{
fprintf(stderr, "Ctl type should be SCM_RIGHTS\n");
exit(EXIT_FAILURE);
}
data->data.fd = *(int*)CMSG_DATA(ptr); //!> get the data : the file des*
}
else
{
data->data.fd = -1;
}
return n;
}
void add_fd_to_epoll_in(int epollfd, int fd){
struct epoll_event event;
event.events = EPOLLIN;
event.data.fd = fd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
}
void child_run(int index, process_t *processes){
int n, k;
message_t out;
struct epoll_event events[MAX_EVENT];
int epollfd = epoll_create(10);
if(epollfd < 0){
perror("epoll_create error");
exit(-1);
}
out.info.type = MSG_TRANS;
out.info.sourceIndex = index;
out.data.fd = -1;
memset(out.data.str, '\0', 64);
snprintf(out.data.str, 63, "I'm %d, who was born later than you",index);
//記得關閉不用的端口
close(processes[index].channel[0]);
processes[index].channel[0] = -1;
for(k=0; k<index; k++){
//向之前就產生的子進程打招呼...
write_channel_fd(processes[k].channel[0], &out);
//在父進程中已經關閉了, 不用在關
//close(processes[k].channel[1]);
processes[k].channel[1] = -1;
}
add_fd_to_epoll_in(epollfd, processes[index].channel[1]);
for(;;){
n = epoll_wait(epollfd, events, MAX_EVENT, -1);
printf("epoll return :%d\n", n);
if(n < 0){
perror("epoll_wait error");
exit(-1);
}
else if(n == 0)
break;
if(events[0].events & EPOLLIN){
message_t msg;
int n;
n = recv_channel(events[0].data.fd, &msg);
if(n == 0){
close(processes[index].channel[1]);
continue;
}
int newfd, sourceIndex;
switch(msg.info.type){
case FD_TRANS:
//添加到processes裏面去
newfd = msg.data.fd;
if(newfd < 0){
fprintf(stderr, "child %d fail to receive fd\n", index);
exit(-1);
}
sourceIndex = msg.info.sourceIndex;
processes[sourceIndex].channel[0] = newfd;
processes[sourceIndex].channel[1] = -1;
#ifdef DEBUG
printf("I am child %d, I have just received %d's fd, %d bytes\n", index, sourceIndex, n);
#endif
printf("I am child %d, I'm gonna say hello to child %d\n", index, sourceIndex);
out.info.type = MSG_TRANS;
out.info.sourceIndex = index;
out.data.fd = -1;
memset(out.data.str, '\0', 64);
snprintf(out.data.str, 63, "hello,I'm %d", index);
n = write_channel_fd(processes[sourceIndex].channel[0], &out);
printf("I am child %d, I send %d bytes to %d\n", index, n, sourceIndex);
break;
case MSG_TRANS:
sourceIndex = msg.info.sourceIndex;
printf("I'm child %d, I have just received a message %d bytes from %d: \n%s\n", index,n,sourceIndex, msg.data.str);
break;
}
}
}
}
main函數:
//目的是在對父進程發送一個信號後, 每個子進程會向其他子同級別的子進程發送一句問候.
//傳入進程個數. 不傳的話默認是2個
#include "CPC.h"
int main(int ac, char *av[])
{
int numOfProcesses, i, j;
process_t *processes;
size_t n;
pid_t pid;
if(ac == 2)
numOfProcesses = atoi(av[1]);
else if(ac == 1)
numOfProcesses = 2;
else{
fprintf(stderr, "Usage : %s num\n", av[0]);
exit(-1);
}
processes = (process_t *)malloc(sizeof(process_t) * numOfProcesses);
for(i=0; i<numOfProcesses; i++)
processes[i].index = -1;
for(i=0; i<numOfProcesses; i++){
message_t msg;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, processes[i].channel) < 0){
perror("socketpair error");
exit(-1);
}
pid = fork();
switch(pid){
case -1:
perror("fork error");
exit(-1);
case 0:
//記得在子進程裏關掉其他子進程的管道某端
child_run(i, processes);
exit(1);
default:
break;
}
processes[i].pid = pid;
processes[i].index = i;
msg.info.type = FD_TRANS;
msg.info.sourceIndex = i;
msg.data.fd = processes[i].channel[0]; //父進程通過它和某子進程的管道 把 父進程和某子進程的通信口 傳過去
//給之前所有子進程發送這個口
for(j=0; j<i; j++){
#ifdef DEBUG
printf("As father, I just want to send child %d's fd to child %d\n",i, j);
#endif
//傳遞每個描述符
//父子進程會不會同時向同一個子進程傳遞數據?
//因爲父進程用0向子進程傳數據, 所起其他子進程也用這個0來傳
n = write_channel_fd(processes[j].channel[0], &msg);
close(processes[j].channel[1]);
#ifdef DEBUG
printf("As father, I have just sent child %d's fd to child %d , %d bytes\n",i, j, n);
#endif
}
}
//以上, 就已經將numOfProcesses個子進程連接起來了. 下面, 再實現一個功能.
//父進程收到SIGINT信號後, 讓子進程退出
return 0;
}