如何在 Android 中创建命名管道 (mkfifo)?

发布于 2024-08-30 23:55:23 字数 534 浏览 8 评论 0原文

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

I am having trouble in creating named pipe in Android and the example below illustrates my dilemma:

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

The code always prints:

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

I can't figure out exactly why this fails. The application has android.permission.WRITE_EXTERNAL_STORAGE permissions. I can create normal files with exactly the same name in the same location, but pipe creation fails. The pipe in question should be accessible from multiple applications.

  1. I suspect that noone can create pipes in /sdcard. Where would it be the best location to do so?
  2. What mode mast should I set (2nd parameter)?
  3. Does application need any extra permissions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

凡尘雨 2024-09-06 23:55:23

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;
}

Roosmaa's answer is correct -- mkfifo() just calls mknod() to create a special file, and FAT32 doesn't support that.

As an alternative you may want to consider using Linux's "abstract namespace" UNIX-domain sockets. They should be roughly equivalent to a named pipe. You can access them by name, but they're not part of the filesystem, so you don't have to deal with various permission issues. Note the socket is bi-directional.

Since it's a socket, you may need INTERNET permission. Not sure about that.

Here's a quick bit of client/server sample code:

#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;
}
凑诗 2024-09-06 23:55:23

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

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

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

The default filesystem of /sdcard is FAT32, which doesn't support named pipes.

On a non-rooted device the only possible place you could try to create those pipes would be the application data directory /data/data/com.example/ .
Note: You shouldn't hardcode that value, use the Context.getApplicationInfo().dataDir .

But be aware that whenever the user is using Apps2SD or whenever Google implements that support officially you need to make sure to let the user know that the app can't be stored on vfat files system.

好多鱼好多余 2024-09-06 23:55:23

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

如果你需要 IPC,最好的做法是使用 Binders

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

there's also /sqlite_stmt_journals (we use it for testing, I don't know how long this directory will survive OS updates)

If you need IPC, the best practices are to use the Binders

If you only need inter-thread communication, you can use unnamed pipes through JNI (this works fine)

踏雪无痕 2024-09-06 23:55:23

我想附加到已接受的答案:

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 的文档。

I would like to append to the accepted answer:

1) I am able to use this method to connect a socket between two native modules of an Android app.

2) write() should be in a loop since it may not write the full amount requested in the first go. For example, it should read something like:

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;
}

The error handling shown above is insufficient, since several error codes simply indicate to try again. See the documentation for write.

初吻给了烟 2024-09-06 23:55:23

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

If you're coding this in Java, you should just use PipedInputStream and PipedOutputStream.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文