【问题标题】:How to create named pipe (mkfifo) in Android?如何在 Android 中创建命名管道 (mkfifo)?
【发布时间】:2011-02-13 23:34:05
【问题描述】:

我在 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. 应用程序是否需要任何额外权限?

【问题讨论】:

    标签: android android-ndk named-pipes


    【解决方案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 的文档。

    【讨论】:

    • 您可以使用TEMP_FAILURE_RETRY() 重试 EINTR。
    【解决方案2】:

    Roosmaa's answer 是正确的 -- 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;
    }
    

    【讨论】:

    • 这可能很难在 java 中复制 - 只是说:)
    • 没错,但原始代码 sn-p 也不是 Java,所以我认为这不会破坏交易。与文件系统中的命名管道相比,套接字方法当然更需要 JNI。
    • 对,目标是 C。这种方法似乎需要每个连接的客户端单独的线程。
    • Berkus:套接字调用需要字符串长度,而不是以空字符结尾的字符串,因此您需要计算它并将其传入。(它可能在空字符终止和过长的情况下工作得很好,但我相信以上是迂腐正确的方法。)
    • 自 android 5.1 以来,socket 出现了一些问题。请参阅我的问题:stackoverflow.com/questions/31068440/…
    【解决方案3】:

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

    【讨论】:

    • 可以跨多个应用程序使用 PipedInputStream 和 PipedOutputStream,例如在 c++ 中,namedPipe 可以被 2 个不同的应用程序用于读取和写入。
    • 不。它只是在进行中。
    【解决方案4】:

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

    如果您需要 IPC,最佳做法是使用 Binders

    如果只需要线程间通信,可以通过JNI使用无名管道(这样就可以了)

    【讨论】:

    • 是的,这就是我最终使用的。这里的关键是让本机代码与本机代码对话。使用 Java 会使它变得更复杂(JNI 等)并且可能更慢。 Binder 接口在 C++ 上可用,但需要完整的 Android 源代码,这是一个很大的障碍。
    • 我们在未命名管道之上使用 JNI,因为我们有 Java 代码异步向我们的本地线程发送数据;但是如果你只需要让你的本地线程在同一个进程中与其他本地线程通信,你就不需要 JNI。我应该补充一点,管道比套接字更快更轻。
    【解决方案5】:

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

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

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

    【讨论】:

      猜你喜欢
      • 2022-01-21
      • 2010-11-24
      • 1970-01-01
      • 2010-10-09
      • 2012-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多