进程间通信(IPC)就是指不同进程之间传播和交换数据,每个进程各自有不同的用户地址空间(虚拟地址空间),任何一个进程的全局变量在另一个进程中都看不到,所以进程间要交换数据必须能通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷贝到内核缓冲区,进程B再把数据从内核缓冲区拷贝到自己的用户空间,内核提供的这种机制称为进程间通信;Linux下IPC机制主要分为:管道/FIFO、消息队列/信号量/共享内存、套接字(socket);
- 随进程持续:IPC一直存在,直到打开IPC对象的进程关闭该对象后,比如管道和FIFO;
- 随内核持续:IPC一直存在,直到内核重新自举或者显示删除该对象为止,比如消息队列、信号量、共享内存;
- 随文件系统持续:IPC一直存在,直到显示删除该对象为止;????????????
一、管道(匿名)
1 定义:在内核开辟出一块缓冲区称之为管道文件,包含一个读端和一个写端,进程间可以通过该缓冲区进行通信;
2 特点:半双工形式、父子进程、自带同步机制、进程结束后管道被删除;
3 用法:父进程调用pipe创建管道,得到两个文件描述符fd[2],fd[0]可读,fd[1]可写;父进程fork出子进程,子进程也同样得到两个描述符fd[2];父进程关闭读端fd[0],子进程关闭写端fd[1],这样父进程往管道里写数据,子进程从管道里读数据,管道是用环形队列实现的,数据从写端流入,从读端流出;说明如下:
(1)读管道时,若管道为空则阻塞,直到写端write将数据写入到管道;若写端被关闭则返回0;
(2)写管道时,若管道已满则阻塞,直到读端read将管道内数据取走;若读端被关闭则返回错误-1,产生信号SIGPIPE;
4 代码实现如下:#include <unistd.h>,若成功则返回0,出错则返回-1
- int pipe(int fd[2]);
- ssize_t write (int fd, const void *buf, size_t nbytes) //返回已写的字节数
- ssize_t read (int fd, void *buf, size_t nbytes) //返回读到的字节数
-
int close (int fd);


1 #include <stdio.h> 2 #include <unistd.h> 3 #include <string.h> 4 5 #define ABCDE "abcde" 6 #define FGHIJ "fghij" 7 8 int main(int argc, char *argv[]) 9 { 10 int fd[2] = {0}, size = 0; 11 pid_t pid = -1; 12 char buf[256] = {0}; 13 14 if(pipe(fd)) //1 pipe 15 { 16 perror("pipe"); 17 return 1; 18 } 19 pid = fork(); //2 fork 20 if(pid < 0) 21 { 22 perror("fork"); 23 return 2; 24 } 25 else if (pid == 0)//3 child write fd[1] 26 { 27 printf("child start\n"); 28 close(fd[0]); 29 size = write(fd[1], ABCDE, 5); 30 printf("child,msg=%s,size=%d\n", ABCDE, size); 31 //sleep(1); 32 size = write(fd[1], FGHIJ, 5); 33 printf("child,msg=%s,size=%d\n", FGHIJ, size); 34 } 35 else //3 father read fd[0] 36 { 37 printf("father start\n"); 38 close(fd[1]); 39 //sleep(1); 40 size = read(fd[0], buf, 256); //block until child write to fd[1] 41 printf("father,msg=%s,size=%d\n", buf, size); 42 memset(buf, 0, sizeof(buf)); 43 size = read(fd[0], buf, 256); //block until child write to fd[1] 44 printf("father,msg=%s,size=%d\n", buf, size); 45 } 46 return 0; 47 }
二、FIFO(有名管道)
1 定义同上面的匿名管道类似(mkfifo),唯一不同的是每个FIFO都关联一个路径名,从而允许无血缘关系的进程间通信;
2 特点:可以用于无血缘关系的进程间通信,只需要一个进程创建FIFO,其他进程直接使用这个已创建好的FIFO即可;
3 用法:服务端进程调用mkfifo来创建一个众所周知的FIFO(所有客户端都直到该FIFO的路径),然后客户端进程通过open/write/close来写入数据到FIFO,服务端通过open/read/close来从FIFO中读取数据;如果是多个客户端写数据,那么每个客户端发送的数据大小必须要小于PIPE_BUF字节,否则会造成多次写之间的交叉;
4 代码如下:#include <unistd.h>
- int mkfifo(const char *path, mode_t mode); //#include <sys/stat.h>
-
int access (const char *name, int type);
- int open (const char *path, int oflag, ...) //#include <fcntl.h>
- ssize_t write (int fd, const void *buf, size_t nbytes)
- ssize_t read (int fd, void *buf, size_t nbytes)
- int close (int fd);
服务端server.c:


