15

我在 Android 中创建命名管道时遇到了麻烦,下面的示例说明了我的困境:

res = mkfifo("/sdcard/fifo9000", S_IRWXO);
if (res != 0)
{
    LOG("Error while creating a pipe (return:%d, errno:%d)", res, errno);
}

代码总是打印:

Error while creating a pipe (return:-1, errno:1)

我无法弄清楚为什么会失败。该应用程序具有 android.permission.WRITE_EXTERNAL_STORAGE 权限。我可以在同一位置创建具有完全相同名称的普通文件,但管道创建失败。有问题的管道应该可以从多个应用程序访问。

  1. 我怀疑没有人可以在 /sdcard 中创建管道。这样做的最佳地点在哪里?
  2. 我应该设置什么模式桅杆(第二个参数)?
  3. 应用程序是否需要任何额外的权限?
4

5 回答 5

18

Roosmaa 的回答是正确的—— mkfifo() 只是调用 mknod() 来创建一个特殊文件,而 FAT32 不支持它。

作为替代方案,您可能要考虑使用 Linux 的“抽象名称空间”UNIX 域套接字。它们应该大致相当于命名管道。您可以按名称访问它们,但它们不是文件系统的一部分,因此您不必处理各种权限问题。请注意,套接字是双向的。

由于它是一个套接字,您可能需要 INTERNET 权限。不确定。

下面是一些客户端/服务器示例代码:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stddef.h>
#include <sys/socket.h>
#include <sys/un.h>

/*
 * Create a UNIX-domain socket address in the Linux "abstract namespace".
 *
 * The socket code doesn't require null termination on the filename, but
 * we do it anyway so string functions work.
 */
int makeAddr(const char* name, struct sockaddr_un* pAddr, socklen_t* pSockLen)
{
    int nameLen = strlen(name);
    if (nameLen >= (int) sizeof(pAddr->sun_path) -1)  /* too long? */
        return -1;
    pAddr->sun_path[0] = '\0';  /* abstract namespace */
    strcpy(pAddr->sun_path+1, name);
    pAddr->sun_family = AF_LOCAL;
    *pSockLen = 1 + nameLen + offsetof(struct sockaddr_un, sun_path);
    return 0;
}

int main(int argc, char** argv)
{
    static const char* message = "hello, world!";
    struct sockaddr_un sockAddr;
    socklen_t sockLen;
    int result = 1;

    if (argc != 2 || (argv[1][0] != 'c' && argv[1][0] != 's')) {
        printf("Usage: {c|s}\n");
        return 2;
    }

    if (makeAddr("com.whoever.xfer", &sockAddr, &sockLen) < 0)
        return 1;
    int fd = socket(AF_LOCAL, SOCK_STREAM, PF_UNIX);
    if (fd < 0) {
        perror("client socket()");
        return 1;
    }

    if (argv[1][0] == 'c') {
        printf("CLIENT %s\n", sockAddr.sun_path+1);

        if (connect(fd, (const struct sockaddr*) &sockAddr, sockLen) < 0) {
            perror("client connect()");
            goto bail;
        }
        if (write(fd, message, strlen(message)+1) < 0) {
            perror("client write()");
            goto bail;
        }
    } else if (argv[1][0] == 's') {
        printf("SERVER %s\n", sockAddr.sun_path+1);
        if (bind(fd, (const struct sockaddr*) &sockAddr, sockLen) < 0) {
            perror("server bind()");
            goto bail;
        }
        if (listen(fd, 5) < 0) {
            perror("server listen()");
            goto bail;
        }
        int clientSock = accept(fd, NULL, NULL);
        if (clientSock < 0) {
            perror("server accept");
            goto bail;
        }
        char buf[64];
        int count = read(clientSock, buf, sizeof(buf));
        close(clientSock);
        if (count < 0) {
            perror("server read");
            goto bail;
        }
        printf("GOT: '%s'\n", buf);
    }
    result = 0;

bail:
    close(fd);
    return result;
}
于 2010-05-03T18:28:41.573 回答
8

/sdcard 的默认文件系统是 FAT32,不支持命名管道。

在非根设备上,您可以尝试创建这些管道的唯一可能位置是应用程序数据目录 /data/data/com.example/ 。注意:您不应该对该值进行硬编码,请使用 Context.getApplicationInfo().dataDir 。

但请注意,无论何时用户使用 Apps2SD 或 Google 正式实施该支持时,您都需要确保让用户知道该应用程序不能存储在 vfat 文件系统上。

于 2010-05-03T12:42:03.873 回答
1

还有/sqlite_stmt_journals(我们用它来测试,我不知道这个目录在操作系统更新后还能存活多久)

如果您需要 IPC,最佳实践是使用Binder

如果只需要线程间通信,可以通过 JNI 使用未命名管道(这样可以正常工作)

于 2010-05-07T09:58:05.317 回答
1

我想附加到接受的答案:

1) 我可以使用这种方法在 Android 应用程序的两个本机模块之间连接套接字。

2)write()应该在一个循环中,因为它可能不会在第一次写入请求的全部金额。例如,它应该是这样的:

void *p = buffer;
count = 0;
while ((count += write(clientSock, buffer, num_bytes - count)) < num_bytes)
{
    if (count < 0)
    {
        close(clientSock);
        errCode = count;
        break;
    }
    p += count;
}

上面显示的错误处理是不够的,因为几个错误代码只是表示要重试。请参阅write的文档。

于 2015-06-10T14:22:37.117 回答
-1

如果您使用 Java 进行编码,您应该只使用PipedInputStreamPipedOutputStream

于 2010-05-08T08:59:25.603 回答