细说linux IPC(一):基于socket的进程间通信(上)

【版权声明:尊重原创,转载请保留出处:blog.csdn.net/shallnet 或 .../gentleliu,文章仅供学习交流,请勿用于商业用途】
    在一个较大的工程当中,一般都会有多个进程构成,各个功能是一个独立的进程在运行。既然多个进程构成一个工程,那么多个进程之间肯定会存在一些信息交换或共享数据,这就涉及到进程间通信。进程间通道有很多种,比如有最熟悉网络编程中的socket、还有共享内存、消息队列、信号、管道等很多方式,每一种方式都有自己的适用情况,在本系列文章中笔者将会对多种进程间通信方式进行详解,一是对自己工作多年在这方面的经验做一个积累,二是将其分享给各位民工,或许还能从大家的拍砖当中得到意外收获。
 
socket网络编程可能使用得最多,经常用在网络上不同主机之间的通信。其实在同一主机内通信也可以使用socket来完成,socket进程通信与网络通信使用的是统一套接口,只是地址结构与某些参数不同。在使用socket创建套接字时通过指定参数domain是af_inet(ipv4因特网域)或af_inet6(ipv6因特网域)或af_unix(unix域)来实现。
在笔者这一篇文章中曾有详细介绍创建socket通信流程及基础知识。
http://blog.csdn.net/shallnet/article/details/17734919
 
首先来看一下使用af_inet域以及本地环回地址来实现本地主机进程间通信。
服务进程创建监听套接字:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int ser_afinet_listen(int port)  
  2. {  
  3.     int                 listenfd, on;  
  4.     struct sockaddr_in  seraddr;  
  5.    
  6.     listenfd = socket(af_inet, sock_stream, 0);  
  7.     if (listenfd < 0) {  
  8.         fprintf(stderr, "socket: %s\n", strerror(errno));  
  9.         return -1;  
  10.     }  
  11.    
  12.     on = 1;  
  13.     setsockopt(listenfd, sol_socket, so_reuseaddr, &on, sizeof(on));  
  14.    
  15.     seraddr.sin_family = af_inet;  
  16.     seraddr.sin_port = port;  
  17.     seraddr.sin_addr.s_addr = htonl(inaddr_any);  
  18.    
  19.     if (bind(listenfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr_in)) < 0) {  
  20.         fprintf(stderr, "bind: %s\n", strerror(errno));  
  21.         return -1;  
  22.     }  
  23.    
  24.     if (listen(listenfd, sock_ipc_max_conn) < 0) {  
  25.         fprintf(stderr, "listen: %s\n", strerror(errno));  
  26.         return -1;  
  27.     }  
  28.    
  29.     return listenfd;  
  30. }  

服务进程处理连接请求如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int ser_accept(int listenfd)  
  2. {  
  3.     int                 connfd;  
  4.     struct sockaddr_un  cltaddr;  
  5.     ssize_t             recvlen, sendlen;  
  6.     char                buf[sock_ipc_max_buf];  
  7.     socklen_t           addrlen;  
  8.    
  9.     addrlen = sizeof(cltaddr);  
  10.     for (;;) {  
  11.         connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);  
  12.         if (connfd < 0) {  
  13.             fprintf(stderr, "accept: %s\n", strerror(errno));  
  14.             return -1;  
  15.         }  
  16.    
  17.         if (recvlen = ipc_recv(connfd, buf, sizeof(buf)) < 0) {  
  18.             continue;  
  19.         }  
  20.    
  21.         printf("recv: %s\n", buf);  
  22.         snprintf(buf, sizeof(buf), "hello, ipc client!");  
  23.         if (ipc_send(connfd, buf, strlen(buf)) < 0) {  
  24.             continue;  
  25.         }  
  26.    
  27.         close(connfd);  
  28.     }  
  29. }  
  30.    
客户进程初始化如下:
指定要连接的服务器地址为本地换回地址,这样发送的连接就会回到本地服务进程。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int clt_afinet_conn_init(int port)  
  2. {  
  3.     int                 fd;  
  4.    
  5.     fd = socket(af_inet, sock_stream, 0);  
  6.     if (fd < 0) {  
  7.         fprintf(stderr, "socket: %s\n", strerror(errno));  
  8.         return -1;  
  9.     }  
  10.    
  11.     seraddr.sin_family = af_inet;  
  12.     seraddr.sin_port = port;  
  13.     if (inet_pton(af_inet, "127.0.0.1", &seraddr.sin_addr) < 0) {//环回地址  
  14.         fprintf(stderr, "inet_pton: %s\n", strerror(errno));  
  15.         return -1;  
  16.     }  
  17.    
  18.     return fd;  
  19. }  

    客户进程向服务进程发送接收请求如下:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. if (connect(fd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr_in)) < 0) {  
  2.         fprintf(stderr,  "connect: %s\n", strerror(errno));  
  3.         return -1;  
  4.     }  
  5.    
  6.     if ((sendlen = ipc_send(fd, buf, strlen(buf))) < 0) {  
  7.         return -1;  
  8.     }  
  9.    
  10.     if ((recvlen = ipc_recv(fd, buf, sizeof(buf))) < 0) {  
  11.         return -1;  
  12.     }  
  13.    

