本地套接字(Unix domain socket IPC)

1、基础

虽然网络socket也可用于同一台主机的进程间通讯(通过lo地址127.0.0.1),但是unix domain socket用于IPC更有效率:不需要经过网络协议栈,不需要打包拆包/计算校验和/维护信号和应答等。只是将应用层数据从一个进程拷贝到另一个进程。这是因为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、TCP与UDP

unix domain中tcp与udp通信方式区别:

1)TCP面向流,数据分包、连包,UDP面向消息,不分包。

2)TCP必须连接后才能收发数据,UDP直接发送。

3)TCP不必记录客户端地址,直接收发数据,而UDP必须记录客户端地址后向具体地址发送数据。

4)通信流程不同。

 

服务端:

(1)TCP连接在 bind() 之后需要 listen() ,而UDP不需要。

(2)TCP连接在 bind() 和 listen() 后还需要 accept(),用来得到客户端连接描述符,而UDP不需要。

(3)收发数据,TCP使用 recv() , send() , 而 UDP 使用 recvfrom(), sendto()。

(4)释放连接,TCP在客户端释放连接后需先释放 accept() 得到的客户端连接描述符,再 close(socket) ,而UDP直接 close(socket)。

客户端:
(1)TCP连接在bind()后需要 connect(),而UDP不需要。

(2)收发数据,TCP使用 recv() , send() , 而 UDP 使用 recvfrom(), sendto()。

4、应用

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回显且小写变大写功能。

TCP server create封装

int create_unixdomain(int type, const char *unix_file)
{
    if(unix_file == NULL)
    {
        printf("unix domain file is NULL!\n");
        return -1;
    }
        
    struct sockaddr_un un;
    int sd = 0, ret = 0;
    socklen_t len = 0;
    
    sd = socket(AF_UNIX, type, 0);
    if(sd < 0)
    {
        printf("failed to create socket(unix domain)!\n");
        return -1;
    }
    
    fcntl(sd, F_SETFL, O_NONBLOCK | fcntl(sd, F_GETFL));
    memset(&un, 0, sizeof(struct sockaddr_un));
    un.sun_family = AF_UNIX;
    strcpy(un.sun_path, unix_file);
    unlink(unix_file);
    
    len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
    ret = bind(sd, (struct sockaddr *)&un, len);
    
    if(ret < 0)
    {
        printf("failed to bind(unix domain)!\n");
        return -1;
    }

    ret = listen(sd, 1024);
    if(ret < 0)
    {
        printf("failed to listen(unix domain)!\n");
        return -1;
    }
    
    return sd;
}

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

5、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;
}

 

参考:

1. Linux 多进程通信开发之 UNIX domain Socket 通信机制从 TCP 切换为 UDP

posted @ 2015-11-28 16:48  yuxi_o  阅读(2297)  评论(0编辑  收藏  举报