Unix Domain Socket

1. 什么是Unix Domain Socket

Socket(套接字)是操作系統(tǒng)定義的一套通信方式和實(shí)現(xiàn)通信的系統(tǒng)調(diào)用成玫,比如最常用的互
聯(lián)網(wǎng)上兩臺(tái)終端之間的通信加酵。

Unix Domain Socket也是Socket的一種拳喻,專(zhuān)門(mén)用于同一臺(tái)機(jī)器的不同進(jìn)程之間進(jìn)行通信

2. 最簡(jiǎn)單的用法

在詳細(xì)介紹Unix Domain Socket各種用法的細(xì)節(jié)前,先看一個(gè)簡(jiǎn)單的例子

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <unistd.h>
#include <wait.h>

int
main(int argc, char *argv[])
{
    int fds[2];
    if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) != 0)
    {
        perror("fail to create socketpair");
        return 1;
    }

    pid_t pid = fork();
    if (pid < 0)
    {
        perror("fail to fork");
        return 1;
    }
    if (pid > 0)
    {
        /*parent process*/
        if (write(fds[1], "hi!", 3) == -1)
        {
            perror("fail to write");
            return 1;
        }
        /*wait for the child process terminatation*/
        wait(NULL);
    } else {
        /*child process*/
        char buf[4] = {0};
        if (read(fds[0], buf, 3) == -1)
        {
            perror("fail to read");
            return 1;
        }
        printf("receive msg:%s\n", buf);
    }
    
}

2.1 程序簡(jiǎn)單分析

  1. 首先通過(guò)socketpair函數(shù)創(chuàng)建了一對(duì)已經(jīng)連接的socket猪腕,不同進(jìn)程可以通過(guò)這對(duì)
    socket進(jìn)行通信冗澈,建立后的socket如下圖所示
   user process
+------------------------+
|                        |
|                        |
| fd[0]           fd[1]  |
+------------------------+
     ^              ^
     |              |
     |              |
     V              V
 +------+        +------+
 |socket|<------>|socket|
 +------+        +------+
 

socketpair調(diào)用成功后,會(huì)通過(guò)fds參數(shù)返回兩個(gè)文件描述符码撰,后面通過(guò)這兩個(gè)文件描
述符進(jìn)行通信渗柿。

  1. 通過(guò)fork函數(shù)創(chuàng)建了一個(gè)子進(jìn)程
  2. 父進(jìn)程中利用已經(jīng)創(chuàng)建好的socket,向fd[1]中寫(xiě)入字符串:hi!
  3. 子進(jìn)程利用已經(jīng)創(chuàng)建好的socket脖岛,從fd[0]中讀取字符串
    因此程序的輸出結(jié)果是:receive msg:hi!

2.2 socketpair函數(shù)

#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int socketfd[2]);
  • 函數(shù)作用:創(chuàng)建一對(duì)沒(méi)有名字的且已經(jīng)連接的socket
    沒(méi)有名字是相對(duì)于后面有名字的socket來(lái)說(shuō)的朵栖,名字相當(dāng)于網(wǎng)絡(luò)通信里的地址,客戶端可
    以通過(guò)名字與特定的服務(wù)端進(jìn)行通信柴梆。已經(jīng)連接的含義是在創(chuàng)建成功的情況下陨溅,可以直
    接通過(guò)這兩個(gè)socketfd進(jìn)行通信。

  • 參數(shù)
    除了最后一個(gè)參數(shù)绍在,前面3個(gè)參數(shù)表示創(chuàng)建socket的類(lèi)型门扇,對(duì)于網(wǎng)絡(luò)通信的socket來(lái)說(shuō)類(lèi)型
    很多,但對(duì)于unix socket來(lái)說(shuō)偿渡,這些參數(shù)取值比較確定臼寄。
    domainAF_UNIX或者AF_LOCAL代表unix socket
    type 可以是SOCK_STREAM SOCK_DGRAM或者SOCK_SEQPACKET 這幾種不同類(lèi)型是通
    用的類(lèi)型,對(duì)unix socket來(lái)說(shuō)其實(shí)區(qū)別不大溜宽,例如unix socket下都是同一臺(tái)機(jī)器吉拳,
    SOCK_DGRAM類(lèi)型的socket也是可靠。
    另一方面适揉,一些接口層面的區(qū)別仍然是存在的留攒,比如對(duì)于SOCK_STREAM類(lèi)型的socket一般
    的通信過(guò)程是:

  1. 創(chuàng)建一個(gè)socket
  2. 綁定一個(gè)地址
  3. 調(diào)用listen告訴內(nèi)核已經(jīng)準(zhǔn)備好接受連接請(qǐng)求
  4. 調(diào)用accept建立一個(gè)連接
  5. 雙方通信
    對(duì)于SOCK_DGRAM類(lèi)型的socket來(lái)說(shuō),是沒(méi)有3和4這兩步的

