进程间通信常见的有5种渠道:编程
管道、信号量、共享内存、消息队列、套接字
下面来一一简单说明:segmentfault
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<assert.h> #include<fcntl.h> #include<string.h> #include<signal.h> //a.c void fun(int sig) { printf("sig=%d\n", sig); } int main() { signal(SIGPIPE, fun); int fdw = open("./fifo", O_WRONLY); assert(fdw != -1); printf("fdw=%d\n", fdw); int newfdw = dup(fdw); char buff[128] = {0}; // dup2(fdw, 1); // printf("hello"); while(1) { printf("input:\n"); fgets(buff, 128, stdin); putchar('\n'); if(strncmp(buff, "end", 3) == 0) { break; } write(newfdw, buff, strlen(buff)); } close(fdw); close(newfdw); } //b.c int main() { int fdr = open("./fifo", O_RDONLY); assert(fdr != -1); printf("fdr=%d\n", fdr); char buff[128] = {0}; while(1) { memset(buff, 0, 128); int len = read(fdr, buff, 127); if(len == 0) { break; } printf("len=%d, buff=%s\n", len, buff); } close(fdr); }
管道文件的使用和普通文件是同样的,一方读取,一方写入,不过在a.c当中有两个有意思的函数,dup();和dup2();在这个例子中,dup()用newfdw表明了fdw,而dup2()的做用是用fdw去替换标准输出,有兴趣不妨试试,是两个有趣的函数,这里很少赘述。数组
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> int main() { int fd[2]; pipe(fd); pid_t pid = fork(); assert(pid != -1); if(pid == 0) { close(fd[0]); char buff[128] = {0}; while(1) { printf("input:\n"); fgets(buff, 128, stdin); if(strncmp(buff, "end", 3) == 0) { break; } write(fd[1], buff, 127); sleep(1); } close(fd[1]); } else { close(fd[1]); char buff[128] = {0}; while(1) { read(fd[0], buff, 127); printf("buff=%s\n", buff); } close(fd[0]); } }
无名管道是由系统建立的,用户并不知道它姓甚名谁,也就难以在其余进程中使用了,但在父子进程中,由于子进程会继承父进程的文件信息,故而在子进程中仍然存在着无名管道,但在使用中通常防止出错,乙方负责读取时必须关闭写端,一方负责写入时必须关闭读端缓存
另外管道的最大长度经过测试可得最大为64K,可是这并不许确,这个大小应该能够经过修改Linux下的内核参数来修改最大长度。网络
同时在这里咱们能够引入一下原子操做,通常原子操做是系统调用中会实现,且原子操做会消耗很大的系统资源,而信号量机制也能够用于实现原子操做。
贴上3个概念:
临界资源:同一时刻只容许一个进程访问的资源
临界区:访问临界资源的代码段
原子操做:是一种不可分割不可中断的操做多线程
信号量机制也是实现原子操做一种方式,这些都是为了去保证在多进程、多线程环境下的内存可见性问题,可是一样的,这种机制对于计算机资源形成了很大的负担,这也就产生了CAS操做,https://segmentfault.com/a/11...
关于CAS操做能够参考一下这篇文章,这里很少赘述了。dom
思路其实很简单,咱们只须要分辨出临界区——————必定要注意就是那个同一时间下只能有一个线程去访问占有资源的代码处,在临界区的上面进行p操做,在临界区下面则进行v操做便可。socket
//sem.h #include<stdio.h> #include<sys/sem.h> #include<stdlib.h> #include<string.h> #include<assert.h> union semun { int val; }; void sem_init(); void sem_p(); void sem_v(); void sem_destroy();
//sem.c #include"sem.h" static semid = -1; void sem_init() { semid = semget((key_t)1234,1,IPC_CREAT | IPC_EXCL | 0600); if(semid == -1) { //已经存在,或者系统资源不足 //处理已经存在 semid = semget((key_t)1234, 1, 0600); if(semid == -1) { //确实系统资源不足 perror("semget error\n"); } } else { union semun a; a.val = 1; //a.val = 0; if(semctl(semid, 0, SETVAL, a) == -1) { perror("semctl error\n"); } } } void sem_p() { struct sembuf buf; buf.sem_num = 0;//下标为0 buf.sem_op = -1;//p buf.sem_flg = SEM_UNDO;//异常结束会将所作操做复原 if(semop(semid, &buf, 1) == -1) { perror("sem_p error\n"); } } void sem_v() { struct sembuf buf; buf.sem_num = 0;//下标为0 buf.sem_op = 1;//v buf.sem_flg = SEM_UNDO;//异常结束会将所作操做复原 if(semop(semid, &buf, 1) == -1) { perror("sem_p errori\n"); } } void sem_destroy() { if(semctl(semid, 0, IPC_RMID) == -1) { perror("destroy error\n"); } }
//sema.c #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<string.h> #include<sys/sem.h> #include"sem.h" int main() { sem_init(); int i; for(i = 0; i < 10; i++) { sem_p(); write(1,"A",1); int n = rand() % 3; sleep(n); write(1,"A",1); sem_v(); n = rand() % 3; sleep(n); } return 0; }
这里面出现了几个系统调用:
在#include<sys/sem.h>中:函数
int semget(key_t key, int nsems, int semflg); int semctl(int semid, int semnum, int cmd,...); int semop(int semid, struct sembuf *sops, unsigned nsops); //内部的成员结构: union semun { short val;//SETVAL用的值 struct semid_ds *buf;//IPC_STAT、IPC_SET用的semid_ds结构 unsigned short *array;//SETALL、GETALL用的数组值 struct seminfo *buf;//为控制IPC_INFO提供的缓存 }; struct sembuf { short semnum;//信号量集合中的信号量编号,0表明第一个信号量 short val;//进行P/V操做所加减的值 short flag; /* 0设置信号量的默认操做 IPC_NOWAIT设置信号量操做不等待 SEM_UNDO会让内核记录一个与调用进程相关的UNDO记录,若是该进程崩溃,则根据这个进程的UNDO记录自动回复相应信号量的计数值 */ }
semctl的cmd选项列表
IPC_STAT
从信号量集上检索semid_ds结构,并存到semun联合体参数的成员buf的地址中IPC_SET
设置一个信号量集合的semid_ds结构中ipc_perm域的值,并从semun的buf中取出值postIPC_RMID
从内核中删除信号量集合GETALL
从信号量集合中得到全部信号量的值,并把其整数值存到semun联合体成员的一个指针数组中GETNCNT
返回当前等待资源的进程个数GETPID
返回最后一个执行系统调用semop()进程的PIDGETVAL
返回信号量集合内单个信号量的值GETZCNT
返回当前等待100%资源利用的进程个数SETALL
用联合体中val成员的值设置信号量集合中所有信号量的值SETVAL
用联合体中val成员的值设置信号量集合中单个信号量的值
关于这几个系统调用的详情可参考于http://blog.csdn.net/guoping1...,对应可理解上面的例子。
在#include<semaphore.h>中:
sem_init(sem_t *sem, int pshared, unsigned int value);
pshared 参数指明信号量是由进程内线程共享,仍是由进程之间共享。若是 pshared 的值为 0,那么信号量将被进程内的线程共享,而且应该放置在这个进程的全部线程均可见的地址上(如全局变量,或者堆上动态分配的变量)。若是 pshared 是非零值,那么信号量将在进程之间共享,而且应该定位共享内存区域。
sem_wait(sem_t *sem);//对应P操做
sem_post(sem_t *sem);//对应V操做
//实例1 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<semaphore.h> #include<pthread.h> static sem_t sem; static pthread_mutex_t mutex; void *fun(void *arg) { int i; for(i = 0; i < 10; i++) { sem_wait(&sem); //pthread_mutex_lock(&mutex); printf("B"); fflush(stdout); sleep(1); printf("B"); fflush(stdout); sem_post(&sem); //pthread_mutex_unlock(&mutex); sleep(1); } pthread_exit("fun exit\n"); } int main() { // sem_init(&sem, 0, 1); pthread_mutex_init(&mutex, NULL); pthread_t id; pthread_create(&id, NULL, fun, NULL); int i; for(i = 0; i < 10; i++) { // sem_wait(&sem); pthread_mutex_lock(&mutex); printf("A"); fflush(stdout); sleep(1); printf("A"); fflush(stdout); // sem_post(&sem); pthread_mutex_unlock(&mutex); sleep(1); } printf("main over\n"); char *s = NULL; pthread_join(id, (void **)&s); printf("%s", s); exit(0); }
//实例2 #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<unistd.h> #include<string.h> #include<pthread.h> #include<semaphore.h> sem_t sem; void *fun(void *arg) { char *buff = (char *)arg; sem_wait(&sem); char *temp = NULL; char *token = NULL; token = strtok_r(buff, " ", &temp); printf("fun token=%s\n", token); sleep(1); while((token = strtok_r(NULL, " ", &temp)) != NULL) { printf("fun token=%s\n", token); sleep(1); } sem_post(&sem); } int main() { char main_buf[] = "1 2 3 4 5 6 7 8 9 10"; char fun_buf[] = "A B C D E F G H I J"; //sem sem_init(&sem, 0, 1); pthread_t id; pthread_create(&id, NULL, fun, (void *)fun_buf); //strtok(char *, " ") sem_wait(&sem); char *token = NULL; char *temp = NULL; token = strtok_r(main_buf, " ", &temp); printf("main token=%s\n", token); sleep(1); while((token = strtok_r(NULL, " ", &temp)) != NULL) { printf("main token=%s\n", token); sleep(1); } sem_post(&sem); pthread_join(id,NULL); exit(0); }
上面的例子中还用到了互斥锁,也就是那个mutex,对应两个函数pthread_mutex_lock(pthread_mutex_t mutex),pthread_mutex_unlock(pthread_mutex_t mutex),和信号量相似,也是在临界区上方加锁下方解锁,这里下次再说吧这个。
//a #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/shm.h> int main() { sem_init(); int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0);//NULL不指定进程连接中的地址具体位置,0即便标识位,为默认) assert((int) s != -1); //assert(s != (char *)-1); while(1) { sem_p(0); printf("input str\n"); char buff[128] = {0}; fgets(buff, 128, stdin); strcpy(s, buff); sem_v(1); if(strncmp(buff, "end", 3) == 0) { break; } } shmdt(s);//断开连接s exit(0); }
//b #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<string.h> #include<assert.h> #include<sys/shm.h> int main() { sem_init(); int shmid = shmget((key_t)1234, 256, IPC_CREAT | 0600); assert(shmid != -1); char *s = (char *)shmat(shmid, NULL, 0);//NULL不指定进程连接中的地址具体位置,0即便标识位,为默认) assert((int) s != -1); //assert(s != (char *)-1); while(1) { sem_p(1); if(strncmp(s, "end", 3) == 0) { break; } printf("read:%s\n", s); sleep(1); sem_v(0); } shmdt(s);//断开连接s sem_destroy(); exit(0); }
这里关于信号量的不在多提,在上面也说到,由于系统内核没有对访问共享内存进行同步,因此必须提供本身的同步措施,因此会用到信号量。而且这里用的信号量是自行实现的,关于实如今前面的实例中有展开。
在头文件#include<sys/shm.h>中:
int shmget(ket_t key, size_t size, int shmflg);
void shmat(int shmid, const void shmaddr, int shmflg);
int shmdt(const void *shmaddr);
详情可参见:http://blog.csdn.net/guoping1...
优势:
A. 咱们能够经过发送消息来几乎彻底避免命名管道的同步和阻塞问题。 B. 咱们能够用一些方法来提早查看紧急消息。
缺点:
A. 与管道同样,每一个数据块有一个最大长度的限制。 B. 系统中全部队列所包含的所有数据块的总长度也有一个上限。
限制:
因为消息缓冲机制中所使用的缓冲区为共用缓冲区,所以使用消息缓冲机制传送数据时,两通讯进程必须知足 以下条件: (1)在发送进程把写入消息的缓冲区挂入消息队列时,应禁止其余进程对消息队列的访问,不然,将引发消 息队列的混乱。同理,当接收进程正从消息队列中取消息时,也应禁止其余进程对该队列的访问。 (2)当缓冲区中无消息存在时,接收进程不能接收任何消息;而发送进程是否能够发送消息,则只由发送进 程是否可以申请缓冲区决定。
//msga #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<memory.h> #include<sys/msg.h> typedef struct my_message { long int type; char msg[128]; }MyMsg; int main() { int msgid = msgget((key_t)1234, IPC_CREAT | 0600); if(msgid == -1) { perror("a msgget error\n"); } char buff[128]; int type; MyMsg m; while(1) { puts("input type(int)"); scanf("%d", &type); getchar(); // fflush(stdin); m.type = type; memset(buff, 0, 128); puts("input message"); fgets(buff, 127, stdin); buff[strlen(buff) - 1] = 0; strcpy(m.msg, buff); if((msgsnd(msgid, &m, sizeof(MyMsg) - sizeof(long), IPC_NOWAIT)) == -1) { perror("a msgsnd error\n"); } if(strncmp(buff, "end", 3) == 0) { break; } } exit(0); }
//msgb #include<stdio.h> #include<stdlib.h> #include<string.h> #include<assert.h> #include<unistd.h> #include<memory.h> #include<sys/msg.h> typedef struct my_message { long int type; char msg[128]; }MyMsg; int main() { int msgid = msgget((key_t)1234, IPC_CREAT | 0600); if(msgid == -1) { perror("b msgget error\n"); } char buff[128]; int type; MyMsg m; int i = 0; while(1) { puts("output type(int)"); scanf("%d", &type); fflush(stdin); m.type = type; memset(buff, 0, 128); if(msgrcv(msgid, &m, sizeof(MyMsg) - sizeof(long), type, IPC_NOWAIT) == -1) { perror("b msgrcv error\n"); continue; } strcpy(buff, m.msg); printf("buff[%d]=%s\n", i++, buff); if(strncmp(buff, "end", 3) == 0) { break; } } msgctl(msgid, IPC_RMID, NULL); exit(0); }
在#inlcude<sys/msg.h>中:
关于struct msgbuf
struct msgbuf{
long mtype; //这是一个消息中必须包含的消息类型,要求为long …………………… //具体的消息
};
详情可参见:http://blog.csdn.net/guoping1...
套接字是网络编程的基础,是进程间通信中一种经常使用的通信方式,按照Linux下一切皆文件的思想,那么套接字就能够看做是一个文件的描述符。这个部分更多的偏向于网络编程,延伸至select,poll,epoll乃至libevent、boost::asio等等网络库,这个放在后面再讨论吧。
在这里额外提一下双向管道好了,双向管道,这是一种全双工的通信方式,双方均可读可写,那么这也就是普通管道不能知足需求的缘由,因此诞生了一种以套接字为基础的轻量级别的解决方案,socketpair();
在#include<sys/socket.h>中:
int socketpair(int domain, int type, int protocol, int sv[2]);
domain参数:选择协议族 AF_UNIX,AF_LOCAL;
type参数: 选择类型:SOCK_STREAM,SOCK_DGRAM
pritocal 必须为0!
这样处理后,sv数组中会放有两个链接好的套接字文件,这样经过两个套接字就能够互通有无了。
参考资料:
http://blog.csdn.net/Zong__Zo...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/guoping1...
http://blog.csdn.net/ttyue_12...