1 #include <stdio.h> 2 #include <unistd.h> //read,write,close 3 #include <sys/stat.h> //mkfifo 4 #include <fcntl.h> //open,O_RDONLY 5 6 #define FIFO_NAME "myfifo" 7 8 int main(int argc, char *argv[]) 9 { 10 int res = -1, fd = -1, size = -1; 11 char buf[100] = ""; 12 13 res = mkfifo(FIFO_NAME, 0777); 14 if(res != 0 ) 15 { 16 printf("mkfifo error\n"); 17 } 18 fd = open(FIFO_NAME, O_RDONLY); 19 if(fd < 0) 20 { 21 printf("open error\n"); 22 } 23 24 size = read(fd, buf, 100); 25 printf("read, size=%d, buf=%s\n", size, buf); //block until client write to fd 26 size = read(fd, buf, 100); 27 printf("read, size=%d, buf=%s\n", size, buf); //block until client write to fd 28 29 close(fd); 30 31 printf("end\n"); 32 return 0; 33 }
客户端clien.c:


1 #include <stdio.h> 2 #include <unistd.h> //read,write,close,access 3 #include <fcntl.h> //open,O_WRONLY 4 5 #define FIFO_NAME "myfifo" 6 7 int main(int argc, char *argv[]) 8 { 9 int res = -1, fd = -1, size = -1; 10 char buf[100] = "abcdefghijklmnopqrstuvwxyz"; 11 12 res = access(FIFO_NAME, F_OK); 13 if(res != 0) 14 { 15 printf("mkfifo error\n"); 16 } 17 fd = open(FIFO_NAME, O_WRONLY); 18 if(fd < 0) 19 { 20 printf("open error\n"); 21 } 22 23 size = write(fd, buf, sizeof(buf)); 24 printf("write, size=%d, buf=%s\n", size, buf); 25 sleep(2); 26 size = write(fd, buf, sizeof(buf)); 27 printf("write, size=%d, buf=%s\n", size, buf); 28 29 close(fd); 30 31 printf("end\n"); 32 return 0; 33 }
XSI IPC(基于System V IPC)提供了三种进程间通信方式:消息队列、信号量、共享内存,XSI IPC不再以文件的形式存在,因此没有文件描述符的概念,所以不能用文件I/O函数来操作,但是它有类似的“标识符”(相当于文件描述符的替代者)即键key来唯一标识内核对象,需要适使用XSI IPC特有的API来进行通信;
- key_t ftok(const char *path, int id); //#include <sys/ipc.h>
三、消息队列
1 定义:内核创建用于存放信息的链表,对消息队列有写权限的进程可以按照一定的格式添加新消息,对消息队列有读权限的进程可以从消息队列中读出信息;消息队列由两部分构成,即消息编号和消息正文,如下:
2 特点:只需要一个进程创建消息队列,其他进程直接使用已创建好的消息队列即可;
3 用法:使用msgget创建一个新队列或打开一个已存在的队列,并返回一个唯一标识消息队列的标识符(msgid),后续收发信息(msgsnd+msgrcv)都通过msgid来实现;msgctl利用标识符msgid来删除消息队列;
4 代码如下:#include <sys/msg.h>,若成功则返回0,出错则返回-1
- int msgget(key_t key, int flag); //返回消息队列ID
- int msgsnd(int msgid, const void *ptr, size_t nbytes, int flag);
- ssize_t msgrcv(int msgid, void *ptr, size_t nbytes, long type, int flag); //返回消息数据部分的长度
- int msgctl(int msgid, int cmd, struct msqid_ds *buf); //cmd = IPC_STAT或IPC_SET或IPC_RMID
发送方send.c:


