Linux之进程间通信IPC

  进程间通信(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 }
View Code

二、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 }
View Code

客户端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 }
View Code

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 }
View Code

接收方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 }
View Code

四、信号量

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_RMIDSET_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 }
View Code

五、共享内存

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 }
View Code

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 }
View Code

六、套接字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 }
View Code

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 }
View Code

七、总结

posted @ 2019-08-23 17:52  博1990  阅读(543)  评论(0编辑  收藏  举报