Android Framework 的 Native 层很多运用了 Unix domain scoket。Unix domain socket 又叫 IPC(inter-process communication 进程间通讯) socket,用于完成同一主机上的进程间通讯。socket 原本是为网络通讯设计的,但后来在 socket 的结构上发展出一种 IPC 机制,便是 UNIX domain socket。尽管网络 socket 也可用于同一台主机的进程间通讯(通过 loopback 地址 127.0.0.1),但是 UNIX domain socket 用于 IPC 更有效率:不需要通过网络协议栈,不需要打包拆包、核算校验和、维护序号和应答等,仅仅将应用层数据从一个进程拷贝到另一个进程。这是因为,IPC 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。

TCP 相关API

TCP 程序整体结构:

Unix domain scoket 运用解析之 TCP 篇

Server 端流程:

  • 构建 socket
  • bind
  • listen
  • accept
  • send/recv
  • close

Client 端流程:

  • Socket
  • bind
  • connect
  • send/recv
  • close

构建 socket

#include <sys/types.h>
#include <sys/socket.h>
int socket (int domain, int type, int protocol)
  • domain 指定Socket类型,AF_INET,AF_INET6,AF_UNIX,分别对应 ipv4、ipv6 和 Unix Domain Socket
  • type 可以选择 SOCK_DGRAM 或 SOCK_STREAM。SOCK_STREAM 意味着会供给按次序的、可靠、双向、面向衔接的比特流。SOCK_DGRAM 意味着会供给定长的、不可靠、无衔接的通讯。
  • protocol 参数指定为 0 即可

对于 Unix Domain Socket,一般这样初始化:

int fd = socket(AF_UNIX,SOCK_DGRAM,0);

bind

收发数据之前,需要对 socket 进行绑定操作,所谓绑定操作,便是让 socket fd 和一个地址相关联:

int bind (int fd, CONST_SOCKADDR_ARG addr, socklen_t len)
  • fd 是构建 socket 过程中回来的文件描述符
  • addr 指定了地址,它是 sockaddr_un 类型,在 sys/un.h 头文件中界说
  • len 用于指定 addr 的长度

addr 数据结构界说如下:

struct sockaddr_un{
	sa_family_t sun_family;   // always AF_UNIX
	char sun_path[128];  
};

用法如下:

char* server_file = "server.sock";
struct sockaddr_un addr;
memset(&addr,0,sizeof(addr));
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path,server_file);
if (access(addr.sun_path,0) != -1)
{
    remove(addr.sun_path);
}
 if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
{
    perror("bind");
    return -1;
}

这里有一个细节:假如绑定的文件已经存在,要删掉它,不然bind不会成功的

listen

int listen (int fd, int n)

listen 通常又服务器调用,用于与衔接前的预备。

  • fd 便是 socket 的描述符
  • n 便是最大的衔接数量

accept

int accept (int fd, SOCKADDR_ARG addr,
		   socklen_t *addr_len)

listen 是为衔接做预备,而 accept 则会等候新的衔接,假如有新的衔接,那就会回来一个新的衔接的文件描述符。衔接成功后,会将客户端的地址填充到 addr 的结构体上,字节大小填充到 addr_len。

读操作 recv

#include <sys/types.h>
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

读写的字节由 len 决定,读取成功后,将内容存放在 buf 傍边。假如读取成功则回来接收到的字节数量。假如产生异常,则回来 -1,通常用回来值判断衔接是否正常。

写操作 send

#include <sys/types.h>
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

要发送的信息存放在 buf 傍边,本次要发送的字节数量由 len 决定。

关闭 socket

int close(int fd);

客户端衔接服务端

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);

connect 是客户端专用,用于衔接 server。connect 异常时回来-1,不然回来 0。地址在 addr 中指明。

用法如下:

struct sockaddr_un serveraddr;
socklen_t addrlen = sizeof(serveraddr);
memset(&serveraddr,0,addrlen);
serveraddr.sun_family = AF_UNIX;
strcpy(serveraddr.sun_path,server_file);
int newcon = -1;
newcon = connect(socketfd,(sockaddr*)&serveraddr,addrlen);

示例代码

服务端代码:

// tcpserver.cpp
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <memory.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;
char* server_file = "/tmp/localserver.sock";
int main(int argc,char** argv)
{
    int socketfd = socket(AF_UNIX,SOCK_STREAM,0);
    if ( socketfd < 0)
    {
        perror("socket");
        return -1;
    }
    struct sockaddr_un addr;
    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,server_file);
    if (access(addr.sun_path,0) != -1)
    {
        remove(addr.sun_path);
    }
    if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("bind");
        return -1;
    }
    if (listen(socketfd,12) < 0)
    {
        perror("listen");
        return -1;
    }
    struct sockaddr_un clientaddr;
    socklen_t addrlen = sizeof(clientaddr);
    char buf[128];
    int newcon = -1;
    newcon = accept(socketfd,(sockaddr*)&clientaddr,&addrlen);
    if (newcon > 0)
    {
        int curindex = 0;
        int edgeindex = 0;
        while (1)
        {
            if (recv(newcon,buf,sizeof(buf),0) == -1)
            {
                perror("server recv:");
                break;
            }
            cout << "server recv a message:" << buf << endl;
            memset(buf,0,sizeof(buf));
            if (send(newcon,"ok",2,0) < 0)
            {
                perror("server send:");
                break;
            }
        }
    }
    close(newcon);
    close(socketfd);
    return 0;
}

客户端代码:

// tcpclient.cpp 
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <memory.h>
#include <unistd.h>
#include <stdio.h>
using namespace std;
char* server_file = "/tmp/localserver.sock";
char* client_file = "/tmp/localclient.sock";
int main(int argc,char** argv)
{
    int socketfd = socket(AF_UNIX,SOCK_STREAM,0);
    if ( socketfd < 0)
    {
        perror("client socket");
        return -1;
    }
    struct sockaddr_un addr;
    memset(&addr,0,sizeof(addr));
    addr.sun_family = AF_UNIX;
    strcpy(addr.sun_path,client_file);
    if (access(addr.sun_path,0) != -1)
    {
        remove(addr.sun_path);
    }
    if (bind(socketfd,(struct sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("client bind");
        return -1;
    }
    struct sockaddr_un serveraddr;
    socklen_t addrlen = sizeof(serveraddr);
    memset(&serveraddr,0,addrlen);
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path,server_file);
    char buf[128];
    int newcon = -1;
    newcon = connect(socketfd,(sockaddr*)&serveraddr,addrlen);
    if (newcon < 0)
    {
        perror("client connect");
    }
    int curindex = 0;
    int edgeindex = 0;
    int index = 0;
    while (index < 3)
    {
        string temp = "client for test:";
        temp.append(to_string(index));
        if (send(socketfd,temp.c_str(),temp.capacity(),0) < 0)
        {
            perror("client send:");
            break;
        }
        if (recv(socketfd,buf,sizeof(buf),0) == -1)
        {
            perror("client recv:");
            break;
        }
        cout << "client recv:" << buf << endl;
        memset(buf,0,sizeof(buf));
        index++;
        sleep(1);
    }
    close(socketfd);
    return 0;
}

参考资料

关于

我叫阿豪,2015 年本科结业于国防科学技术大学指挥信息系统专业,结业后从事信息化装备的研制作业,作业内容首要触及 Android Framework 与 Linux Kernel。

假如你对 Android Framework 感兴趣或许正在学习 Android Framework,可以重视我的微信公众号和抖音,我会继续共享我的学习经历,帮助正在学习的你少走一些弯路。学习过程中假如你有疑问或许你的经历想要共享给大家可以添加我的微信,我拉你进技术交流群。

Unix domain scoket 运用解析之 TCP 篇

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。