protocol固定傳0

  • 返回值:成功返回0, 失敗返回-1

socketpairpipe的作用和用法都十分相似嫉嘀,主要區(qū)別是scokpair調(diào)用成功返回的
一對(duì)描述符是可以互相通信的炼邀,而pipe返回的一對(duì)描述符只能單方向通信,一個(gè)描述符
只能用于寫(xiě)剪侮,另一個(gè)描述符只能用于讀

3. 有名字的UNIX Domain Sockets

前面提到socketpair創(chuàng)建的unix socket是沒(méi)有名字的拭宁,名字相對(duì)于網(wǎng)絡(luò)通信中的地址,
沒(méi)有地址瓣俯,其他不相關(guān)進(jìn)程也就不能和他進(jìn)行通信红淡,那么有名字的unix socket如何創(chuàng)建
呢?

下面的例子是一個(gè)服務(wù)端/客戶端通過(guò)unix socket通信的例子降铸,通信內(nèi)容非常簡(jiǎn)單,
客戶端向服務(wù)端發(fā)送一段字符串摇零,服務(wù)端接受后打印出來(lái)推掸。代碼里大部分的都是socket通
信的一些前期常規(guī)準(zhǔn)備工作,這些工作的主要步驟是:創(chuàng)建socket,綁定地址谅畅,等待連接登渣,
握手連接,連接成功進(jìn)行通信

  • unix socket服務(wù)端
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>   //strcpy
#include <stddef.h> //offsetof
#include <unistd.h>

int
main(int argc, char *argv[])
{
    /* 1. 創(chuàng)建socket */
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("fail to create socket");
        return 1;
    }

    struct sockaddr_un addr;
    addr.sun_family = AF_UNIX;
    /* 綁定的地址為當(dāng)前目錄的文件名為my_unix_domain的文件*/
    strcpy(addr.sun_path, "my_unix_domain");
    int addrLen = offsetof(struct sockaddr_un, sun_path) 
        + strlen(addr.sun_path) + 1;
    /* 2. 綁定地址*/
    if (bind(fd, (struct sockaddr*)&addr, addrLen) == -1)
    {
        perror("fail to bind");
        return 1;
    }
    /* 3. 監(jiān)聽(tīng)客戶端連接 */
    if (listen(fd, 10) == -1)
    {
        perror("listen");
        return 1;
    }

    struct sockaddr_un clientAddr;
    socklen_t clientLen = sizeof(clientAddr);
    /* 4. 和客戶端建立連接 */
    int clientFD = accept(fd, (struct sockaddr *)&clientAddr, &clientLen);
    if (clientFD == -1)
    {
        perror("accept");
        return 1;
    }

    /* 5. 讀取客戶端發(fā)來(lái)的內(nèi)容 */
    char buf[256] = {0};
    int n = read(clientFD, buf, 256);
    if (n < 0)
    {
        perror("read");
        return 1;
    }
    printf("receive msg:%s\n", buf);
    close(fd);
    close(clientFD);
    /* 6. 釋放綁定的文件 */
    unlink("my_unix_domain");
}

客戶端代碼

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        exit(1);
    }
    struct sockaddr_un servAddr;
    servAddr.sun_family = AF_UNIX;
    strcpy(servAddr.sun_path, "my_unix_domain");
    int addrLen = offsetof(struct sockaddr_un, sun_path) 
        + strlen(servAddr.sun_path) + 1;
    if (connect(fd, (struct sockaddr *)&servAddr, addrLen) < 0)
    {
        perror("connect");
        exit(1);
    }
    printf("connect to server\n");
    char buf[256] = "hi~";
    int n = write(fd, buf, strlen(buf));
    if (n < 0)
    {
        perror("write");
        exit(1);
    }
    close(fd);
    return 0;
}

3.1 Unix Domain Socket地址格式

通常Unix Socket的地址有兩種情況毡泻,一種是上面提到的無(wú)名字的unix domain socket胜茧,另
一種有明確地址的,且地址和磁盤(pán)文件對(duì)應(yīng)仇味,使用下面結(jié)構(gòu)表示

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

