linux上的进程通信学习笔记
2018-09-09 23:38
746 查看
参考资料
<<精通Linux C编程>>
http://man7.org/linux/man-pages/man2/open.2.html
https://www.cnblogs.com/52php/p/5840229.html
在Android中的Handler的Native层研究文章中研究一下一把Linux中的匿名管道的通信机制,今天这里Linux中的进程间通信补齐。
在Linux中,实现进程通信的方法包括管道(匿名管道和具名管道),消息队列,信号量,共享内存,套接口等。消息队列,信号量,共享内存统称为系统的(POSIX和System V)IPC,用于本地间的进程通信,套接口(socket)则运用于远程进程通信。
各个通信机制定义如下:
匿名管道(Pipe)和具名管道(named pipe):匿名管道用于具有亲缘关系进程间的通信,具名管道克服了管道没有名字的限制,因此除了具有匿名管道的功能外,还允许在无亲缘关系的进程中进行通信。
消息队列(Message):消息队列为消息的链接表,包括POSIX消息队列和System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息。
共享内存:是的多个进程可以访问同一块内存空间,是最快的可以IPC形式。是针对其他的通信机制运行效率较低而设计出来的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步与互斥。
信号量(semaphore):主要作为进程间以及同一进程不同线程的同步手段。
套接口(socket):最一般的进程通信机制,可用于远程通信。
匿名管道与FIFO的区别主要在如下俩个点:
FIFO可以用于任何两个进程的通信,而匿名管道只能用于有亲缘关系的进程中
FIFO作为一种特殊的文件存放于系统中,不像匿名管道存放于内存当中(使用后消失)。当进程对FIFO使用完毕后,FIFO依然存活于文件系统当中,除非主动删除,否则不会消失。
由于上面的第二个特性,可以解决系统在应用中产生的大量的中间临时文件的问题,达到重用的目的。
创建一个命令管道可以使用如下两个命令创建:
关于mode_t,看过其实就是文件访问权限表示,在鸟哥linux私房菜的权限一章中有介绍,这里就不讲了,详细的自己查看链接吧,下面简单实现一个Demo:
编译需要加入-lrt 如g++ main.cpp -lrt -o main
对于管道的操作如下:
open():打开命名管道
read():读取命名管道数据
write(): 向命名管道写入数据
close():关闭命名管道
unlink():删除命名管道
在操作命令管道open()函数的传参主要实现有如下几种:
引用自这篇文章
在open函数的调用的第二个参数中,你看到一个陌生的选项 O_NONBLOCK,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。
open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
简单实现阻塞的Demo如下,这里直接使用父子进程进行测试:
得到的结果:
消息队列跟匿名管道以及FIFO的区别(来自该篇文章):
一个进程向消息队列写入消息之前,并不需要某个进程在该队列上等待该消息的到达,而管道和FIFO是相反的,进程向其中写消息时,管道和FIFO必需已经打开来读,那么内核会产生SIGPIPE信号。
IPC的持续性不同。管道和FIFO是随进程的持续性,当管道和FIFO最后一次关闭发生时,仍在管道和FIFO中的数据会被丢弃。消息队列是随内核的持续性,即一个进程向消息队列写入消息后,然后终止,另外一个进程可以在以后某个时刻打开该队列读取消息。只要内核没有重新自举,消息队列没有被删除。
POSIX消息队列的相关操作(更详细的可以man各个函数查看):
测试代码如下:
测试中遇到了两个问题记录如下:
在调用mq_receive()时候遇到了Message too long的问题,主要是因为主要原因在于传递的msg_len小于mq_msgsize导致,详细可查看文章1以及文章2。
在mq_open()时候爆出invalid argument错误,原因是不同ubuntu系统中对于mq_attr支持的设置不一样,可通过文章3查看系统支持的参数大小
测试Demo:
输出结果如下:
额外提个tip,这里队列可以理解成优先级队列的概念,在我们mq_send()最后一个参数为优先级,在服务端receive的时候会按照优先级进行读取,而不是客户端最先发送的。
在POSIX信号量中,分为有名信号量和无名信号量:
有名信号量:使用Posix IPC名字标识,既可用于线程间的同步,又可以用于进程间的同步。
无名信号量:无名信号量只存在于内存中,并且规定能够访问该内存的进程才能够使用该内存中的信号量。这就意味着,无名信号量只能被这样两种线程使用:(1)来自同一进程的各个线程(2)来自不同进程的各个线程,但是这些进程映射了相同的内存范围到自己的地址空间。
总而言之,无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥。
有名信号量和无名信号量的使用区别如下:
![](https://oscimg.oschina.net/oscnet/6b0dd18140d50c0e860605dcf38f24e1481.jpg)
图片引用
注: Link with -pthread
打开一个有名信号量:
操作有名信号量:
关闭有名信号量:
删除有名信号量:
这里选择使用线程进行测试,测试Demo如下:
结果如下:
上述Demo的信号量的数量设置为1,只有一个线程能获取到信号量进入代码执行,其他线程需要等待当前线程释放信号量后,然后进行抢夺信号量,或得到的线程进行代码执行,依次进行下去,如果把sem_open()的value参数改成三则说明最多三个线程可以同时进行,这里就不写Demo了。
需要注意的一点是有名信号量的值是随内核持续的。也就是说,一个进程创建了一个信号量,这个进程结束后,这个信号量还存在,并且信号量的值也不会改变。
测试的Demo可以把对应有名信号量的方法换成上述两个方法即可,就不详细介绍了。
使用说明:
由于共享内存本身不涉及进程通信,就不给出Demo了,想要了解使用方式的可以百度一下。
的通信,在网络编程中经常能遇见Socket编程。上面介绍的进程通信局限于本机的进程之间通信,而Socket则主要实现远端与本机的进程通信。
这里简单介绍一下Socket与Http的区别把。在大学里都学过网络由下往上分为,物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,一般来说我们把会话层,表示层和应用层统称为应用层。IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议在应用层。如图下所示([图片来自网络):
![](https://oscimg.oschina.net/oscnet/3b8b5cd3729e0fb8731d35119065fc684fc.jpg)
而Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,封装了TCP/IP的调用实现,当然也支持UDP协议。http的本质实现上也需要依赖Socket进行通信。
关于linux下的Socket通信用法,网上写的文章我觉得比我整理学习的好的多,再次放上几个链接把(吐个槽,网上基本一篇文章复制来复制去的),就不整理了,要整理的话不是一篇文章可以写的。其实最好就是看文档了,用man命令是非常值得拥有的。
https://segmentfault.com/a/1190000010838127
https://www.cnblogs.com/jiangzhaowei/p/8261174.html
针对TCP放上Demo,linux下使用c++开发服务端,使用JAVA充当客户端《服务端如下:
客户端代码:
最终输出结果:
<<精通Linux C编程>>
http://man7.org/linux/man-pages/man2/open.2.html
https://www.cnblogs.com/52php/p/5840229.html
在Android中的Handler的Native层研究文章中研究一下一把Linux中的匿名管道的通信机制,今天这里Linux中的进程间通信补齐。
在Linux中,实现进程通信的方法包括管道(匿名管道和具名管道),消息队列,信号量,共享内存,套接口等。消息队列,信号量,共享内存统称为系统的(POSIX和System V)IPC,用于本地间的进程通信,套接口(socket)则运用于远程进程通信。
各个通信机制定义如下:
匿名管道(Pipe)和具名管道(named pipe):匿名管道用于具有亲缘关系进程间的通信,具名管道克服了管道没有名字的限制,因此除了具有匿名管道的功能外,还允许在无亲缘关系的进程中进行通信。
消息队列(Message):消息队列为消息的链接表,包括POSIX消息队列和System V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读取队列中的消息。
共享内存:是的多个进程可以访问同一块内存空间,是最快的可以IPC形式。是针对其他的通信机制运行效率较低而设计出来的。往往与其他通信机制,如信号量结合使用,来达到进程间的同步与互斥。
信号量(semaphore):主要作为进程间以及同一进程不同线程的同步手段。
套接口(socket):最一般的进程通信机制,可用于远程通信。
匿名管道与具名管道
关于匿名管道的理解以及Demo,在Android中的Handler的Native层研究文章中已经讲述过了,这里就不做介绍了。直接看具名管道(FIFO)。具名管道的提出是为了解决匿名管道只能用于具有亲缘关系(父子,兄弟)的进程间通信,具名管道提供了一个路径名与之关联,以FIFO的文件形式存在于文件系统中,即使没有亲缘关系的进程也可通过该路径名达到互相通信的目的。匿名管道与FIFO的区别主要在如下俩个点:
FIFO可以用于任何两个进程的通信,而匿名管道只能用于有亲缘关系的进程中
FIFO作为一种特殊的文件存放于系统中,不像匿名管道存放于内存当中(使用后消失)。当进程对FIFO使用完毕后,FIFO依然存活于文件系统当中,除非主动删除,否则不会消失。
由于上面的第二个特性,可以解决系统在应用中产生的大量的中间临时文件的问题,达到重用的目的。
创建一个命令管道可以使用如下两个命令创建:
#include <sys/types.h> #include <sys/stat.h> int mkfifo(const char *filename, mode_t mode); int mknod(const char *filename, mode_t mode | S_IFIFO, (dev_t)0); //不建议使用了
关于mode_t,看过其实就是文件访问权限表示,在鸟哥linux私房菜的权限一章中有介绍,这里就不讲了,详细的自己查看链接吧,下面简单实现一个Demo:
#include<iostream> #include<signal.h> #include<sys/types.h> #include<sys/stat.h> #include<errno.h> void testNamePipe(){ mode_t mode=0666; const char* name="namePipeTest"; int ret=mkfifo(name,mode); if(errno==EEXIST){ printf("对象存在"); }else if(ret<0){ printf("创建命名管道失败,自动退出"); exit(1); }else{ printf("创建命名管道成功"); } }
编译需要加入-lrt 如g++ main.cpp -lrt -o main
对于管道的操作如下:
open():打开命名管道
read():读取命名管道数据
write(): 向命名管道写入数据
close():关闭命名管道
unlink():删除命名管道
在操作命令管道open()函数的传参主要实现有如下几种:
open(const char *path, O_RDONLY); // 1 open(const char *path, O_RDONLY | O_NONBLOCK); // 2 open(const char *path, O_WRONLY); // 3 open(const char *path, O_WRONLY | O_NONBLOCK); // 4
引用自这篇文章
在open函数的调用的第二个参数中,你看到一个陌生的选项 O_NONBLOCK,选项 O_NONBLOCK 表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的。
open调用的阻塞是什么一回事呢?很简单,对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。
对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。
简单实现阻塞的Demo如下,这里直接使用父子进程进行测试:
#include <errno.h> #include <fcntl.h> //O_WRONLY等头文件 #include <iostream> #include <signal.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> using namespace std; const char* name = "namePipeTest"; void rwNamePipe() { int pid = -1; if ((pid = fork()) < 0) { printf("%s", "fork error"); } else if (pid == 0) { //子进程 printf("%s\n", "子进程创建成功"); int writeId = open(name, O_WRONLY); //以只写的形式 if (writeId < 0) { printf("写端打开失败"); exit(1); } else { printf("写端打开成功"); char buf[] = "hello named pipe"; for (int i = 0; i < 10; i++) { cout << "写入数据中" << endl; write(writeId, buf, sizeof(buf)); sleep(2); //睡眠两秒 } close(writeId); exit(0); } } else { //父进程,即当前进程 printf("%s\n", "父进程开始作业"); int readId = open(name, O_RDONLY); if (readId < 0) { printf("读端打开失败"); exit(1); } else { printf("开始进入读取数据阶段"); char buffer[1024]; while (read(readId, buffer, sizeof(buffer)) > 0) { printf("父进程读到数据=%s\n", buffer); } close(readId); exit(0); } } } void testNamePipe() { mode_t mode = 0666;//owner,group,others都有读写权限 int ret = mkfifo(name, mode); if (errno == EEXIST) { printf("对象存在"); rwNamePipe(); } else if (ret < 0) { printf("创建命名管道失败,自动退出"); exit(1); } else { printf("创建命名管道成功"); rwNamePipe(); } } int main() { testNamePipe(); return 0; }
得到的结果:
$ ./main 对象存在父进程开始作业 对象存在子进程创建成功 写端打开成功写入数据中 开始进入读取数据阶段父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe 写入数据中 父进程读到数据=hello named pipe
POSIX消息队列
消息队列为以一种链表式结构组织的一组数据,存放于内核之中,由个进程通过消息队列标识符引用传递数据的一种方式,由内核维护。消息队列为最具有数据操作性的数据床送方式,在消息队列中可以随意的根据特定的数据类型来检索消息。消息队列跟匿名管道以及FIFO的区别(来自该篇文章):
一个进程向消息队列写入消息之前,并不需要某个进程在该队列上等待该消息的到达,而管道和FIFO是相反的,进程向其中写消息时,管道和FIFO必需已经打开来读,那么内核会产生SIGPIPE信号。
IPC的持续性不同。管道和FIFO是随进程的持续性,当管道和FIFO最后一次关闭发生时,仍在管道和FIFO中的数据会被丢弃。消息队列是随内核的持续性,即一个进程向消息队列写入消息后,然后终止,另外一个进程可以在以后某个时刻打开该队列读取消息。只要内核没有重新自举,消息队列没有被删除。
POSIX消息队列的相关操作(更详细的可以man各个函数查看):
//打开一个消息队列 mqd_t mq_open(const char *name, int oflag); mqd_t mq_open(const char *name, int oflag, mode_t mode,struct mq_attr *attr); //关闭消息队列 int mq_close(mqd_t mqdes); //从系统中删除消息队列 int mq_unlink(const char *name); //获取以及设置消息队列属性 int mq_getattr(mqd_t mqdes, struct mq_attr *attr); int mq_setattr(mqd_t mqdes, struct mq_attr *newattr, struct mq_attr *oldattr); //man查阅可知 struct mq_attr { long mq_flags; /* Flags: 0 or O_NONBLOCK */ long mq_maxmsg; /* Max. # of messages on queue */ long mq_msgsize; /* Max. message size (bytes) */ long mq_curmsgs; /* # of messages currently in queue */ }; //发送以及接收消息 int mq_send(mqd_t mqdes, const char *msg_ptr, size_t msg_len, unsigned msg_prio); ssize_t mq_receive(mqd_t mqdes, char *msg_ptr, size_t msg_len, unsigned *msg_prio);
测试代码如下:
测试中遇到了两个问题记录如下:
在调用mq_receive()时候遇到了Message too long的问题,主要是因为主要原因在于传递的msg_len小于mq_msgsize导致,详细可查看文章1以及文章2。
在mq_open()时候爆出invalid argument错误,原因是不同ubuntu系统中对于mq_attr支持的设置不一样,可通过文章3查看系统支持的参数大小
测试Demo:
#ifndef MES_QUEUE_H_ #define MES_QUEUE_H_ #include <fcntl.h> #include <iostream> #include <mqueue.h> #include <string.h> #include <sys/stat.h> #include <unistd.h> using namespace std; void receiveQueue(string name) { cout << "客户端读取消息-----------------------" << endl; mode_t mode = 0666; struct mq_attr att; att.mq_msgsize = 30; att.mq_maxmsg = 10; att.mq_curmsgs = 0; att.mq_flags = 0; mqd_t openId = mq_open(name.c_str(),O_RDWR | O_CREAT|O_EXCL, mode,&att ); if (openId < 0 && errno != EEXIST) { cout << "error open mq:" << strerror(errno) << endl; return; } if(openId<0&&errno==EEXIST){ cout<<"文件存在打开"<<endl; openId=mq_open(name.c_str(),O_RDONLY); if(openId<0){ cout<<"打开失败:"<<strerror(errno)<<endl; return; } } struct mq_attr attr; if (mq_getattr(openId, &attr) < 0) { cout << "error get attr" << endl; return; } else { printf("flags: %ld, maxmsg: %ld, msgsize: %ld, curmsgs: %ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs); } char buffer[50]; cout << "开始读取消息" << endl; while (true) { if(mq_receive(openId, buffer,50, NULL)>=0){ printf("读取的消息是:%s\n", buffer); }else{ //cout<<strerror(errno)<<endl; } } mq_close(openId); } void sendQueue(string name) { cout << "服务端发送消息----------------------" << endl; mqd_t openId = mq_open(name.c_str(),O_RDWR); if (openId < 0) { cout << "error open mq" << errno << endl; return; } struct mq_attr attr; if (mq_getattr(openId, &attr) < 0) { cout << "error get attr" << endl; fprintf(stderr, "发送失败: %s\n", strerror(errno)); return; } else { printf("flags: %ld, maxmsg: %ld, msgsize: %ld, curmsgs: %ld\n", attr.mq_flags, attr.mq_maxmsg, attr.mq_msgsize, attr.mq_curmsgs); } //int size=static_cast<int>(attr.mq_msgsize); cout << "开始发送消息" << endl; for (int i = 0; i < 10; i++) { string result = "msq no: " + to_string(i); const char* msg_ptr = result.c_str(); cout<<"发送消息中"<<endl; int rec = mq_send(openId, msg_ptr, strlen(msg_ptr)+1, 1); if (rec < 0) { cout << "发送信息失败" << rec << endl; fprintf(stderr, "发送失败: %s\n", strerror(errno)); break; } else { cout << "写入消息为" << result << endl; } sleep(3); } cout<<"发送完毕"<<endl; mq_close(openId); } void runMsgQueue() { string name = "/msq_test"; int pid = -1; if ((pid = fork()) < 0) { printf("%s", "fork error"); exit(1); } else if (pid == 0) { //子进程 sendQueue(name); } else { //本进程 receiveQueue(name); } } #endif
输出结果如下:
$ ./main 客户端读取消息----------------------- flags: 0, maxmsg: 10, msgsize: 30, curmsgs: 0 开始读取消息 服务端发送消息---------------------- flags: 0, maxmsg: 10, msgsize: 30, curmsgs: 0 开始发送消息 发送消息中 写入消息为msq no: 0 读取的消息是:msq no: 0 发送消息中 写入消息为msq no: 1 读取的消息是:msq no: 1 发送消息中 写入消息为msq no: 2 读取的消息是:msq no: 2 发送消息中 写入消息为msq no: 3 读取的消息是:msq no: 3 发送消息中 写入消息为msq no: 4 读取的消息是:msq no: 4 发送消息中 写入消息为msq no: 5 读取的消息是:msq no: 5 发送消息中 写入消息为msq no: 6 读取的消息是:msq no: 6 发送消息中 写入消息为msq no: 7 读取的消息是:msq no: 7 发送消息中 写入消息为msq no: 8 读取的消息是:msq no: 8 发送消息中 写入消息为msq no: 9 读取的消息是:msq no: 9 发送完毕
额外提个tip,这里队列可以理解成优先级队列的概念,在我们mq_send()最后一个参数为优先级,在服务端receive的时候会按照优先级进行读取,而不是客户端最先发送的。
POSIX信号量
信号量(semaphore)是一种提供不同进程间或者一个给定进程不同线程之间的同步,这里依然分为POSIX信号量和SystemV信号量,文章中只对POSIX信号量进行学习归纳。在POSIX信号量中,分为有名信号量和无名信号量:
有名信号量:使用Posix IPC名字标识,既可用于线程间的同步,又可以用于进程间的同步。
无名信号量:无名信号量只存在于内存中,并且规定能够访问该内存的进程才能够使用该内存中的信号量。这就意味着,无名信号量只能被这样两种线程使用:(1)来自同一进程的各个线程(2)来自不同进程的各个线程,但是这些进程映射了相同的内存范围到自己的地址空间。
总而言之,无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥。
有名信号量和无名信号量的使用区别如下:
![](https://oscimg.oschina.net/oscnet/6b0dd18140d50c0e860605dcf38f24e1481.jpg)
图片引用
有名信号量
有名信号量的头文件在semaphore.h中,具体涉及的函数如下所示:
注: Link with -pthread
sem_open() //初始化并打开有名信号量 sem_wait()/sem_trywait()/sem_timedwait()/sem_post()/sem_getvalue() //操作信号量 sem_close() //退出有名信号量 sem_unlink() //销毁有名信号量
打开一个有名信号量:
//传入参数参考消息队列的mq_open()中相对应参数,value参数用来指定信号量的初始值,取值范围[0,SEM_VALUE_MAX] sem_t *sem_open(const char *name, int oflag); sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);
操作有名信号量:
//成功返回降低后的信号量的值,失败返回-1以及errno //试图占用信号量,如果信号量值>0,就-1,如果已经=0,就block,直到>0 int sem_wait(sem_t *sem); //试图占用信号量,如果信号量已经=0,立即报错 int sem_trywait(sem_t *sem); //试图占用信号量 //如果信号量=0,就block abs_timeout那么久,超时则报错 int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); //归还信号量,成功返回0,失败返回-1,以及errno int sem_post(sem_t *sem); //获得信号量sem的当前的值,放到sval中。如果有线程正在block这个信号量,sval可能返回两个值,0或“-正在block的线程的数目”,Linux返回0 //成功返回0,失败返回-1以及errno int sem_getvalue(sem_t *sem, int *sval);
关闭有名信号量:
//关闭有名信号量,成功返回0,失败返回-1以及errno int sem_close(sem_t *sem);
删除有名信号量:
//试图销毁信号量,一旦所有占用该信号量的进程都关闭了该信号量,那么就会销毁这个信号量,成功返回0,失败返回-1以及errno int sem_unlink(const char *name);
这里选择使用线程进行测试,测试Demo如下:
#ifndef SEMAPHORE_TEST_H_ #define SEMAPHORE_TEST_H_ #include<iostream> #include<fcntl.h> #include<unistd.h> #include <semaphore.h> #include <pthread.h> #include <sys/stat.h> #include<string.h> using namespace std; static sem_t* sem; void *runChildThread(void* arg) { int * id=static_cast<int*>(arg); int pid=*id; cout<<"pid="<<pid<<"的线程等待信号量"<<endl; sem_wait(sem); //申请信号量 cout<<"pid="<<pid<<"获得信号量"<<endl; sleep(2); sem_post(sem); /*释放信号量*/ cout<<"pid="<<pid<<"释放信号量"<<endl; } void runSemaphoreTest() { string name="/sem_test"; mode_t mode=0666; uint value=1; sem= sem_open(name.c_str(),O_CREAT,mode,value); if(sem==SEM_FAILED){ cout<<"create name sem error:"<<strerror(errno)<<endl; return; } cout<<"成功创建信号量"<<endl; pthread_t tid=12; for(int i=0;i<10;i++){ int result= pthread_create(&tid,NULL,runChildThread,&i); if(result!=0){ cout<<"创建线程失败,程序退出"<<endl; exit(1); } } sleep(30);//测试线程执行 sem_close(sem); } #endif
结果如下:
$ ./main 成功创建信号量 pid=1的线程等待信号量 pid=1获得信号量 pid=2的线程等待信号量 pid=3的线程等待信号量 pid=4的线程等待信号量 pid=5的线程等待信号量 pid=6的线程等待信号量 pid=7的线程等待信号量 pid=8的线程等待信号量 pid=9的线程等待信号量 pid=10的线程等待信号量 pid=1释放信号量 pid=2获得信号量 pid=2释放信号量 pid=3获得信号量 pid=3释放信号量 pid=4获得信号量 pid=4释放信号量 pid=5获得信号量 pid=5释放信号量 pid=6获得信号量 pid=6释放信号量 pid=7获得信号量 pid=7释放信号量 pid=8获得信号量 pid=8释放信号量 pid=9获得信号量 pid=9释放信号量 pid=10获得信号量 pid=10释放信号量
上述Demo的信号量的数量设置为1,只有一个线程能获取到信号量进入代码执行,其他线程需要等待当前线程释放信号量后,然后进行抢夺信号量,或得到的线程进行代码执行,依次进行下去,如果把sem_open()的value参数改成三则说明最多三个线程可以同时进行,这里就不写Demo了。
需要注意的一点是有名信号量的值是随内核持续的。也就是说,一个进程创建了一个信号量,这个进程结束后,这个信号量还存在,并且信号量的值也不会改变。
无名信号量
无名信号量由于没有名字,所以使用方法与有名信号量略有不同,却别主要在创建以及销毁的操作上,区别的函数如下:sem_init() //创建/获得无名信号量 sem_destroy() //销毁无名信号量
测试的Demo可以把对应有名信号量的方法换成上述两个方法即可,就不详细介绍了。
共享内存
内核管理一片物理内存,允许不同的进程同时映射,多个进程可以映射同一块内存,被多个进程同时映射的物理内存,即共享内存。由于本身实现并不能保证同步,所以需要我们自己进行同步,最常见的是使用信号量的方式进行同步。使用说明:
#include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, size_t size, int shmflg); void *shmat(int shm_id, const void *shm_addr, int shmflg); int shmdt(const void *shm_addr); int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
由于共享内存本身不涉及进程通信,就不给出Demo了,想要了解使用方式的可以百度一下。
套接字
套接字,就是我们的Socket,通过套接字,我们可以实现本地或者远程两个进程之间的通信,在网络编程中经常能遇见Socket编程。上面介绍的进程通信局限于本机的进程之间通信,而Socket则主要实现远端与本机的进程通信。
这里简单介绍一下Socket与Http的区别把。在大学里都学过网络由下往上分为,物理层、数据链路层、网络层、传输层、会话层、表示层和应用层,一般来说我们把会话层,表示层和应用层统称为应用层。IP协议对应于网络层,TCP协议对应于传输层,而HTTP协议在应用层。如图下所示([图片来自网络):
![](https://oscimg.oschina.net/oscnet/3b8b5cd3729e0fb8731d35119065fc684fc.jpg)
而Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,封装了TCP/IP的调用实现,当然也支持UDP协议。http的本质实现上也需要依赖Socket进行通信。
关于linux下的Socket通信用法,网上写的文章我觉得比我整理学习的好的多,再次放上几个链接把(吐个槽,网上基本一篇文章复制来复制去的),就不整理了,要整理的话不是一篇文章可以写的。其实最好就是看文档了,用man命令是非常值得拥有的。
https://segmentfault.com/a/1190000010838127
https://www.cnblogs.com/jiangzhaowei/p/8261174.html
针对TCP放上Demo,linux下使用c++开发服务端,使用JAVA充当客户端《服务端如下:
#ifndef SOCKET_TEST_H_ #define SOCKET_TEST_H_ #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include<iostream> #include<string.h> #include<errno.h> #include<sys/types.h> #include<netinet/in.h> #include<unistd.h> #include <arpa/inet.h> #include <net/if.h> #include <sys/ioctl.h> #define PORT 9876 #define MAXLINE 4096 using namespace std; void getSockName(int& sock){ struct sockaddr_in addr; socklen_t addr_len = sizeof(struct sockaddr_in); /* 获取本端的socket地址 */ int nRet = getsockname(sock,(struct sockaddr*)&addr,&addr_len); if(nRet == -1) { perror("getsockname error: "); }else{ printf("this socket addr %s %d successful\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port)); } } void startServer(){ //ipv4 int socketFd=socket(AF_INET,SOCK_STREAM,0); if(socketFd==-1){ cout<<"open socket error: "<<strerror(errno)<<endl; exit(1); } cout<<"创建socket成功"<<endl; struct sockaddr_in addr; addr.sin_family = AF_INET; //如果使用INADDR_ANY方式,需要加以判断 //是否符合网络字节序,即大端的传输方式,如果机器为小端, //需要通过htonl转换成网络字节序相配的,如果使用inet_addr()方法 //,无需考虑大小端的问题 addr.sin_addr.s_addr =(inet_addr("192.168.199.244")); addr.sin_port = htons(PORT); //绑定 int bindStatus=bind(socketFd,(struct sockaddr*)&addr,sizeof(addr)); getSockName(socketFd); if(bindStatus==-1){ cout<<"bind error "<<strerror(errno)<<endl; exit(1); } cout<<"绑定接口ok"<<endl; if(listen(socketFd,10)==-1){ cout<<"开启监听失败"<<endl; exit(1); } char buffer[50]; while(1){ memset(buffer,0,50); cout<<"开始接收"<<endl; int isAccept=accept(socketFd, (struct sockaddr*)NULL, NULL); if( isAccept== -1){ cout<<"接收失败"<<strerror(errno)<<endl; exit(1); } int result=recv(isAccept,buffer,MAXLINE,0); if(result==-1){ cout<<"接收消息失败"<<strerror(errno)<<endl; exit(1); } string bufferStr=buffer; close(isAccept); if(bufferStr=="over"){ cout<<"收到over信号,关闭服务端"<<endl; break; }else{ printf("接收到的消息为:%s\n",buffer); } } close(socketFd); } #endif
客户端代码:
private static void startClient(){ new Thread(() -> { try { Socket socket = new Socket("192.168.199.244",9876); //2.拿到客户端的socket对象的输出流发送给服务器数据 OutputStream os = socket.getOutputStream(); //写入要发送给服务器的数据 os.write(("over" ).getBytes(StandardCharsets.UTF_8)); os.flush(); os.close(); socket.close(); } catch (IOException e) { e.printStackTrace(); System.out.print("socket connect erro"); } }).start(); }
最终输出结果:
创建socket成功 this socket addr 192.168.199.244 9876 successful 绑定接口ok 开始接收 接收到的消息为:01234 开始接收 收到over信号,关闭服务端
相关文章推荐
- 【linux高级环境编程学习笔记四】消息队列进程通信
- linux学习笔记:关于linux守护进程与终端的通信
- linux下进程间的通信——有名管道fifo学习笔记
- 学习笔记——操作系统_Linux的进程通信
- Linux学习笔记——进程间的通信-文件和文件锁
- Linux进程通信---学习笔记(二)
- 学习笔记——操作系统_Linux进程通信之消息队列
- Linux进程通信学习笔记
- 嵌入式学习笔记_Linux(四)——Linux进程通信
- Linux学习笔记——进程间的通信-管道
- Linux进程间的通信——守护进程学习笔记
- Linux进程通信----学习笔记(一)
- 在C中的学习,linux 进程间的通信
- Linux进程线程学习笔记:进程控制
- Linux 系统编程笔记 守护进程,进程通信
- linux学习笔记-读《Linux编程技术详解》-进程与进程环境
- [Linux学习笔记]进程概念及控制
- Linux进程线程学习笔记:进程创建
- Linux进程线程学习笔记
- Linux进程线程学习笔记:运行新程序