天天看點

本地套接字(Unix domain socket IPC)

1、基礎

雖然網絡socket也可用于同一台主機的程序間通訊(通過lo位址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要經過網絡協定棧,不需要打包拆包/計算校驗和/維護信号和應答等。隻是将應用層資料從一個程序拷貝到另一個程序。這是因為IPC機制本質上是可靠的通訊,而網絡協定是不可靠的通訊。

本地套接字(Unix domain socket IPC)

unix domain socket也提供面向流和面向資料的兩種API接口,類似TCP和UDP,但是面向消息的unix domain socket也是可靠的,消息既不會丢失也不會順序錯亂。

2、socket位址

socket:address family指定為AF_UNIX,type可以選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍指定為0即可。

unix domain socket的位址格式用sockaddr_un表示,指定一個socket類型檔案在檔案系統中的路徑。這個socket檔案有bind()調用建立,如果調用bind()時該檔案已存在,則bind()錯誤傳回。

unix domain socket用戶端一般要顯示調用bind(),而不是依賴系統自動配置設定的位址。用戶端bind一個自己指定的socket檔案名的好處是,該檔案可以包含用戶端的pid以便伺服器區分不同的用戶端。

time + pid

sprintf(cli_addr.sun_path, "%u.%u.sock", time(NULL), getpid()).

注:用戶端與伺服器端各自綁定自己的檔案,檔案名必須不同(兩端的檔案名沒有聯系)。

位址格式,摘自man unix:

A UNIX domain socket address is represented in the following structure:

#define UNIX_PATH_MAX 108

struct sockaddr_un {

sa_family_t sun_family; /* AF_UNIX */

char sun_path[UNIX_PATH_MAX]; /* pathname */

};

sun_family always contains AF_UNIX.

Three types of address are distinguished in this structure:

* pathname: a UNIX domain socket can be bound to a null-terminated file system pathname using

bind(2). When the address of the socket is returned by getsockname(2), getpeername(2), and

accept(2), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1, and

sun_path contains the null-terminated pathname.

* unnamed: A stream socket that has not been bound to a pathname using bind(2) has no name. Like?

wise, the two sockets created by socketpair(2) are unnamed. When the address of an unnamed socket

is returned by getsockname(2), getpeername(2), and accept(2), its length is sizeof(sa_family_t),

and sun_path should not be inspected.

* abstract: an abstract socket address is distinguished by the fact that sun_path[0] is a null byte

('\0'). The socket's address in this namespace is given by the additional bytes in sun_path that

are covered by the specified length of the address structure. (Null bytes in the name have no spe‐

cial significance.) The name has no connection with file system pathnames. When the address of an

abstract socket is returned by getsockname(2), getpeername(2), and accept(2), the returned addrlen

is greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of the socket is contained

in the first (addrlen - sizeof(sa_family_t)) bytes of sun_path. The abstract socket namespace is a

nonportable Linux extension.

3、應用

1)UDP應用

// UDP server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>

#define BUF_SIZE 10

#define DES_PATH "/tmp/main.socket"

int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_un un, peer_un;
    socklen_t len;
    int i;
    int ret;
    char buf[BUF_SIZE];

    sd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(sd < 0)
    {
        perror("socket");
        return -1;
    }
    
    unlink(DES_PATH);
    memset(&un, 0, sizeof(struct sockaddr_un));
    un.sun_family = AF_UNIX;
    strncpy(un.sun_path, DES_PATH, sizeof(un.sun_path) - 1);
    ret = bind(sd, (struct sockaddr *)&un, sizeof(struct sockaddr_un));
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    while(1)
    {
        memset(buf, 0, BUF_SIZE);
    //    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path) + 1;
        len = sizeof(struct sockaddr_un);
        ret = recvfrom(sd, buf, BUF_SIZE, 0, (struct sockaddr *)&peer_un, &len);        

        if(ret > 0)
        {
            printf("Recvfrom [%d] bytes from >>%s:\n", ret, peer_un.sun_path);
            for(i = 0; i < BUF_SIZE; i++)
            {
                printf("0x%.2x\t", 0xFF&buf[i]);
                if(0 == (i + 1) % 5)
                {
                    printf("\n");
                }
            }
        } else {
            printf("Recvfrom [%d]\n", ret);
        }    
    }

    close(sd);

    return 0;
}      
// UDP client
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stddef.h>
#include <fcntl.h>
#include <time.h>
#include <sys/un.h>
#include <sys/socket.h>
#include <sys/stat.h>