sun_family字段固定為AF_UNIX呻顽,sun_path是一個(gè)磁盤(pán)文件的路徑,以null字符結(jié)尾丹墨。
上面的例子中廊遍,綁定的地址是當(dāng)前目錄下的名字為my_unix_domain的文件,綁定成功后贩挣,
操作系統(tǒng)會(huì)在磁盤(pán)上創(chuàng)建對(duì)應(yīng)的文件喉前,如果其他socket的試圖綁定這個(gè)地址,操作系統(tǒng)檢
測(cè)到磁盤(pán)中已經(jīng)存在對(duì)應(yīng)文件王财,就會(huì)報(bào)錯(cuò)卵迂,提示地址已經(jīng)被綁定。
socket關(guān)閉后绒净,操作系統(tǒng)不會(huì)自動(dòng)清楚這個(gè)文件见咒,所以需要顯式的調(diào)用unlink函數(shù)刪除這
個(gè)文件。

在Linux系統(tǒng)中疯溺,Unix Domain Socket地址除了上面兩種情況外论颅,還有一種虛擬地址類(lèi)型,
也使用struct sockadd_un結(jié)構(gòu)表示囱嫩,但sun_path字段不一樣恃疯,和磁盤(pán)文件無(wú)關(guān),且
sun_path字段的第一個(gè)字節(jié)是null('\0')墨闲。另外今妄,socket關(guān)閉后,這種虛擬的地址也會(huì)
自動(dòng)釋放

3.2 地址長(zhǎng)度的計(jì)算

上述代碼中試用的計(jì)算方式是:

offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1

而不是:

sizeof(sa_family_t) + strlen(sun_path) + 1

這是因?yàn)橛械南到y(tǒng)實(shí)現(xiàn)中鸳碧,在sun_familysun_path字段之間可能還有其他字段盾鳞,因
次采用第一種方式,代碼移植性更好

4 傳遞輔助數(shù)據(jù)(Ancillary Data)

Unix Domain Socket還支持傳遞輔助數(shù)據(jù)瞻离,如文件句柄和認(rèn)證信息腾仅,下面的例子是傳遞了
句柄,可以看出傳遞時(shí)所用的數(shù)據(jù)結(jié)構(gòu)很復(fù)雜套利,而且必須調(diào)用sendmsg函數(shù)

  • 服務(wù)端代碼
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/un.h>
#include <errno.h>
#include <string.h>   //strcpy
#include <stddef.h> //offsetof
#include <unistd.h>

int NUM_FD = 1;

void
sendfd(int clientfd, int fd_to_send)
{
    struct msghdr msg = { 0 };
    struct cmsghdr *cmsg;
    int myfds[NUM_FD];  /* Contains the file descriptors to pass */
    myfds[0] = fd_to_send;
    char iobuf[2] = {'f'};
    iobuf[0] = 'e';
    struct iovec io = {
        .iov_base = iobuf,
        .iov_len = sizeof(iobuf)
    };
    union {         /* Ancillary data buffer, wrapped in a union
                       in order to ensure it is suitably aligned */
        char buf[CMSG_SPACE(sizeof(myfds))];
        struct cmsghdr align;
    } u;

    msg.msg_iov = &io;
    msg.msg_iovlen = 1;
    msg.msg_control = u.buf;
    msg.msg_controllen = sizeof(u.buf);
    cmsg = CMSG_FIRSTHDR(&msg);
    cmsg->cmsg_level = SOL_SOCKET;
    cmsg->cmsg_type = SCM_RIGHTS;
    cmsg->cmsg_len = CMSG_LEN(sizeof(int) * NUM_FD);
    memcpy(CMSG_DATA(cmsg), myfds, NUM_FD * sizeof(int));
    printf("server send msg_controllen is %d\n", msg.msg_controllen);
    int send_bytes = sendmsg(clientfd, &msg, 0);
    if (send_bytes == -1)
    {
        perror("sendmsg");
    } else {
        printf("send %d bytes\n", send_bytes);
    }
}