1 #include <stdio.h> 2 #include <sys/ipc.h> 3 #include <fcntl.h> 4 #include <sys/msg.h> 5 6 typedef struct _ST_MSGBUF { 7 long type; 8 char buf[100]; 9 } ST_MSGBUF; 10 11 int main(int argc, char *argv[]) 12 { 13 key_t key = -1; 14 int msgid = -1; 15 ST_MSGBUF stMsgSnd = {1, "abcde"}; //type must > 0 16 int res = -1; 17 18 key = ftok("mymsg", '6'); 19 printf("key:%d\n", key); 20 21 msgid = msgget(key, IPC_CREAT | O_WRONLY | 0777); 22 printf("msgid:%d\n", msgid); 23 24 res = msgsnd(msgid, &stMsgSnd, sizeof(stMsgSnd)-sizeof(stMsgSnd.type), 0); 25 26 printf("res=%d\n", res); 27 28 return 0; 29 }
接收方recv.c:


1 #include <stdio.h> 2 #include <sys/ipc.h> 3 #include <fcntl.h> 4 #include <sys/msg.h> 5 6 typedef struct _ST_MSGBUF { 7 long type; 8 char buf[100]; 9 } ST_MSGBUF; 10 11 int main(int argc, char *argv[]) 12 { 13 key_t key = -1; 14 int msgid = -1; 15 ST_MSGBUF stMsgRcv = {0}; 16 int res = -1; 17 18 key = ftok("mymsg", '6'); 19 printf("key:%d\n", key); 20 21 msgid = msgget(key, O_RDONLY); 22 printf("msgid:%d\n", msgid); 23 24 res = msgrcv(msgid, &stMsgRcv, sizeof(stMsgRcv)-sizeof(stMsgRcv.type), 0, 0); 25 printf("recv:type=%lu, buf=%s, res=%d\n", stMsgRcv.type, stMsgRcv.buf, res); 26 27 res = msgctl(msgid, IPC_RMID, NULL); 28 printf("res=%d\n", res); 29 30 return 0; 31 }
四、信号量
1 定义:一种具有原子操作的计数器,它控制多个进程对共享资源的访问,信号量本身不具有通信的功能,而是控制其他资源来实现进程间通信,负责了互斥+同步功能;
2 特点:可以同时让多个进程访问临界资源;
3 用法:第一步调用semget函数创建新的信号量集合或者获取已有的信号量集合,第二步调用semctl函数初始化集合中的每个信号量,第三步调用semop函数对集合中的信号量进行PV操作,第四部调用semctl函数删除信号量集合;
4 代码如下:#include <sys/sem.h>,若成功则返回0,出错则返回-1
- int semget(ket_t key, int nems, int flag); //返回信号量ID
- int semop(int semid, struct sembuf semoparray[], size_t nops);
- int semctl(int semid, int semnum, int cmd, ...); //cmd = IPC_RMID或SET_VAL或……