int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_un un;
    socklen_t len;
    int tnode;
    int ret ;

    if(argc < 2)
    {
        return -1;
    }

    tnode = atoi(argv[1]);    
    sd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(sd < 0)
    {
        perror("socket");
        return -1;
    }

    memset(&un, 0, sizeof(struct sockaddr_un));
    un.sun_family = AF_UNIX;
//    strcpy(un.sun_path, "/tmp/main.socket");
    snprintf(un.sun_path, sizeof(struct sockaddr_un), "%u.%u.sock", time(NULL), getpid());

    printf("sockaddr is %s\n", un.sun_path);
    ret = bind(sd, (struct sockaddr *)&un, sizeof(struct sockaddr_un)); 
    if(ret < 0)
    {
        perror("bind");
        return -1;
    }

    memset(&un, 0, sizeof(struct sockaddr_un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, "/tmp/main.socket");

    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path) + 1;

    sendto(sd, &tnode, sizeof(int), 0, (struct sockaddr *)&un, len);
    
    close(sd);

    return 0;
}      

 運作結果:

~$./c 100
sockaddr is 1480428387.3096.sock
~$./c 1000
sockaddr is 1480428390.3097.sock
~$./c 10000
sockaddr is 1480428392.3099.sock
~$./c 100000
sockaddr is 1480428395.3100.sock
~$./c 1000001
sockaddr is 1480428398.3101.sock
~$./c 100
sockaddr is 1480428409.3103.sock      
~$./s
Recvfrom [4] bytes from >>1480428387.3096.sock:
0x64    0x00    0x00    0x00    0x00    
0x00    0x00    0x00    0x00    0x00    
Recvfrom [4] bytes from >>1480428390.3097.sock:
0xe8    0x03    0x00    0x00    0x00    
0x00    0x00    0x00    0x00    0x00    
Recvfrom [4] bytes from >>1480428392.3099.sock:
0x10    0x27    0x00    0x00    0x00    
0x00    0x00    0x00    0x00    0x00    
Recvfrom [4] bytes from >>1480428395.3100.sock:
0xa0    0x86    0x01    0x00    0x00    
0x00    0x00    0x00    0x00    0x00    
Recvfrom [4] bytes from >>1480428398.3101.sock:
0x41    0x42    0x0f    0x00    0x00    
0x00    0x00    0x00    0x00    0x00    
Recvfrom [4] bytes from >>1480428409.3103.sock:
0x64    0x00    0x00    0x00    0x00    
0x00    0x00    0x00    0x00    0x00      

 如果在server端讀資料前延遲一段時間如10s,在client端一個sock多次sendto相同資料,server讀取資料仍然和client發送包數量一緻并且接收資料一緻,可知udp每讀取一次都是一包資料,無需做分包處理。

UDP是面向消息的協定,每個UDP段都是一條消息,應用程式必須以消息為機關提取資料,不能一次提取任意位元組的資料。

UDP每讀取一次都是一包資料(UDP已做分包處理)。

TCP需要做分包處理,具體事例可參考文檔“TCP&UDP”。

注意:TCP隻能與接入它的用戶端通信,用戶端必須與伺服器綁定後才能互相通信。

UDP伺服器(準确的說,是UDP端)可以與任意用戶端通信,且兩者之間可以不建立聯系就可直接發送資訊。 

2)TCP應用

// unix_server.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#define SOCK_NAME "/tmp/echo.server"
#define LISTEN_BACKLOG 50
#define BUF_SIZE 1024

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while(0)

int main(int argc, char *argv[])
{
    int sfd = 0, cfd = 0;
    int i = 0, ret = 0;
    struct sockaddr_un my_addr, peer_addr;
    socklen_t peer_addr_size;
    char buf[BUF_SIZE] = {0};

    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(sfd < 0){
//        printf("%s socket error.\n", SOCK_NAME);
        handle_error("socket");
    }
    
    unlink(SOCK_NAME);
    memset(&my_addr, 0, sizeof(struct sockaddr_un));
    my_addr.sun_family = AF_UNIX;
    strncpy(my_addr.sun_path, SOCK_NAME, sizeof(my_addr.sun_path)-1);
    if(bind(sfd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr_un)) == -1){
        handle_error("bind");    
    }

    if(listen(sfd, LISTEN_BACKLOG) == -1){
        handle_error("listen");
    }
    
    signal(SIGCHLD, SIG_IGN);
    while(1){

        peer_addr_size = sizeof(struct sockaddr_un);
        cfd = accept(sfd, (struct sockaddr *)&peer_addr, &peer_addr_size);
        if(cfd < 0){
            handle_error("accept");
        }else {
            ret = fork();
            if(ret < 0){
                handle_error("fork");
            } else if(ret == 0){
                while(1){    
                    ret = read(cfd, buf, sizeof(buf));
                    buf[ret] = 0; 
                    printf("%s (len %d) recv %d bytes: %s\n", peer_addr.sun_path, peer_addr_size, ret, buf);
                    for(i = 0; i < ret; i++){
                        if(buf[i] >= 'a' && buf[i] <='z')
                            buf[i] += 'A'-'a';
                    }
                    write(cfd, buf, ret);
                }
            }
        }
    }

    return 0;
}      
// unix_client.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stddef.h>

