使用Unix域套接字进行跨进程通信

Unix域套接字简介

《Unix环境高级编程》中对Unix域套接字有如下介绍:

虽然socketpair函数创建相互连接的一对套接字,但是每一个套接字都没有名字。这意味着无关进程不能使用它们。

我们可以命名unix域套接字,并可将其用于告示服务。但是要注意的是,UNXI与套接字使用的地址不同与因特网域套接字。

UNIX域套接字的地址由sockaddr_un结构表示。

在linux2.4.22中,sockaddr_un结构按下列形式定义在有文件

    struct sockaddr_un{
    sa_family_t sun_family; //AF_UNIX
    char sun_path[108]; //pathname
    };

sun_path成员包含一路经名,当我们将一个地址绑定至UNIX域套接字时,系统用该路经名创建一类型为S_IFSOCK文件。该文件仅用于向客户进程告知套接字名字。该文件不能打开,也不能由应用程序用于通信。

如果当我们试图绑定地址时,该文件已经存在,那么bind请求失败。当关闭套接字时,并不自动删除该文件,所以我们必须确保在应用程序终止前,对该文件执行解除链接操作。

服务器进程可以使用标准bind、listen和accept函数,为客户进程安排一个唯一UNIX域连接。客户进程使用connect与服务器进程连接;

服务器进程接受了connect请求后,在服务器进程和客户进程之间就存在了唯一连接。这种风格和因特网套接字的操作很像。

使用命名的Unix域套接字进程编程

示例为使用Unix域套接字实现一个Client-Server交互的功能

Server端代码如下:创建Unix套接字并绑定到 /tmp/test.sock 下进行监听,当有客户端连接时fork出子进程进行处理,子进程负责接收数据并打印到屏幕上:

/******************************************************************************
 * 文件名称:TestUnixSocket.cpp
 * 文件描述:Unix域套接字测试
 * 创建日期:2015-04-02
 * 作    者:casheywen
 ******************************************************************************/

#include <iostream>
using namespace std;
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>

#define LOG_ERR(fmt, args...) fprintf(stderr, "PID:%d|"fmt"\n", getpid(), ##args)
#define LOG_INFO(fmt, args...) fprintf(stdout, "PID:%d|"fmt"\n", getpid(), ##args)

int CreateUnixSocket(const char *pszPath)
{
    int iFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (iFd < 0)
    {
        LOG_ERR("Create Socket Fail: %s", strerror(errno));
        return -1;
    }

    struct sockaddr_un stAddr;
    memset(&stAddr, 0, sizeof(stAddr));

    stAddr.sun_family = AF_UNIX;
    strncpy(stAddr.sun_path, pszPath, sizeof(stAddr.sun_path));


    int iRet = bind(iFd, (struct sockaddr *)&stAddr, sizeof(stAddr));
    if (iRet < 0)
    {
        LOG_ERR("Bind Fail: %s", strerror(errno));
        return -1;
    }

    return iFd;
}

int main(int argc, char *argv[])
{
    const char szSocketPath[] = "/tmp/test.sock";

    if (0 == access(szSocketPath, F_OK))
    {
        unlink(szSocketPath);
    }

    int iFd = CreateUnixSocket(szSocketPath);

    if (iFd < 0)
    {
        LOG_ERR("CreateUnixSocket %s Fail", szSocketPath);
        return 1;
    }

    int iRet = 0;

    while (true)
    {
        iRet = listen(iFd, 5);
        if (iRet < 0)
        {
            LOG_ERR("listen Fail: %s", strerror(errno));
            return 1;
        }

        LOG_INFO("Listening on %s", szSocketPath);

        struct sockaddr_un stClientAddr;
        socklen_t nClientAddrLen = sizeof(stClientAddr);
        memset(&stClientAddr, 0, sizeof(stClientAddr));

        int iClientFd = accept(iFd, (struct sockaddr *)&stClientAddr, &nClientAddrLen);
        if (iClientFd < 0)
        {
            LOG_ERR("accept Fail: %s", strerror(errno)); 
            return 1;
        }

        LOG_INFO("Connected: Client Addr %s", stClientAddr.sun_path);

        pid_t pid = fork();

        if (pid == 0)   // 父进程
        {
            close(iClientFd);
            continue;
        }
        else if (pid < 0)
        {
            LOG_ERR("fork Fail: %s", strerror(errno));
        }

        char acBuf[4096] = {0};

        int iCnt = 0;

        while ((iCnt = read(iClientFd, acBuf, sizeof(acBuf))))
        {
            LOG_INFO("Read %d Bytes:[%s]", iCnt, acBuf); 
        }

        LOG_INFO("Disconnected: Client Addr %s", stClientAddr.sun_path);

        return 0;
    }
}

