记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