#define SER_NAME "/tmp/echo.server"
#define SOCK_NAME_PRE "/tmp/ECHO" 

#define BUF_SIZE 1024

#define handle_error(msg) \
    do { perror(msg); exit(EXIT_FAILURE); } while(0)


int main(int argc, char *argv[])
{
    int cfd = 0;
    int ret = 0;
    struct sockaddr_un client_addr, server_addr;
    char buf[BUF_SIZE]={0};
    int len = 0;


    cfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if(cfd < 0){
        handle_error("socket");
    }

    memset(&client_addr, 0, sizeof(struct sockaddr_un));
    client_addr.sun_family = AF_UNIX;
    snprintf(client_addr.sun_path, sizeof(struct sockaddr_un), "%s.%d.%ld", SOCK_NAME_PRE, getpid(), time(NULL));
    len = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path) + 1;
    printf("client addr:%s, len:%d\n", client_addr.sun_path, len);
    ret = bind(cfd, (struct sockaddr *)&client_addr,  sizeof(struct sockaddr_un));
    if(ret < 0){
        handle_error("bind");
    }

    memset(&server_addr, 0, sizeof(struct sockaddr_un));
    server_addr.sun_family = AF_UNIX;
    strncpy(server_addr.sun_path, SER_NAME, sizeof(struct sockaddr_un));
    len = offsetof(struct sockaddr_un, sun_path) + strlen(server_addr.sun_path) + 1;
//    ret = connect(cfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_un));
    ret = connect(cfd, (struct sockaddr *)&server_addr, len);
    if(ret < 0){
        handle_error("connect");
    }

    while(1){
        printf("please input the bytes:\n");
        scanf("%s", buf);
        ret = strlen(buf);
        buf[ret] = 0;
        write(cfd, buf, ret+1);
        ret = read(cfd, buf, sizeof(buf));
        if(ret >=1024) ret = 1023;
        buf[ret]=0;
        printf("conversion %d bytes:[%s]\n", ret, buf);
    }

    return 0;
}      

該例程實作echo回顯且小寫變大寫功能。

3)本地套接字通過curl用HTTP協定通路:

curl --unix-socket /var/run/docker.sock -X GET http:/v1.39/containers/json
curl --unix-socket /var/run/docker.sock -X GET http:/containers/json      

4、socket IPC設計

本節内容來自:細說linux IPC(二):基于socket的程序間通信(下)

定義通信資料結構:

typedef struct _ipc_sock_msg_t {
    int     msg_type;//請求類型
    int     msg_rc;//服務程序處理結果的傳回值
    int     msg_buflen;//交換資料的大小
    char    msg_buf[SOCK_IPC_MAX_BUF];//交換資料的内容
} ipc_sock_msg_t;      

服務程序收到客戶程序請求之後首先判斷請求類型,根據請求類型來進行處理。我們首先定義一個函數數組,在服務程序接收請求之前将要處理的所有請求注冊到該函數數組當中來,收到請求之後根據請求類型索引找到處理函數。

函數數組如下:

static int (*sln_ipc_ser_func[SLN_IPC_MAX_TYPE])(
        void *recvbuf, int recv_size,
        void *sendbuf, int *send_size);      

服務程序接收處理之前先将需要處理的函數注冊到函數數組中,如下:

int sln_ipc_ser_func_init(void)
{
    int     i;
 
    for (i = 0; i < SLN_IPC_MAX_TYPE; i++) {
        sln_ipc_ser_func[i] = NULL;
    }
 
    sln_ipc_ser_func[SLN_IPC_TYPE_0x1] = sln_ipc_handle_0x1;
    sln_ipc_ser_func[SLN_IPC_TYPE_0x2] = sln_ipc_handle_0x2;
 
    return 0;
}      

之後服務程序開始監聽,等待連接配接:

