1. 程式人生 > >使用Unix域套接字進行跨程序通訊

使用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