概述
进程间的通信.png每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
管道通信
管道是一种最基本的通信机制。
在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,进程1往管道中写数据,进程2读管道中的数据,这样就实现了进程间的通信。对用户程序来说,管道就像一个文件。
创建管道的函数:
#include<unistd.h>
int pipe(int filedes[2]);
调用pipe函数时在内核中开辟一块缓冲区(称为管道)用于通信,它有一个读端一个写端,然后通过filedes参数传出给用户程序两个文件描述符,filedes[0]指向管道的读端,filedes[1]指向管道的写端(很好记,就像0是标准输入1是标准输出一样)。所以管道在用户程序看起来就像一个打开的文件,通过read(filedes[0]);或者write(filedes[1]);向这个文件读写数据其实是在读写内核缓冲区。pipe函数调用成功返回0,调用失败返回-1。
管道通信的过程:
- 父进程创建管道;
- 父进程fork出子进程;
- 父进程关闭fileds[0],子进程关闭fileds[1];
管道是用队列实现的,由于是队列,故在两个进程间通信只能由一端到另一端,如果要双向通信则需创建两个管道。由于是通过文件描述符来指向管道的,那么只能在父子进程间通信。
写个简单的例子
#include<stdio.h>
#include<unistd.h>
int main(void)
{
int fd[2];
int n;
pid_t pid;
char line[80];
pipe(fd);
pid = fork();
if(pid > 0){
close(fd[0]);
write(fd[1], "hello world\n", 12);
waitpid(pid,NULL,0);
}else if(pid == 0){
close(fd[1]);
n = read(fd[0], line, 80);
printf("msg:%s", line);
}
return 0;
}
运行截图.png
其他通信机制-FIFO
用命令行来测试:
先创建一个FIFO文件
mkfifo test
然后读取FIFO的内容,此时因为FIFO为空,所以会挂起
cat < ./test
打开另一个终端往test中写入信息
echo "just a test" > ./test
这时你会发现第一个终端会输出"just a test"并且退出。
用程序测试:
(1)和命令行一样采取阻塞式,代码如下
写FIFO
//write.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#define FIFO_FILE "./myfifo"
int main()
{
int fd = 0;
int n;
int re;
char buf[15] = "hello world\n";
unlink( FIFO_FILE );
re = mkfifo( FIFO_FILE, 0777 );
fd = open(FIFO_FILE, O_WRONLY);
if(fd >= 0){
n = write(fd, buf, 15);
close(fd);
printf("%d writed\n", n);
}else{
perror("open error");
exit(1);
}
return 0;
}
读FIFO
//read.c
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#define FIFO_FILE "./myfifo"
int main()
{
char buf[80];
int n = 0;
int fd;
fd = open(FIFO_FILE, O_RDONLY);
if(fd < 0){
perror("open error");
exit(-1);
}
n = read(fd, buf, 80);
close(fd);
printf("n = %d\n", n);
printf("msg:%s\n", buf);
return 0;
}
运行
./write &
./read &
编译运行截图
编译运行阻塞式FIFO(2)非阻塞式
代码稍微修改一下
写FIFO
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<errno.h>
#define FIFO_FILE "/home/lkxiaolou/c/fifo/myfifo"
int main()
{
int fd = 0;
int n;
int re;
char buf[15] = "hello world\n";
fd = open(FIFO_FILE, O_WRONLY|O_NONBLOCK);
if(fd >= 0){
n = write(fd, buf, 15);
close(fd);
printf("%d writed\n", n);
}else{
perror("open error");
exit(1);
}
return 0;
}
读FIFO
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<errno.h>
#define FIFO_FILE "/home/lkxiaolou/c/fifo/myfifo"
int main()
{
char buf[80];
int n = 0;
int fd;
int re;
unlink( FIFO_FILE );
re = mkfifo( FIFO_FILE, 0777 );
fd = open(FIFO_FILE, O_RDONLY | O_NONBLOCK);
if(fd < 0){
perror("open error");
exit(-1);
}
while(n == 0){
n = read(fd, buf, 80);
sleep(1);
}
close(fd);
printf("n = %d\n", n);
printf("msg:%s\n", buf);
return 0;
}
编译运行结果
编译运行非阻塞式FIFO其他通信机制-UNIX Domain Socket
socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket。UNIX Domain Socket是全双工的,API接口语义丰富,相比其它IPC机制有明显的优越性,目前已成为使用最广泛的IPC机制
由于是全双工,则可以互相通信,不存在读写端,这里称为服务端和客户端。
服务端工作流程:
- socket 建立一个socket
- bind 将这个socket绑定在文件(端口)上
- listen 开始监听
- accept 如果客户端连接,则接受并建立一个新的socket来和客户端通信
- read/write 读取或者发送消息
- close 关闭连接
客户端工作流程:
- socket 建立一个socket
- connect 主动连接服务端的文件(端口)
- read/write 接收或者发送消息
- close 关闭连接
找了一个例子,稍微修改了一下:
server代码:
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>
// the max connection number of the server
#define MAX_CONNECTION_NUMBER 5
/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
int unix_socket_listen(const char *servername)
{
int fd;
struct sockaddr_un un;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){
return(-1);
}
int len, rval;
unlink(servername); /* in case it already exists */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, servername);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servername);
/* bind the name to the descriptor */
if (bind(fd, (struct sockaddr *)&un, len) < 0){
rval = -2;
} else{
if (listen(fd, MAX_CONNECTION_NUMBER) < 0){
rval = -3;
}else{
return fd;
}
}
int err;
err = errno;
close(fd);
errno = err;
return rval;
}
int unix_socket_accept(int listenfd, uid_t *uidptr)
{
int clifd, len, rval;
time_t staletime;
struct sockaddr_un un;
struct stat statbuf;
len = sizeof(un);
if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0){
return(-1);
}
/* obtain the client's uid from its calling address */
len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */
un.sun_path[len] = 0; /* null terminate */
if (stat(un.sun_path, &statbuf) < 0){
rval = -2;
}else{
if (S_ISSOCK(statbuf.st_mode) ){
if (uidptr != NULL) *uidptr = statbuf.st_uid; /* return uid of caller */
unlink(un.sun_path); /* we're done with pathname now */
return clifd;
}else{
rval = -3; /* not a socket */
}
}
int err;
err = errno;
close(clifd);
errno = err;
return(rval);
}
void unix_socket_close(int fd)
{
close(fd);
}
int main(void)
{
int listenfd,connfd;
listenfd = unix_socket_listen("foo.sock");
if(listenfd<0){
printf("Error[%d] when listening...\n",errno);
return 0;
}
printf("Finished listening...\n",errno);
uid_t uid;
connfd = unix_socket_accept(listenfd, &uid);
unix_socket_close(listenfd);
if(connfd<0){
printf("Error[%d] when accepting...\n",errno);
return 0;
}
printf("Begin to recv/send...\n");
int i,n;
int size = 0;
char rvbuf[2048];
while(size == 0)
{
//===========接收==============
size = recv(connfd, rvbuf, 12, 0);
if(size>0){
printf("Recieved Data[%d]:%s\n",size,rvbuf);
}
if(size==-1){
printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno));
}
}
unix_socket_close(connfd);
printf("Server exited.\n");
}
client代码:
#include <stdio.h>
#include <stddef.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>
/* Create a client endpoint and connect to a server. Returns fd if all OK, <0 on error. */
int unix_socket_conn(const char *servername)
{
int fd;
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0){ /* create a UNIX domain stream socket */
return(-1);
}
int len, rval;
struct sockaddr_un un;
memset(&un, 0, sizeof(un)); /* fill socket address structure with our address */
un.sun_family = AF_UNIX;
sprintf(un.sun_path, "scktmp%05d", getpid());
len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
unlink(un.sun_path); /* in case it already exists */
if (bind(fd, (struct sockaddr *)&un, len) < 0){
rval= -2;
}else{
/* fill socket address structure with server's address */
memset(&un, 0, sizeof(un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, servername);
len = offsetof(struct sockaddr_un, sun_path) + strlen(servername);
if (connect(fd, (struct sockaddr *)&un, len) < 0) {
rval= -4;
}else{
return (fd);
}
}
int err;
err = errno;
close(fd);
errno = err;
return rval;
}
void unix_socket_close(int fd)
{
close(fd);
}
int main(void)
{
srand((int)time(0));
int connfd;
connfd = unix_socket_conn("foo.sock");
if(connfd<0){
printf("Error[%d] when connecting...",errno);
return 0;
}
printf("Begin to recv/send...\n");
int i,n,size;
//=========发送======================
char rvbuf[12]="hello world";
size = send(connfd, rvbuf, 12, 0);
if(size>=0){
printf("Data[%d] Sended:%s\n",size,rvbuf);
}
if(size==-1){
printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno));
}
unix_socket_close(connfd);
printf("Client exited.\n");
}
编译运行截图:
unix domain socket运行截图好了,进程通信就写到这吧,累死我了--!
网友评论