服务进程先运行,客户进程执行:
# ./client
recv: hello, ipc client!
# ./server
recv: hello ipc server!
通信过程完成。
创建类型为af_unix(或af_local)的socket,表示用于进程通信。
socket进程通信与网络通信使用的是统一套接口:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include<sys/socket.h>  
  2. int socket(int domain, int type, int protocol);  
其中,domain 参数指定协议族,对于本地套接字来说,其值被置为 af_unix 枚举值,随便说一下,af_unix和af_local是同一个值,看下面linux/socket.h头文件部分如下,两个宏的值都一样为1。
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.          ……  
  2. /* supported address families. */  
  3. #define af_unspec       0  
  4. #define af_unix         1       /* unix domain sockets          */  
  5. #define af_local        1       /* posix name for af_unix       */  
  6. #define af_inet         2       /* internet ip protocol         */  
  7. #define af_ax25         3       /* amateur radio ax.25          */  
  8. ……  
以af_xx开头和pf_xx开头的域都是一样的,继续看头文件部分内容就一切都明白了:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. ……  
  2. #define pf_unspec       af_unspec  
  3. #define pf_unix         af_unix  
  4. #define pf_local        af_local  
  5. #define pf_inet         af_inet  
  6. #define pf_ax25         af_ax25  
  7. ……  
所以我们在指定socket的类型时这四个域可以随便用啦,笔者这里统一使用af_unix了。
type 参数可被设置为 sock_stream(流式套接字)或 sock_dgram(数据报式套接字),对于本地套接字来说,流式套接字(sock_stream)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;数据报式套接字(sock_dgram)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。
本地套接字的通信双方均需要具有本地地址,地址类型为 struct sockaddr_un结构体(位于linux/un.h):
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #ifndef _linux_un_h  
  2. #define _linux_un_h  
  3.    
  4. #define unix_path_max   108  
  5.    
  6. struct sockaddr_un {  
  7.         sa_family_t sun_family; /* af_unix */  
  8.         char sun_path[unix_path_max];   /* pathname */  
  9. };  
  10.  #endif /* _linux_un_h */  
创建监听套接字:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int ser_afunix_listen(const char *pathname)  
  2. {  
  3.     int                 listenfd, on;  
  4.     struct sockaddr_un  seraddr;  
  5.    
  6.     listenfd = socket(af_unix, sock_stream, 0);  
  7.     if (listenfd < 0) {  
  8.         fprintf(stderr, "socket: %s\n", strerror(errno));  
  9.         return -1;  
  10.     }  
  11.    
  12.     unlink(pathname);  
  13.     seraddr.sun_family = af_unix;  
  14.     snprintf(seraddr.sun_path, sizeof(seraddr.sun_path), "%s", pathname);  
  15.    
  16.     if (bind(listenfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr_un)) < 0) {  
  17.         fprintf(stderr, "bind: %s\n", strerror(errno));  
  18.         return -1;  
  19.     }  
  20.    
  21.     if (listen(listenfd, sock_ipc_max_conn) < 0) {  
  22.         fprintf(stderr, "listen: %s\n", strerror(errno));  
  23.         return -1;  
  24.     }  
  25.    
  26.     return listenfd;  
  27. }  
服务进程处理连接请求类似上面网络通信。
 
客户进程初始化套接字过程为:
[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. int clt_afunix_conn_init(const char *pathname)  
  2. {  
  3.     int                 fd;  
  4.     struct sockaddr_un  localaddr;  
  5.    
  6.     fd = socket(af_unix, sock_stream, 0);  
  7.     if (fd < 0) {  
  8.         fprintf(stderr, "socket: %s\n", strerror(errno));  
  9.         return -1;  
  10.     }  
  11.    
  12.     localaddr.sun_family = af_unix;  
  13.     snprintf(localaddr.sun_path, sizeof(localaddr.sun_path), "%s-cltid%d", pathname, getpid());  
  14.    
  15.     if (bind(fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr_un)) < 0) {  
  16.         fprintf(stderr, "bind: %s\n", strerror(errno));  
  17.         return -1;  
  18.     }  
  19.    
  20.     seraddr.sun_family = af_unix;  
  21.     snprintf(seraddr.sun_path, sizeof(seraddr.sun_path), "%s", pathname);  
  22.    
  23.     return fd;  
  24. }  

客户进程向服务进程发送接收请求类似上面网络通信。
运行结果同网络通信部分。
本节仅仅给出了一个很简单的通信程序,仅仅简单实现了客户进程和服务进程之间的通信,在下一节笔者将会在本节示例的基础上做修改,写一个在实际应用中可以使用的代码。
本节示例代码下载链接:
 http://download.csdn.net/detail/gentleliu/8140459
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值