UNIX环境高级编程(第17章 高级进程间通信)

基于STREAMS的管道和UNIX域套接字是两种高级的进程间通信机制,通过使用这两种IPC,可以在进程间传送打开文件描述符。服务进程可以使它们的打开文件描述符与指定的名字相关联,客户进程可以使用这些名字与服务进程通信。

1 基于STREAMS的管道

   基于STREAMS的管道是一个双向(全双工)管道。单个STREAMS管道就能向父、子进程提供双向的数据流。STREAMS管道必须在基于流的系统上才能实现,Linux默认对基于流的管道是不支持的(Solaris支持STREAMS管道,Linux的可选附加包也提供了STREAMS管道)。在Linux上,同样的逻辑可以用UNIX域套接字来实现。

2 UNIX域套接字

UNIX域套接字用于在同一台机器上运行的进程之间的通信。虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高。UNIX域套接字仅仅复制数据;它们并不执行协议处理,不需要添加或删除网络报头,无需计算检验和,不用产生顺序号,无需发送确认报文。

UNIX域套接字提供流和数据报两种接口。UNIX域套接字是套接字和管道之间的混合物。为了创建一对非命名的、相互连接的UNIX域套接字,用户可以使用它们面向网络的域套接字接口,也可以使用socketpair函数。

头文件

#include <sys/socket.h>

函数

int socketpair(int domain, int type, int protocl, int sockfd[2]);

返回值

成功返回0,出错返回-1

功能

创建相互连接的一对套接字

缺点

每一个套接字都没有名字,意味着无关进程不能使用它们。

2.1 命名UNIX域套接字

可将UNIX域套接字命名,用于告示服务。

UNIX域套接字使用的地址格式不同于因特网域套接字。

#include<sys/un.h>

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <poll.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>

#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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <syslog.h>
#include <errno.h>
#include <poll.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/stat.h>

#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函数。











评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值