socket IPC(本地套接字Domain)

UNIX Domain Socket的特点

1.UNIX Domain Socket为什么比TCP/IP在本地上通信更加快
因为UNIX Domain Socket不经过网络协议栈 / 不需要打包拆包 / 计算校验和 / 维护序号和应答,只是将应用层数据从一个进程拷贝到另一个进程
2.UNIX Domain Socket也提供面向流和面向数据包的两种API接口,类似于TCP和UDP,但是面向消息的 UNIX Domain Socket也是可靠的,消息既不会丢失也不会失序。
3.全双工
4.目前UNIX Domain Socket已经成为最广泛的IPC机制

Unix 本地套接字 API

1.socket创建套接字

mysocket = socket(int socket_family, int socket_type, int protocol)

网络socket UNIX Domain Socket
address family AF_INET或PF_INET AF_UNIX
地址格式不同 sockaddr_in(IP+Port) sockaddr_un(本地文件)
type SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW
Protocol 0 0

2.绑定地址bind

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
my_addr参数

  • struct sockaddr*
  • struct sockaddr_in*
  • struct sockaddr_un*
    对于 Unix 本地套接字来说,绑定的地址就不是原来的“IP地址 + 端口号”了,而是一个有效的路径。
    socket IPC(本地套接字Domain)
    结论:Unix 本地套接字的地址结构体中包含两个成员,其中 sun_family 表示协议族,填 AF_LOCAL 或 AF_UNIX 即可;sun_path 表示一个路径名。
    补充:路径名,其实还要分为两种,普通路径名 / 抽象路径名。
    • 普通路径名是一个正常的字符串,也就是说,sun_path 字段是以空字符(’\0’)结尾的。
    • 而抽象路径名,sun_path 字段的第一个字节需要设置成 NULL(’\0’),所以在计算抽象路径名的长度的时候就要特别小心了,否则在解析抽象路径名时就有可能出现异常情况,因为抽象路径名不是像解析普通路径名那样,解析到第一个 NULL 就可以停止了
        使用抽象路径名的好处
        1.因为不会再在文件系统中创建文件了,就不需要担心与文件系统中已存在的文件产生名字冲突
        2.不需要在使用完套接字之后删除,当套接字被关闭之后会自动删除这个抽象名

3.其他API

其他的一些 API,比如 listen()、accept()、connect(),以及数据通信用的 read()、write()、recv()、send()、recvfrom()、sendto()、recvmsg()、sendmsg(),用法跟网络 socket 基本一样,主要是地址结构体需要注意一下

4.常用API

1)用于创建类似于无名管道(pipe)的本地套接字

int socketpair(int domain, int type, int protocol, int sv[2]);

2)当不再需要这个 Unix 域套接字时,应删除路径名对应的文件

int unlink(const char *pathname);
int remove(const char *pathname);
注意,如果是抽象路径名,就不需要在使用完本地套接字后手动删除对应的套接字文件,因为当本地套接字被关闭之后,内核会自动删除这个抽象名。

3)获取本地套接字的地址信息

int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);


TCP(字节流套接字)

【unixstr_serv.c】

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

#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256

int main(void)
{
    int listenfd, connfd;
    socklen_t len;
    struct sockaddr_un servaddr, cliaddr;

    if(-1 == (listenfd = socket(AF_LOCAL, SOCK_STREAM, 0)))
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    unlink(UNIXSTR_PATH);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);
    if(-1 == bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)))
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    listen(listenfd, LISTENQ);

    len = sizeof(cliaddr);

    if(-1 == (connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len)))
    {
        perror("accept");
        exit(EXIT_FAILURE);
    }

    char buf[BUFFER_SIZE];

    while(1)
    {
        bzero(buf, sizeof(buf));
        if(read(connfd, buf, BUFFER_SIZE) == 0) break;
        printf("Receive: %s", buf);
    }

    close(listenfd);
    close(connfd);
    unlink(UNIXSTR_PATH);

    return 0;
}

【unixstr_cli.c】

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

#define UNIXSTR_PATH "/tmp/unix.str"
#define LISTENQ 5
#define BUFFER_SIZE 256

int main(void)
{
    int sockfd;
    struct sockaddr_un servaddr;

    sockfd = socket(AF_LOCAL, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXSTR_PATH);

    connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    char buf[BUFFER_SIZE];

    while(1)
    {
        bzero(buf, sizeof(BUFFER_SIZE));
        printf(">> ");
        if(fgets(buf, BUFFER_SIZE, stdin) == NULL)
        {
            break;
        }
        write(sockfd, buf, strlen(buf));
    }

    close(sockfd);

    return 0;
}

UDP(数据报套接字)

1)类似于上面 TCP 的例子,我们使用绝对路径名”/tmp/unix.dg”来实现一个数据报的本地套接字,一端接收数据,一端发送数据。
2)需要注意的是,跟网络套接字不一样,对于 Unix 域套接字的 UDP 客户端,必须 bind 一个路径名到 UDP 套接字,以使得 UDP 服务器有发送应答的目的地。
【unixdg_serv.c】

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

#define UNIXDG_PATH "/tmp/unix.dg"
#define BUFFER_SIZE 256

int main(void)
{
    int sockfd;
    struct sockaddr_un servaddr, cliaddr;

    sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);

    unlink(UNIXDG_PATH);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);

    bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));

    socklen_t len = sizeof(cliaddr);
    char buf[BUFFER_SIZE];

    while(1)
    {
        bzero(buf, BUFFER_SIZE);

        if(0 == recvfrom(sockfd, buf, BUFFER_SIZE, 0, (struct sockaddr *)&cliaddr, &len))
        {
            break;
        }
        printf("recvfrom: %s", buf);
    }

    close(sockfd);
    unlink(UNIXDG_PATH);

    return 0;
}

【unixdg_cli.c】

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

#define UNIXDG_PATH "/tmp/unix.dg"
#define BUFFER_SIZE 256

int main(void)
{
    int sockfd;
    struct sockaddr_un servaddr, cliaddr;
    socklen_t len;

    sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);

    /* local address */
    bzero(&cliaddr, sizeof(cliaddr));
    cliaddr.sun_family = AF_LOCAL;
    strcpy(cliaddr.sun_path, UNIXDG_PATH);
    bind(sockfd, (struct sockaddr *)&cliaddr, sizeof(cliaddr));

    /* remote address */
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sun_family = AF_LOCAL;
    strcpy(servaddr.sun_path, UNIXDG_PATH);
    len = sizeof(servaddr);

    char buf[BUFFER_SIZE];

    while(1)
    {
        bzero(buf, BUFFER_SIZE);

        printf(">> ");
        if(fgets(buf, BUFFER_SIZE, stdin) == NULL)
        {
            break;
        }
        sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&servaddr, len);
    }

    close(sockfd);

    return 0;   
}