int
main(int argc, char *argv[])
{
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd == -1)
    {
        perror("fail to create socket");
        return 1;
    }

    struct sockaddr_un addr;
    memset(&addr, 0, sizeof(struct sockaddr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path, "my_unix_domain");
    int addrLen = offsetof(struct sockaddr_un, sun_path) 
        + strlen(addr.sun_path) + 1;
    if (bind(fd, (struct sockaddr*)&addr, addrLen) == -1)
    {
        perror("fail to bind");
        return 1;
    }
    if (listen(fd, 10) == -1)
    {
        perror("listen");
        return 1;
    }

    struct sockaddr_un clientAddr;
    memset(&clientAddr, 0, sizeof(struct sockaddr_un));
    socklen_t clientLen = sizeof(clientAddr);
    int clientFD = accept(fd, (struct sockaddr *)&clientAddr, &clientLen);
    if (clientFD == -1)
    {
        perror("accept");
        return 1;
    } 
    char buf[256] = {0};
    int n = read(clientFD, buf, 256);
    if (n < 0)
    {
        perror("read");
        return 1;
    }
    printf("receive msg:%s\n", buf);
    int fd_to_send = open("a.txt", O_RDONLY);
    if (fd_to_send == -1 )
    {
        perror("open");
        return 1;
    }

    sendfd(clientFD, fd_to_send);
    close(fd);
    close(clientFD);
    unlink("my_unix_domain");
}

  • 客戶端代碼
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    int fd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (fd < 0)
    {
        perror("socket");
        exit(1);
    }

    struct sockaddr_un servAddr;
    servAddr.sun_family = AF_UNIX;
    strcpy(servAddr.sun_path, "my_unix_domain");
    int addrLen = offsetof(struct sockaddr_un, sun_path) 
        + strlen(servAddr.sun_path) + 1;
    if (connect(fd, (struct sockaddr *)&servAddr, addrLen) < 0)
    {
        perror("connect");
        exit(1);
    }
    printf("connect to server\n");
    char buf[256] = "hi~";
    int n = write(fd, buf, strlen(buf));
    if (n < 0)
    {
        perror("write");
        exit(1);
    }

    sleep(1);
    /* receive ancillary data*/
    struct msghdr msg = {0};
    char iobuf[1] = {'b'};
    struct iovec iov[1];
    iov[0].iov_base = iobuf;
    iov[0].iov_len = sizeof(iobuf);
    msg.msg_iov = iov;
    msg.msg_iovlen = 1;
    int myfds[1];
    union {         /* Ancillary data buffer, wrapped in a union
                       in order to ensure it is suitably aligned */
        char buf[CMSG_SPACE(sizeof(myfds))];
        struct cmsghdr align;
    } u;
    msg.msg_control = u.buf;
    msg.msg_controllen = sizeof(u.buf);

    ssize_t receive_size = recvmsg(fd, &msg, 0);
    if ( receive_size == -1)
    {
        perror("recvmsg");
    } else {
        printf("client receive size is %d,  receive msg_iovlen is %d, msg_controllen length is %d\n", receive_size, msg.msg_iovlen, 
                msg.msg_controllen);
        printf("client buf is %c\n", iobuf[0]);
        struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
        if (cmsg == NULL) {
            perror("cmsg is null");
        } else {
            int receive_fd = *(int *)CMSG_DATA(cmsg);
            char buf[1024];
            if (read(receive_fd, buf, 1024) == -1)
            {
                perror("read");
            } else {
                printf("read from receive fd:%s\n", buf);
            }
            close(receive_fd);
        }
    }
    close(fd);
    return 0;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末推励,一起剝皮案震驚了整個(gè)濱河市鹤耍,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌验辞,老刑警劉巖稿黄,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異跌造,居然都是意外死亡杆怕,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén)壳贪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)陵珍,“玉大人,你說(shuō)我怎么就攤上這事撑碴〕沤蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵醉拓,是天一觀的道長(zhǎng)伟姐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)亿卤,這世上最難降的妖魔是什么愤兵? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮排吴,結(jié)果婚禮上秆乳,老公的妹妹穿的比我還像新娘。我一直安慰自己钻哩,他們只是感情好屹堰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著街氢,像睡著了一般扯键。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上珊肃,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天荣刑,我揣著相機(jī)與錄音,去河邊找鬼伦乔。 笑死厉亏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的烈和。 我是一名探鬼主播爱只,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼招刹!你這毒婦竟也來(lái)了恬试?” 一聲冷哼從身側(cè)響起沥匈,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎忘渔,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體缰儿,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡畦粮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乖阵。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宣赔。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瞪浸,靈堂內(nèi)的尸體忽然破棺而出儒将,到底是詐尸還是另有隱情,我是刑警寧澤对蒲,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布钩蚊,位于F島的核電站,受9級(jí)特大地震影響蹈矮,放射性物質(zhì)發(fā)生泄漏砰逻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一泛鸟、第九天 我趴在偏房一處隱蔽的房頂上張望蝠咆。 院中可真熱鬧,春花似錦北滥、人聲如沸刚操。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)菊霜。三九已至,卻和暖如春祝闻,著一層夾襖步出監(jiān)牢的瞬間占卧,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工联喘, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留华蜒,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓豁遭,卻偏偏與公主長(zhǎng)得像叭喜,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子蓖谢,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容