1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/sem.h> 4 5 union semun{ 6 int val; 7 struct semid_ds *buf; 8 unsigned short *array; 9 }; 10 11 int sem_create() 12 { 13 key_t key; 14 int semid = -1; 15 key = ftok("mysem", 1); 16 semid = semget(key, 1, IPC_CREAT | 0666); 17 18 return semid; 19 } 20 21 int sem_init(int semid) 22 { 23 union semun _semun; 24 int res = -1; 25 _semun.val = 1; 26 res = semctl(semid, 0, SETVAL, _semun); 27 28 return res; 29 } 30 31 int sem_destroy(int semid) 32 { 33 int res = -1; 34 res = semctl(semid, 0, IPC_RMID); 35 36 return res; 37 } 38 39 int sem_p(int semid) 40 { 41 struct sembuf _sembuf; 42 int res = -1; 43 44 _sembuf.sem_num = 0; 45 _sembuf.sem_op = -1; //-1:p 46 _sembuf.sem_flg = SEM_UNDO; 47 res = semop(semid, &_sembuf, 1); 48 49 return res; 50 } 51 52 int sem_v(int semid) 53 { 54 struct sembuf _sembuf; 55 int res = -1; 56 57 _sembuf.sem_num = 0; 58 _sembuf.sem_op = 1; //1:V 59 _sembuf.sem_flg = SEM_UNDO; 60 res = semop(semid, &_sembuf, 1); 61 62 return res; 63 } 64 65 int main(int argc, char *argv[]) 66 { 67 printf("enter\n"); 68 pid_t pid = -1; 69 int res = -1, semid = -1; 70 71 semid = sem_create(); 72 res = sem_init(semid); 73 //printf("res:%d, semid:%d\n", res, semid); 74 75 pid = fork(); 76 if(pid < 0) 77 { 78 perror("fork"); 79 return 2; 80 } 81 else if (pid == 0)//child 82 { 83 while(1) 84 { 85 res = sem_p(semid); 86 printf("B"); 87 fflush(stdout); 88 sleep(1); 89 printf("B"); 90 fflush(stdout); 91 92 res = sem_v(semid); 93 } 94 } 95 else //father 96 { 97 while(1) 98 { 99 res = sem_p(semid); 100 101 printf("A"); 102 fflush(stdout); 103 sleep(1); 104 printf("A"); 105 fflush(stdout); 106 107 res = sem_v(semid); 108 } 109 } 110 res = sem_destroy(semid); 111 printf("res:%d\n", res); 112 113 return 0; 114 }
五、共享内存
1 定义:把不同进程的部分地址空间通过页表映射到同一片物理地址
2 特点:无需额外的系统调用或内核操作,避免了多余 的内存拷贝,所以效率最高;但是内核不提供任何对共享内存访问的同步机制,所以一般搭配信号量使用;
3 用法:第一步调用shmget函数创建或打开共享内存,第二步调用shmat将共享内存映射到进程空间的某一地址,然后开始使用共享内存通信,第三步调用shmdt将共享内存与进程空间分离,第四步调用shmctl删除共享内存段,
4 代码如下:#include <sys/shm.h>,若成功则返回0,出错则返回-1
- int shmget(key_t key, size_t size, int flag); //返回共享内存ID
- int *shmat(int shmid, ocnst void *addr, int flag); //返回指向共享内存段的指针
- int shmdt(const void *addr);
- int shmctl(int shmid, int cmd, struct shmid_ds *buf); //cmd = IPC_RMID或……
write.c:


1 #include <stdio.h> 2 #include <sys/shm.h> 3 4 int main(int argc, char *argv[]) 5 { 6 int shmid = -1; 7 void *shm = NULL; 8 int *pshare = NULL; 9 int res = -1; 10 int semid = -1; 11 12 key_t key = ftok("myshm", 1); 13 printf("key:%d\n", key); 14 15 shmid = shmget(key, sizeof(int), IPC_CREAT | 0666); 16 printf("shmid:%d\n", shmid); 17 18 shm = shmat(shmid, 0, 0); 19 if((void*)-1 == shm) 20 { 21 printf("shmat error\n"); 22 } 23 24 pshare = (int*)shm; 25 *pshare = 10; 26 27 res = shmdt(shm); 28 printf("res:%d\n", res); 29 30 return 0; 31 }
read.c:


1 #include <stdio.h> 2 #include <sys/shm.h> 3 4 int main(int argc, char *argv[]) 5 { 6 int shmid = -1; 7 void *shm = NULL; 8 int *pshare = NULL; 9 int res = -1; 10 11 key_t key = ftok("myshm", 1); 12 printf("key:%d\n", key); 13 14 shmid = shmget(key, sizeof(int), IPC_CREAT | 0666); 15 printf("shmid:%d\n", shmid); 16 17 shm = shmat(shmid, 0, 0); 18 if((void*)-1 == shm) 19 { 20 printf("shmat error\n"); 21 } 22 23 pshare = (int*)shm; 24 printf("value of pshare:%d\n", *pshare); 25 26 res = shmdt(shm); 27 printf("res:%d\n", res); 28 29 res = shmctl(shmid, IPC_RMID, 0); 30 printf("res:%d\n", res); 31 32 return 0; 33 }
六、套接字socket
1 定义:socket是应用层和TCP/IP协议族通信的中间软件抽象层,把复杂的TCP/IP协议隐藏在socket接口后面,即socket是一组对TCP/IP协议族进行封装的API。
2 特点:可以用于不同PC之间的进程间通信;流套接字(TCP)和数据报套接字(UDP);
3 用法:
(1)服务端:socket---bind---listen---accept---write/read、send/recv
(2)客户端:socket--------------------connect---write/read、send/recv
4 代码如下:#include <sys/socket.h>,若成功则返回0,出错则返回-1
- int socket(int domain, int type, int protocol); //AF_INET, SOCK_STREAM或SOCK_DGRAM, IPPROTO_TCP或IPPROTO_UDP,若成功则返回socket描述符
- int bind(int sockfd, const struct sockaddr *addr, socklen_t len);
- int listen(int sockfd, int backlog);
- int accept(int sockfd, struct sockaddr *addr, socklen_t *len); //若成功则返回socket描述符
- int connect(int sockfd, const struct sockaddr *addr, socklen_t len);
- uint32_t htonl(uint32_t hostin32); //32位主机字节序--->32位网络字节序
- uint16_t htons(uint16_t hostin16); //16位主机字节序--->16位网络字节序
- uint32_t ntohl(uint32_t netint32); //32位网络字节序--->32位主机字节序
- uint16_t ntohs(uint16_t netint16); //16位网络字节序--->16位主机字节序
- const char *inet_ntop(int domain, const void *addr, char *str, socklen_t size); //网络字节序--->点分十进制文本字符串,返回地址字符串指针
- int inet_pton(int domain, const char *str, void *addr); //点分十进制文本字符串--->网络字节序
- ssize_t send(int sockfd, const void *buf, size_t nbytes, int flags);
- ssize_t recv(int sockfd, void *buf, size_t nbytes, int flags);
关于send函数:
(1)如果send函数发送的字节数nbytes > 套接字sockfd发送缓冲区的长度,则返回SOCKET_ERROR;
(2)如果nbytes <= sockfd发送缓冲区的长度,则首先检查协议是否在发送sockfd发送缓冲区的长度,若是则等待协议发送完成;否则比较nbytes和socketfd发送缓冲区的剩余空间的大小;
(3)如果nbytes > sockfd发送缓冲区的剩余空间大小,则等待协议把sockfd发送缓冲区的数据发送完;
(4)如果nbytes < sockfd发送缓冲区的剩余空间大小,则把buf中的nbytes数据copy到sockfd发送缓冲区的剩余空间里;
(5)send函数copy成功后则返回实际copy的字节数,否则返回SOCKET_ERROR,比如copy出错、网络断开等;
关于recv函数
(1) 等待协议发送sockfd的发送缓冲区中的数据,如果发送过程网络错误则返回SOCKET_ERROR;发送完成后则检查接收缓冲区;
(2)如果sockfd接收缓冲区没有数据或者协议正在接收数据,则recv函数一直等待;
(3)协议把数据接收完成后,recv函数则把接收缓冲区的数据copy到buf中;
(4)recv函数copy成功后则返回实际copy的字节数,否则返回SOCKET_ERROR,比如copy出错、网络断开等;
server.c


1 #include <unistd.h> //close 2 #include <stdio.h> //printf 3 //#include <sys/socket.h> 4 #include <arpa/inet.h> //inet_pton 5 #include <string.h> //memset 6 7 int main(int argc, char *argv[]) 8 { 9 int listenfd, connfd, n = -1; 10 struct sockaddr_in serveraddr; 11 char recvbuf[100] = ""; 12 char sendbuf1[6] = "abcde"; 13 char sendbuf2[6] = "fghij"; 14 char sendbuf3[6] = "klmno"; 15 char sendbuf4[6] = "pqrst"; 16 17 //1.socket 18 printf("1.socket\n"); 19 if((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 20 { 21 printf("server, socket error\n"); 22 } 23 //2.bind 24 printf("2.bind\n"); 25 serveraddr.sin_family = AF_INET; 26 serveraddr.sin_port = htons(6666); 27 serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); 28 if(bind(listenfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr)) == -1) 29 { 30 printf("bind error\n"); 31 } 32 printf("port:%d\n", serveraddr.sin_port); 33 int len = 0; 34 if(getsockname(listenfd, (struct sockaddr *)&serveraddr, &len) == -1) 35 { 36 printf("getsockname error\n"); 37 } 38 printf("port:%d\n", serveraddr.sin_port); 39 //3.listen 40 printf("3.listen\n"); 41 if(listen(listenfd, 1) == -1) 42 { 43 printf("listen error\n"); 44 } 45 //4.accept 46 printf("4.accept\n"); 47 if((connfd = accept(listenfd, NULL, NULL)) == -1) 48 { 49 printf("accept error\n"); 50 } 51 //5.send/recv 52 n = recv(connfd, recvbuf, 10, 0); 53 printf("recv %d: %s\n", n, recvbuf);//abcdefghij 54 n = recv(connfd, recvbuf, 10, 0); 55 printf("recv %d: %s\n", n, recvbuf);//klmnopqrst 56 n = recv(connfd, recvbuf, 10, 0); 57 printf("recv %d: %s\n", n, recvbuf);//uvwxyz 58 59 n = send(connfd, sendbuf1, 5, 0); 60 printf("send %d\n", n); 61 n = send(connfd, sendbuf2, 5, 0); 62 printf("send %d\n", n); 63 64 //6.close 65 //sleep(5); 66 printf("close\n"); 67 close(connfd); 68 close(listenfd); 69 70 return 0; 71 }
client.c


1 #include <unistd.h> //close 2 #include <stdio.h> //printf 3 //#include <sys/socket.h> 4 //#include <sys/types.h> 5 //#include <netinet/in.h> 6 #include <arpa/inet.h> //inet_pton 7 8 9 int main(int argc, char *argv[]) 10 { 11 int sockfd, n = -1; 12 struct sockaddr_in serveraddr; 13 char sendbuf[100] = "abcdefghijklmnopqrstuvwxyz"; 14 char recvbuf[100] = ""; 15 16 //1.socket 17 printf("1.socket\n"); 18 if((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) 19 { 20 printf("socket error\n"); 21 return 0; 22 } 23 //2.connct 24 printf("2.connect\n"); 25 serveraddr.sin_family = AF_INET; 26 serveraddr.sin_port = htons(6666); 27 if(inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr) <= 0) 28 { 29 printf("inet_pton error\n"); 30 } 31 if(connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) 32 { 33 printf("connet error\n"); 34 } 35 //3.send/recv 36 n = send(sockfd, sendbuf, 100, 0); 37 printf("send %d\n", n); 38 n = recv(sockfd, recvbuf, 100, 0); 39 printf("recv %d: %s\n", n, recvbuf); //abcde 40 41 n = recv(sockfd, recvbuf, 100, 0); 42 printf("recv %d: %s\n", n, recvbuf); //fghij 43 44 //4.close 45 //sleep(5); 46 printf("close\n"); 47 close(sockfd); 48 49 return 0; 50 }
七、总结