Unix Domain Scoket 使用解析之 UDP 篇

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 机制本质上是可靠的通讯,而网络协议是为不可靠的通讯设计的。

UDP 相关API

整体框架:

无论是 Server 还是 Client,流程一致:

  • 构建 socket
  • bind
  • sendto/recvfrom
  • close

构建 socket

cpp 复制代码
#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,一般这样初始化:

cpp 复制代码
int fd = socket(AF_UNIX,SOCK_DGRAM,0);

bind

收发数据之前,需要对 socket 进行绑定操作:

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

addr 数据结构定义如下:

cpp 复制代码
struct sockaddr_un{
	sa_family_t sun_family;   // always AF_UNIX
	char sun_path[128];  
};

用法如下:

cpp 复制代码
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);
bind(fd,(sockaddr*)&addr,sizeof(addr));

对于 UDP,不需要监听和接受连接,直接读写数据:

写数据:

cpp 复制代码
ssize_t sendto(int fd, const void *buf, size_t n,
		       int flags, CONST_SOCKADDR_ARG addr,
		       socklen_t addr_len)
  • fd socket 的文件描述符
  • buf 存了要发送的消息
  • n 要发送的消息的容量
  • flags 操作位,一般是 0
  • addr 要发送的对象的地址
  • addr_len addr 的字节大小

如果写入成功就返回实际的消息字节数量,否则返回 -1,使用方法如下:

cpp 复制代码
char *p = "OK,I got id!";
//clientaddr 要指定发送对象的地址
int ssize = sendto(fd,p,strlen(p),0,(sockaddr*)&clientaddr,len);
if (ssize < 0)
{
    perror("sendto");
    return -1;
}

读信息:

cpp 复制代码
ssize_t recvfrom (int fd, void *restrict buf, size_t n,
			 int flags, SOCKADDR_ARG addr,
			 socklen_t *restrict addr_len)
  • fd socket的文件描述符
  • buf 用来存放接收到的新消息
  • n 接收到的信息的大小
  • flags 操作位,取О就好
  • addr 用来存放消息发送者的地址,是指针类型
  • addr_len addr的字节大小,注意的是它也是指针类型

用法如下:

cpp 复制代码
struct sockaddr_un clientaddr;
socklen_t len = sizeof(clientaddr);

char msgrecv[1024];

memset(msgrecv,'\0',1024);
int size = recvfrom(fd,msgrecv,sizeof(msgrecv),0,(sockaddr*)&clientaddr,&len);
if (size < 0)
{
    perror("recv");
    return -1;
}

关闭 Socket

cpp 复制代码
int close (int fd)

这个没什么好说的

示例代码

cpp 复制代码
//服务端
// updserver.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <iostream>
#include <unistd.h>

using namespace std;


char* server_file = "server.sock";

int main(int argc,char** argv)
{
    int fd = socket(AF_UNIX,SOCK_DGRAM,0);

    if (fd < 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(fd,(sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("bind");
        return -1;
    }


    struct sockaddr_un clientaddr;
    socklen_t len = sizeof(clientaddr);

    char msgrecv[1024];

    

    while (1)
    {
        memset(msgrecv,'\0',1024);
        int size = recvfrom(fd,msgrecv,sizeof(msgrecv),0,(sockaddr*)&clientaddr,&len);
        if (size < 0)
        {
            perror("recv");
            return -1;
        }


        cout << "I'm server,receive a msg: " << msgrecv  << " from: " << clientaddr.sun_path << endl;

        if (strncmp("quit",msgrecv,4) == 0)
        {
            cout << "Server is exiting!" << endl;
            break;
        }

        char *p = "OK,I got id!";
        int ssize = sendto(fd,p,strlen(p),0,(sockaddr*)&clientaddr,len);
        if (ssize < 0)
        {
            perror("sendto");
            return -1;
        }

        sleep(1);
    }


    if (close(fd) < 0)
    {
        perror("close");
        return -1;
    }

    return 0;
    
}

// 客户端
// updclient.cpp
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <sys/un.h>
#include <iostream>
#include <unistd.h>

using namespace std;


char* server_file = "server.sock";
char* client_file = "client.sock";

int main(int argc,char** argv)
{
    int fd = socket(AF_UNIX,SOCK_DGRAM,0);

    if (fd < 0)
    {
        perror("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(fd,(sockaddr*)&addr,sizeof(addr)) < 0)
    {
        perror("bind");
        return -1;
    }


    struct sockaddr_un clientaddr;
    socklen_t len = sizeof(clientaddr);

    char msgrecv[1024];
    struct sockaddr_un serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sun_family = AF_UNIX;
    strcpy(serveraddr.sun_path,server_file);

    

    char *p = "Hello,how are you?";
    int ssize = sendto(fd,p,strlen(p),0,(sockaddr*)&serveraddr,len);
    if (ssize < 0)
    {
        perror("sendto");
        return -1;
    }

    int size = recvfrom(fd,msgrecv,sizeof(msgrecv),0,(sockaddr*)&serveraddr,&len);
    if (size < 0)
    {
        perror("recv");
        return -1;
    }

    cout << "I'm client,receive a msg :" << msgrecv << endl;

    sleep(2);

    char* goodbye = "quit";

    if (sendto(fd,goodbye,strlen(goodbye),0,(sockaddr*)&serveraddr,len) < 0)
    {
        perror("sendto");
        return -1;
    }



    if (close(fd) < 0)
    {
        perror("close");
        return -1;
    }

    return 0;
    
}

参考资料

关于

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

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

相关推荐
alexhilton11 小时前
Room Fts4:全文搜索替代 SQL LIKE
android·kotlin·android jetpack
hai_android17 小时前
把 Android App 打包成系统应用(System App)
android·kotlin·android jetpack
JMchen17 小时前
【Jetpack 内核实战 01】开篇:Jetpack 组件全家桶核心逻辑与专栏完整规划
android·kotlin·android jetpack
骑着蜗牛撵大象32719 小时前
Linux 进程间通信之管道:从 pipe() 的内核语义到 FIFO 落地排错
android·java·linux·系统编程·fifo·管道·进程间通信
hunterandroid19 小时前
Retrofit 网络请求的异常处理与重试策略实战
android
sejinan20 小时前
极致轻量与秒级起播:NativeWasmTv 开源硬核技术解析与升级版体验
android·开源·webview·tv
小羊没烦恼!20 小时前
Ajax与JSON的一些总结
android·java·前端·数据库·mysql·算法·okhttp
mmsx20 小时前
Android 静默失败实录:五个没有日志的线上事故
android·kotlin
hai_android21 小时前
从零实现一个支持跨进程(IPC)的自定义 Toast
android·kotlin·android jetpack
龙腾-虎跃21 小时前
基于 SIP 协议的自研电话终端:FreeSWITCH 语音与视频通信实战
android·音视频·freeswitch·sip协议终端·wertc