ipc 通信介绍
linux应用开发中,进程中通信的使用是不可能避免的,本文介绍常用的进程间通信方式。有名管道,消息队列,域套接字。
一,有名管道
管道文件,可以由任意进程访问,打开管道就可以指定读写方式
通过文件IO操作,内容在内存中,读写端全部关闭就释放,使用时注意,读端打开是会阻塞,写端打开时才运行,写端存在但是不写数据,读端也会阻塞,可以使用open read write进行读写,一但读到文件末尾,需要关闭,否则一直读到“”,返回为0。
代码实现:
#include<stdio.h>
#include<stdlib.h>
#include<sys/stat.h>
#include<fcntl.h>
int main(int argc, const char *argv[])
{
int fd;
char ch;
mknod("/home/colin/test/fifo_test",S_IFIFO|0666,0);
fd = open("/home/colin/test/fifo_test",O_RDONLY);
printf("open fifo is success \n");
while(1)
{
if(!read(fd,&ch,1))
{
sleep(1);
close(fd); //这里注意,如果不关闭,一旦另外写管道了 这边就会一直从管道读,即使读不到也不会停下来
fd = open("/home/test/fifo_test",O_RDONLY);
printf("reopen fifo \n");
continue;
}
else
{
printf("read fifo is %x\n",ch);
}
}
return 0;
}
实测
运行程序,产生管道文件,并且阻塞到open之后 读之前
另外一个进程写管道时,读进程才会取消阻塞
这里注意代码中的描述:
一旦管道 阻塞到读到数据后,如果读到文件末尾不关闭 管道,程序就回一直从管道中读数据,如果没有数据就返回0。
二. 消息队列
1.消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.
2.消息队列允许一个或多个进程向它写入与读取消息.
3.管道和命名管道都是通信数据都是先进先出的原则。
4.消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。
目前主要有两种类型的消息队列:POSIX消息队列以及系统V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
代码实现,这里不写测试程序了
//初始化两个消息队列
int msg_queue_init(void)
{
key_t key_s;
int ret = 0;
if ((key_s = ftok("/etc", 'm')) == -1) // send queue
{
log_dbg("fail to ftok");
ret = -1;
}
if((msgid1 = msgget(key_s, IPC_EXCL)) < 0)
{
if((msgid1 = msgget(key_s,0666 | IPC_CREAT)) < 0)
{
log_dbg("msget1 error");
ret = -1;
}
}
else
{
log_dbg("msgid exist, msgid = %d\n", msgid1);
}
return ret;
}
//定义消息类型
struct msgbuf{
int mtype;
char buf[100];
};
//调用接口发送与接收
发送接口
msgsnd(msgid1, msgp, msgsz, msgflg);
接收接口
msgrcv(msgid1, msgp, msgsz, msgtype, msgflg);
三.域套接字
UNIX域套接字(UDS):UNIX Domain Socket
UNIX域socket实现本地进程间通信,需要一个进程作为服务器端,一个进程作为客户端,使用方法有点像socket网络,但又不经过网络底层的那些东西。与网络编程最不一样的地方是服务器端bind的时候用的是sockaddr_un结构,客户端connect的时候用的也是sockaddr_un结构,而不是sockaddr_in或sockaddr。而对于sockaddr_un结构,重点是给它提供一个bind()函数生成的socket类型文件的路径,即sockaddr_un.sun_path的值。并且客户端与服务器端的这个sockaddr_un结构的sun_path是一致的,通常这个路径是众所众知的,就像百度的域名那样。
经过bind,listen,accept,和connect后,两进程就通过读写socket文件描述符来通信,具体是服务器端读写accept返回的socket文件描述符,客户端读写经过connect处理后的文件描述符。
代码实现
service代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/un.h>
#define dbg_printf(...) printf("[%s.%d]",__func__,__LINE__);printf(__VA_ARGS__)
#define UNIX_SOCKET_FILE "/home/test/temp.file"
static int sockfd = 0;
static struct sockaddr_un servaddr,cliaddr;
int main(int argc, const char *argv[])
{
int ret = 0;
int connfd = 0;
sockfd = socket(AF_UNIX,SOCK_STREAM,0);
if(sockfd < 0 )
{
dbg_printf("socket error \n");
return -1;
}
if(access(UNIX_SOCKET_FILE,F_OK) == 0)
{
dbg_printf("remove unix socket file \n");
remove(UNIX_SOCKET_FILE);
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path,UNIX_SOCKET_FILE);
if(0 != bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)))
{
dbg_printf("bind fail \n");
close(sockfd);
return -1;
}
if(0 != listen(sockfd,10))
{
dbg_printf("linsten fail \n");
close(sockfd);
return -1;
}
int clilen = sizeof(cliaddr.sun_family)+strlen(cliaddr.sun_path);
if((connfd = accept(sockfd,(struct sockaddr *)&cliaddr,(socklen_t *)&clilen)) == -1)
{
dbg_printf("accept fail \n");
return -1;
}
dbg_printf("accept success connfd = %d \n",connfd);
char recvbuf[64];
int n = 0;
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
n = recv(connfd,recvbuf,sizeof(recvbuf),0);
if(n < 0)
{
dbg_printf("recv msg fail n= %d \n",n);
return -1;
}
dbg_printf("recv is %s \n",recvbuf);
send(connfd,recvbuf,n,0);
}
close(sockfd);
close(connfd);
return 0;
}
client代码
#include<stdio.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include<string.h>
#include<sys/un.h>
#define dbg_printf(...) printf("[%s.%d]",__func__,__LINE__);printf(__VA_ARGS__)
#define UNIX_SOCKET_FILE "/home/test/temp.file"
static int sockfd = 0;
static struct sockaddr_un servaddr,cliaddr;
int main(int argc, const char *argv[])
{
int ret = 0;
int connfd = 0;
sockfd = socket(AF_UNIX,SOCK_STREAM,0);
if(sockfd < 0 )
{
dbg_printf("socket error \n");
return -1;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family = AF_UNIX;
strcpy(servaddr.sun_path,UNIX_SOCKET_FILE);
int serlen = sizeof(servaddr.sun_family)+strlen(servaddr.sun_path);
if(connect(sockfd,(struct sockaddr *)&servaddr,serlen) == -1)
{
dbg_printf("connect fail \n");
close(sockfd);
return -1;
}
char recvbuf[64];
char sendbuf[64];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
memset(sendbuf,0,sizeof(sendbuf));
strcpy(sendbuf,"hello world");
send(sockfd,sendbuf,strlen(sendbuf),0);
recv(sockfd,recvbuf,sizeof(recvbuf),0);
dbg_printf("recv is %s \n",recvbuf);
}
close(sockfd);
return 0;
}
程序测试
关于域套接字,还可以加上select机制,或者epoll机制,后面我抽时间实现后 再做记录。