Как создать именованный канал (mkfifo) в Android?

У меня возникли проблемы с созданием именованного канала в 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. Я могу создать нормальные файлы с точно таким же именем в одном месте, но создание труб завершается неудачно. Труба, о которой идет речь, должна быть доступна из нескольких приложений.

  • Я подозреваю, что никто не может создавать каналы в /sdcard. Где это было бы лучшим местом для этого?
  • Какую настройку мачты следует установить (2-й параметр)?
  • Требуется ли приложению дополнительные разрешения?

Ответ 1

Ответ 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;
}

Ответ 2

По умолчанию файловая система /sdcard - это FAT32, которая не поддерживает именованные каналы.

На ненагруженном устройстве единственным возможным местом, где вы могли бы попытаться создать эти каналы, будет каталог данных приложения /data/data/com.example/. Примечание. Вы не должны жестко кодировать это значение, используйте Context.getApplicationInfo(). DataDir.

Но имейте в виду, что всякий раз, когда пользователь использует Apps2SD или всякий раз, когда Google официально реализует эту поддержку, вам необходимо убедиться, что пользователь не знает, что приложение не может быть сохранено в системе файлов vfat.

Ответ 3

там также /sqlite_stmt_journals (мы используем его для тестирования, я не знаю, как долго этот каталог выдержит обновления ОС)

Если вам нужен IPC, лучше всего использовать Binders

Если вам нужна только межпоточная связь, вы можете использовать неназванные каналы через JNI (это отлично работает)

Ответ 4

Я хотел бы добавить к принятому ответу:

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.