#if USE_AF_UNIX
    fd = sln_ipc_ser_afunix_listen(SOCK_IPC_NAME);
    if (fd < 0) {
        return -1;
    }
#else
    fd = sln_ipc_ser_afinet_listen(SOCK_IPC_SER_LISTEN_PORT);
    if (fd < 0) {
        return -1;
    }
#endif      

服務程序接收客戶程序發送的資料,交給函數sln_ser_handle來處理:

static int sln_ipc_ser_accept(int listenfd)
{
    int                 connfd;
    ssize_t             recvlen;
    ipc_sock_msg_t      recv_msg;
    socklen_t           addrlen;
#if USE_AF_UNIX
    struct sockaddr_un  cltaddr;
#else
    struct sockaddr_in  cltaddr;
#endif
 
    addrlen = sizeof(cltaddr);
    for (;;) {
        connfd = accept(listenfd, (struct sockaddr *)&cltaddr, &addrlen);
        if (connfd < 0) {
            fprintf(stderr, "accept: %s\n", strerror(errno));
            continue;
        }
 
        if ((recvlen = sln_ipc_recv(connfd, &recv_msg, sizeof(ipc_sock_msg_t))) < 0) {
            continue;
        }
 
        sln_ser_handle(connfd, &recv_msg);
 
        close(connfd);
    }
 
    return 0;
}      

其中處理函數sln_ser_handle實作為:

sln_ser_handle(int sockfd, ipc_sock_msg_t *recv_msg)
{
    ipc_sock_msg_t  send_msg;
 
    memset(&send_msg, 0, sizeof(ipc_sock_msg_t));
 
    send_msg.msg_type = recv_msg->msg_type;
   
    if ((recv_msg->msg_type >= SLN_IPC_MAX_TYPE)
        && (recv_msg->msg_rc < 0)) {
       send_msg.msg_rc = SLN_IPC_RC_TYPE;
    } else if (NULL == sln_ipc_ser_func[recv_msg->msg_type]) {
        send_msg.msg_rc = SLN_IPC_RC_FUNC;
    } else {
        send_msg.msg_rc
            = sln_ipc_ser_func[recv_msg->msg_type](
                    recv_msg->msg_buf,
                    recv_msg->msg_buflen,
                    send_msg.msg_buf,
                    &send_msg.msg_buflen);
    }
 
    if (sln_ipc_send(sockfd, &send_msg, sizeof(ipc_sock_msg_t)) < 0) {
        return -1;
    }
 
    return 0;
}                      

用戶端程式:

int
sln_ipc_clt_conn(
        int msg_type,
        int *ret_code,
        void *sendbuf,
        int sendlen,
        void *recvbuf,
        int *recvlen)
{
    int                 connfd;
    ssize_t             ret_size;
    socklen_t           addrlen;
    ipc_sock_msg_t      send_msg, recv_msg;
 
#if USE_AF_UNIX
    if ((connfd = sln_ipc_clt_afunix_conn_init(SOCK_IPC_NAME)) < 0) {
        return -1;
    }
    addrlen = sizeof(struct sockaddr_un);
#else
    if ((connfd = sln_ipc_clt_afinet_conn_init(SOCK_IPC_SER_LISTEN_PORT)) < 0) {
        return -1;
    }
    addrlen = sizeof(struct sockaddr_in);
#endif
 
    if (connect(connfd, (struct sockaddr *)&seraddr, addrlen) < 0) {
        fprintf(stderr,  "connect: %s\n", strerror(errno));
        return -1;
    }
 
    memset(&send_msg, 0, sizeof(ipc_sock_msg_t));
    send_msg.msg_type = msg_type;
    if (NULL != sendbuf) {
        send_msg.msg_buflen = sendlen;
        memcpy(send_msg.msg_buf, sendbuf, sendlen);
    }
    if ((ret_size = ipc_send(connfd, &send_msg, 3 * sizeof(int) + sendlen)) < 0) {
        return -1;
    }
 
    if ((ret_size = ipc_recv(connfd, &recv_msg, sizeof(ipc_sock_msg_t))) < 0) {
        return -1;
    }
 
    if (recv_msg.msg_type != send_msg.msg_type) {
        printf("Error msg type!\n");
        return -1;
    }
 
    *ret_code = recv_msg.msg_rc;
    if (NULL != recvbuf) {
        *recvlen = recv_msg.msg_buflen;
        memcpy(recvbuf, recv_msg.msg_buf, recv_msg.msg_buflen);
    }
 
    return 0;
}