客户端代码如下:创建Unix套接字并连接/tmp/test.sock 下监听的套接字,从标准输入读取数据并通过套接字发送到Server端

/******************************************************************************
 * 文件名称:Client.cpp
 * 文件描述:Unix域套接字测试
 * 创建日期:2015-04-02
 * 作    者:casheywen
 ******************************************************************************/

#include <iostream>
using namespace std;
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>

#define LOG_ERR(fmt, args...) fprintf(stderr, "PID:%d|"fmt"\n", getpid(), ##args)
#define LOG_INFO(fmt, args...) fprintf(stdout, "PID:%d|"fmt"\n", getpid(), ##args)

void SigPipeHandler(int iSigno)
{
    LOG_ERR("SigPipe received");
    exit(1);
}

bool ConnectUnixSocket(const char *pszPath, int iFd)
{
    struct sockaddr_un stAddr;
    memset(&stAddr, 0, sizeof(stAddr));

    stAddr.sun_family = AF_UNIX;
    strncpy(stAddr.sun_path, pszPath, sizeof(stAddr.sun_path));

    int iRet = connect(iFd, (struct sockaddr *)&stAddr, sizeof(stAddr));
    if (iRet < 0)
    {
        LOG_ERR("Connect Fail: %s", strerror(errno));
        return false;
    }

    return true;
}


int main()
{
    const char szSocketPath[] = "/tmp/test.sock";

    int iFd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (iFd < 0)
    {
        LOG_ERR("Create Socket Fail: %s", strerror(errno));
        return 1;
    }

    if (!ConnectUnixSocket(szSocketPath, iFd))
    {
        LOG_ERR("ConnectUnixSocket Fail");
        return 1;
    }

    LOG_INFO("Connect Success");

    if (SIG_ERR == signal(SIGPIPE, SigPipeHandler))    // 当连接中断时调用write函数会收到SIGPIPE信号
    {
        LOG_ERR("Signal Fail: %s", strerror(errno));
        return 1;
    }

    char szContent[2048];
    ssize_t nWrite = 0;

    while (cin >> szContent)
    {
        nWrite = write(iFd, szContent, strlen(szContent));

        if (nWrite < 0)
        {
            LOG_ERR("write Fail: %s", strerror(errno));
            return 1;
        }
    }

    return 0;
}

程序测试结果:

Server端

$ ./TestUnixSocket 
PID:10013|Listening on /tmp/test.sock
PID:10013|Connected: Client Addr 
PID:10037|Listening on /tmp/test.sock
PID:10013|Read 13 Bytes:[alsdkfjlasjdf]
PID:10013|Read 18 Bytes:[asdfljasldfalskdjf]
PID:10013|Read 20 Bytes:[alsdjkfasldfjalsdkfj]
PID:10013|Read 29 Bytes:[asdasdfasdfasdfasdasdflkjsadf]
^C

Client端

$ ./Client 
PID:10036|Connect Success
alsdkfjlasjdf
asdfljasldfalskdjf
alsdjkfasldfjalsdkfj
asdasdfasdfasdfasdasdflkjsadf
asdfasdffsd
PID:10036|SigPipe received          --- Server端退出后对Fd写入,收到SIGPIPE

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。