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

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

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

上一篇:java nio之selector源码探究


下一篇:JVM学习笔记