V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
howellz
V2EX  ›  Android

记一次 Android 关于 Unix abstract socket 的连接问题

  •  
  •   howellz · 2020-12-23 19:43:12 +08:00 · 4311 次点击
    这是一个创建于 1237 天前的主题,其中的信息可能已经有所发展或是发生改变。

    Keywords

    Android Unix abstract namespace socket LocalSocket LocalServerSocket length 地址长度

    正文

    在一个项目中,需要 native 的程序对 java 的LocalServerSocket发起连接。即 Java 端监听某个 Unix 套接字,等待 native 的程序连接。结果死活连不上,总是提示Connection Refused(110)

    这是 Java 端的代码示意,非常简单:

    public void listen() {
        String name = "myname";
        mSocket = new LocalServerSocket(name);
        LocalSocket client = mSocket.accept();
        ...
    }
    

    发现客户端如果用 java 代码连接很容易就连接上了:

    public void connect() {
        String name = "myname";
        LocalSocket client = new LocalSocket();
        client.connect(new LocalSocketAddress(name));
        Log.d("client", "connected to " + name);
    }
    

    但是客户端如果使用如下的 c 代码就连接不上:

    #define SOKET_NAME "@myname"
    void connect() {
        char* name = SOKET_NAME;
        struct sockaddr_un addr;
        int sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock < 0) {
            perror("failed to create socket");
            return -1;
        }
    
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOKET_NAME, sizeof(addr.sun_path)-1);
        if (name[0] == '@') 
           addr.sun_path[0] = '\0';
    	
        if (connect(sock, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
            perror("failed to connect");
            close(sock);
            return -1;
        }
        ...
    }
    

    这段代码主要参考权威 man UNIX(7): https://man7.org/linux/man-pages/man7/unix.7.html

    注意,这个 SOCKET_NAME 中的第一个字符 @是一个占位符,其实后面被'\0'取代了,符合abstract unix socket的地址要求。

    一直输出:

    failed to connect: Connection refused
    

    同样的代码在普通的 Linux 系统上就可以正常工作,百思不得其姐姐。最后查看LocalSocket的实现代码,发现在 Android 的LocalSocket实现中,使用了如下的代码来确定 address 的长度:

    *alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1;
    

    这样看来,** 直接传递sizeof(struct sockaddr_un)作为地址长度是不合适的,应该传递的长度不包括结构体中 sun_path[]数组后的全零 padding 。**

    修改代码如下:

    #define SOKET_NAME "@myname"
    void connect() {
        char* name = SOKET_NAME;
        struct sockaddr_un addr;
    
        // add this variant
        int alen;
    
        int sock = socket(AF_UNIX, SOCK_STREAM, 0);
        if (sock < 0) {
            perror("failed to create socket");
            return -1;
        }
    
        memset(&addr, 0, sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, SOKET_NAME, sizeof(addr.sun_path)-1);
        if (name[0] == '@') 
            addr.sun_path[0] = '\0';
    
        // Added this line 
        alen = offsetof(struct sockaddr_un, sun_path) + strlen(name);
    	
        // change sizeof(addr) to alen
        if (connect(sock, (struct sockaddr *)&addr, alen) < 0) {
            perror("failed to connect");
            close(sock);
            return -1;
        }
    }
    

    再次运行,可以连接上了。

    对于 Android 的实现,可以参考如下代码: https://android-opengrok.bangnimang.net/android-9.0.0_r61/xref/system/core/libcutils/socket_local_client_unix.cpp?r=db87e6d1#111

    1 条回复    2020-12-24 12:49:37 +08:00
    tkl
        1
    tkl  
       2020-12-24 12:49:37 +08:00
    分析的可以
    另 百思不得其姐姐?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2927 人在线   最高记录 6547   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 15:26 · PVG 23:26 · LAX 08:26 · JFK 11:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.