基于STREAMS的管道和UNIX域套接字是两种高级的进程间通信机制,通过使用这两种IPC,可以在进程间传送打开文件描述符。服务进程可以使它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务进程通信。
基于STREAMS的管道是一个双向(全双工)管道。单个STREAMS管道就能向父、子进程提供双向的数据流。STREAMS管道必须在基于流的系统上才能实现,Linux默认对基于流的管道是不支持的(Solaris支持STREAMS管道,Linux的可选附加包也提供了STREAMS管道)。在Linux上,同样的逻辑可以用UNIX域套接字来实现。
UNIX域套接字用于在同一台机器上运行的进程之间的通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不用产生顺序号,无需发送确认报文。
UNIX域套接字提供流和数据报两种接口。UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的、相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接口,也可以使用socketpair函数。
头文件 |
#include |
函数 |
int socketpair(int domain, int type, int protocl, int sockfd[2]); |
返回值 |
成功返回0,出错返回-1 |
功能 |
创建相互连接的一对套接字 |
缺点 |
每一个套接字都没有名字,意味着无关进程不能使用它们。 |
2.1 命名UNIX域套接字
可将UNIX域套接字命名,用于告示服务。
UNIX域套接字使用的地址格式不同于因特网域套接字。
#include struct sockaddr_un { sa_family_t sun_family;/*AF_UNIX*/ char sun_path[108];/*pathname*/ }; |
Sockaddr_un结构的sun_path成员包含一路径名。当我们将一地址绑定至UNIX域套接字时,系统用该路径名创建一类型为S_IFSOCK的文件。
该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。
如果试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以必须确保应用程序终止前,对该文件执行解除链接操作,可通过调用unlink()来实现。
UNIX域套接字与因特网域套接字的有两个主要区别:首先,套接字的地址是文件系统路径,而不是一个包含服务器名称和端口的元组。其次,在文件系统中创建的表示套接字节点在套接字关闭后永久存在,服务器重新启动时需要删除这些文件。
/*UNIX域进程间通信,服务器端server.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CMD_ADD 0
#define CMD_DEL 1
#define CMD_MDY 2
#define CMD_QUR 3
/*进程间通信路径名*/
#define IPC_PATHNAME "/ipc_pathname"
typedef struct ipc_req {
int cmd; //请求命令
/*something else*/
}ipc_req_t;
typedef struct ipc_rsp {
int res; //请求的响应结果
/*something else*/
}ipc_rsp_t;
/*守护进程具体处理*/
void daemon_process(void)
{
int fd;
int acp_fd;
int err;
int size;
ipc_req_t req;
ipc_rsp_t rsp;
struct sockaddr_un un_addr;
struct sockaddr sock_addr;
struct pollfd poll_array;
socklen_t len = (socklen_t)sizeof(sock_addr);
memset(&req,0,sizeof(req));
memset(&rsp,0,sizeof(rsp));
/*创建套接字*/
fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if (fd == -1)
{
syslog(LOG_INFO, "socket error\n");
return ;
}
memset(&un_addr, 0, sizeof(un_addr));
un_addr.sun_family = AF_LOCAL;
strcpy(un_addr.sun_path, IPC_PATHNAME);
size = offsetof(struct sockaddr_un, sun_path) + strlen(un_addr.sun_path);
unlink(un_addr.sun_path);/*以防IPC_PATHNAME文件存在*/
/*绑定地址*/
if((err = bind(fd, (struct sockaddr *)&un_addr, size)) < 0)
{
syslog(LOG_INFO, "bind error, %s\n", strerror(errno));
close(fd);
return;
}
/*监听*/
if ((err = listen(fd, 5))<0)
{
syslog(LOG_INFO, "listen error\n");
close(fd);
return;
}
poll_array.fd = fd; /*描述符*/
poll_array.events = POLLIN;/*等待事件*/
for(;;)
{
/*若直接调用accept则当没有连接请求时,服务器会阻塞到一个请求到来。
可通过poll来等待一个请求的到来。这种情况下,一个带等待处理的连接
请求套接字会以可读的方式出现。
*/
err = poll(&poll_array, 1, 2);
if (err < 0)
{
/*出错退出*/
syslog(LOG_INFO, "poll error..\n");
break;
}
else if(err == 0)
{
/*超时继续*/
continue;
}
else
{
/*事件到来时,将revents设置为发生的事件*/
if(poll_array.revents)
{
/*接收连接请求*/
acp_fd = accept(fd, (struct sockaddr *)&sock_addr, &len);
if(acp_fd < 0)
{
syslog(LOG_INFO, "accept error\n");
break;
}
/*接收数据*/
err = recv(acp_fd, (struct ipc_req*)&req, sizeof(req), MSG_WAITALL);
if (err != sizeof(req))
{
syslog(LOG_INFO, "recv error\n");
break;
}
rsp.res = 0;
/*处理请求*/
switch (req.cmd)
{
case CMD_ADD:
{
syslog(LOG_INFO, "server receive an add cmd....\n");
break;
}
case CMD_DEL:
{
syslog(LOG_INFO, "server receive a delete cmd....\n");
break;
}
case CMD_MDY:
{
syslog(LOG_INFO, "server receive a modify cmd....\n");
break;
}
case CMD_QUR:
{
syslog(LOG_INFO, "server receive a query cmd....\n");
break;
}
default:
{
syslog(LOG_INFO, "server receive a invaild cmd...\n");
rsp.res = -1;
break;
}
}
/*发送响应*/
err = send(acp_fd, (struct ipc_rsp*)&rsp, sizeof(rsp), MSG_DONTROUTE);
if (err != sizeof(rsp))
{
syslog(LOG_INFO, "send error\n");
break;
}
}
}
}
return ;
}
int main(void)
{
int fd;
umask(0);
fd = fork();
if (fd < 0)
{
printf("fork error...\n");
exit(1);
}
else if (fd > 0)
{
exit(1);
}
setsid();
fd = fork();
if (fd < 0)
{
printf("fork error\n");
exit(1);
}
else if (fd > 0)
{
exit(1);
}
if (chdir("/") < 0)
{
printf("change directory to / error..\n");
exit(1);
}
close(0);
close(1);
close(2);
open("/dev/null",O_RDWR);
dup(0);
dup(0);
openlog("server", LOG_CONS, LOG_DAEMON);
daemon_process();
exit(0);
}
/*UNIX域进程间通信,客户端client.c*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define CMD_ADD 0
#define CMD_DEL 1
#define CMD_MDY 2
#define CMD_QUR 3
/*进程间通信路径名*/
#define IPC_PATHNAME "/ipc_pathname"
typedef struct ipc_req {
int cmd; //请求命令
/*something else*/
}ipc_req_t;
typedef struct ipc_rsp {
int res; //请求的响应结果
/*something else*/
}ipc_rsp_t;
int main()
{
int fd;
int err;
int size;
ipc_req_t req;
ipc_rsp_t rsp;
struct sockaddr_un addr;
/*创建套接字*/
fd = socket(AF_LOCAL, SOCK_STREAM, 0);
if(fd < 0)
{
printf("create socket error, %s", strerror(errno));
return fd;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_LOCAL;
strcpy(addr.sun_path, IPC_PATHNAME);
size = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path);
/*请求连接*/
err = connect((int)fd, (struct sockaddr *)&addr, size);
if (err < 0)
{
printf("connect error, %s\n", strerror(errno));
close(fd);
return err;
}
req.cmd = CMD_QUR;/*具体命令*/
/*发送请求命令*/
err = send(fd, &req, sizeof(req), MSG_DONTROUTE);
if (err < 0)
{
printf("send data error, %s\n", strerror(errno));
return err;
}
/*接收请求响应*/
err = recv(fd, &rsp, sizeof(rsp), MSG_WAITALL);
if (err < 0)
{
printf("recv data error, %s\n", strerror(errno));
return err;
}
else if (rsp.res != 0)
{
err = rsp.res;
printf("requset handle error, %d\n", err);
return err;
}
return err;
}
/*输出*/
lincoln@ubuntu:~$ ./server
lincoln@ubuntu:~$ ./client
lincoln@ubuntu:~$ tail /var/log/messages -f
Sep 6 22:22:10 ubuntu server: server receive a query cmd....
2.2 传送文件描述符
在进程间传送打开的文件描述符,可以对客户进程/服务器进程应用进行不同的设计。使一个进程(一般是服务器进程)能够处理为打开一个文件所要求的一切操作(具体如将网络名翻译为网络地址、协商文件锁等)以及向调用进程送回一个描述符,该描述符可被用于以后的所有I/O函数。