最近做项目,遇到一个需要通过Localsocket通信的问题,实现Library建立local socket服务端,APK实现Local socket客户端,以前自己也做过,直接在网上找的代码,客户端Java,服务器端C,没有问题。这次客户端还是照着之前的做,服务器端同时做的,然后怎么都连接不上,发现他建立Local socket的方式跟我以前的不一样,然后仔细研究了一下才发现,Local socket的实现方式也分好几种情况。
下面就Android App和Linux中关于Local socket的定义分别介绍。
先看看关于LocalSocketAddress的两个构造函数:
/**
* The namespace that this address exists in. See also
* include/cutils/sockets.h ANDROID_SOCKET_NAMESPACE_*
*/
public enum Namespace {
/** A socket in the Linux abstract namespace */
ABSTRACT(0),
/**
* A socket in the Android reserved namespace in /dev/socket.
* Only the init process may create a socket here.
*/
RESERVED(1),
/**
* A socket named with a normal filesystem path.
*/
FILESYSTEM(2);
/** The id matches with a #define in include/cutils/sockets.h */
private int id;
Namespace (int id) {
this.id = id;
}
/**
* @return int constant shared with native code
*/
/*package*/ int getId() {
return id;
}
public LocalSocketAddress(String name, Namespace namespace) {
this.name = name;
this.namespace = namespace;
}
public LocalSocketAddress(String name) {
this(name,Namespace.ABSTRACT);
}
可以看到NameSpace有三种:
ABSTRACT:在系统中维护一个socket描述符,不会在系统上建立文件。
FILESYSTEM:系统会在用户指定的路径下创建一个描述符文件。
RESEVED:系统保留,其本质也是FILESYSTEM,只是文件在/dev/socket/目录下,并且只有init进程才有权限创建
可以看到,如果我们直接用LocalSocketAddress(String name);来指定一个socket的话,那么系统默认它是一个ABSTRACT类型的Local socket。
public LocalServerSocket(String name) throws IOException
{
impl = new LocalSocketImpl();
impl.create(true);
localAddress = new LocalSocketAddress(name);
impl.bind(localAddress);
impl.listen(LISTEN_BACKLOG);
}
/**
* Create a LocalServerSocket from a file descriptor that's already
* been created and bound. listen() will be called immediately on it.
* Used for cases where file descriptors are passed in via environment
* variables
*
* @param fd bound file descriptor
* @throws IOException
*/
public LocalServerSocket(FileDescriptor fd) throws IOException
{
impl = new LocalSocketImpl(fd);
impl.listen(LISTEN_BACKLOG);
localAddress = impl.getSockAddress();
}
private static final String ANDROID_SOCKET_ENV = "ANDROID_SOCKET_zygote";
String env = System.getenv(ANDROID_SOCKET_ENV);
fileDesc = Integer.parseInt(env);
sServerSocket = new LocalServerSocket(createFileDescriptor(fileDesc));
/**
* Creates a AF_LOCAL/UNIX domain stream socket.
*/
public LocalSocket() {
this(new LocalSocketImpl());
isBound = false;
isConnected = false;
}
/**
* Creates a AF_LOCAL/UNIX domain stream socket with FileDescriptor.
* @hide
*/
public LocalSocket(FileDescriptor fd) throws IOException {
this(new LocalSocketImpl(fd));
isBound = true;
isConnected = true;
}
/**
* for use with AndroidServerSocket
* @param impl a SocketImpl
*/
/*package*/ LocalSocket(LocalSocketImpl impl) {
this.impl = impl;
this.isConnected = false;
this.isBound = false;
}
public void connect(LocalSocketAddress endpoint) throws IOException {
synchronized (this) {
if (isConnected) {
throw new IOException("already connected");
}
implCreateIfNeeded();
impl.connect(endpoint, 0);
isConnected = true;
isBound = true;
}
}
A UNIX domain socket address is represented in the following
structure:
#define UNIX_PATH_MAX 108
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[UNIX_PATH_MAX]; /* pathname */
};
Linux通过sockaddr_un来指定特定的socket,下面是关于socket的说明:
* pathname: a UNIX domain socket can be bound to a null-terminated filesystem pathname using bind(2). When the address of a pathname socket is returned (by one of the system calls noted above), its length is offsetof(struct sockaddr_un, sun_path) + strlen(sun_path) + 1 and sun_path contains the null-terminated pathname. (On Linux, the above offsetof() expression equates to the same value as sizeof(sa_family_t), but some other implementations include other fields before sun_path, so the offsetof() expression more portably describes the size of the address structure.) For further details of pathname sockets, see below. * unnamed: A stream socket that has not been bound to a pathname using bind(2) has no name. Likewise, the two sockets created by socketpair(2) are unnamed. When the address of an unnamed socket is returned, its length is sizeof(sa_family_t), and sun_path should not be inspected. * abstract: an abstract socket address is distinguished (from a pathname socket) by the fact that sun_path[0] is a null byte ('\0'). The socket's address in this namespace is given by the additional bytes in sun_path that are covered by the specified length of the address structure. (Null bytes in the name have no special significance.) The name has no connection with filesystem pathnames. When the address of an abstract socket is returned, the returned addrlen is greater than sizeof(sa_family_t) (i.e., greater than 2), and the name of the socket is contained in the first (addrlen - sizeof(sa_family_t)) bytes of sun_path. The abstract socket namespace is a nonportable Linux extension.简单来说,"Pathname"方式在sun_path变量中指定路径名字,"abstract"方式sun_path的首位一定要是’\0',并且按照它要求的特定方式设置path,“unamed”方式不需要指定名字。
“Pathname”方式对于android中的FILESYSTEM, "abstract"对于android中的“ABSTRACT”,“unamed”非命名的应该是在最开始的时候就创建一对socket,然后利用这一对socket来通信。
对于跨平台的socket通信开发,一定要指定相同的LocalSocket方式,否则通信不会成功。对于相同的平台,可以不用指定,都是用系统默认的方式大部分时候也是没有问题的。