2010-04-29 4 views
11

Ho problemi nella creazione di named pipe in Android e l'esempio di seguito illustra il mio dilemma:Come creare named pipe (mkfifo) in Android?

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

Il codice stampa sempre:

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

non riesco a capire esattamente il motivo per cui questo non riesce. L'applicazione ha le autorizzazioni android.permission.WRITE_EXTERNAL_STORAGE. Posso creare file normali con esattamente lo stesso nome nella stessa posizione, ma la creazione di pipe fallisce. La pipa in questione dovrebbe essere accessibile da più applicazioni.

  1. Sospetto che nessuno possa creare pipe in/sdcard. Dove sarebbe la posizione migliore per farlo?
  2. Quale albero di modalità dovrei impostare (2 ° parametro)?
  3. L'applicazione richiede autorizzazioni extra?

risposta

14

Roosmaa's answer è corretto - mkfifo() chiama semplicemente mknod() per creare un file speciale e FAT32 non lo supporta.

In alternativa si consiglia di prendere in considerazione l'utilizzo di socket di dominio UNIX "namespace astratto" di Linux. Dovrebbero essere approssimativamente equivalenti a una named pipe. Puoi accedervi per nome, ma non fanno parte del filesystem, quindi non devi occuparti di vari problemi di autorizzazione. Nota che la presa è bidirezionale.

Poiché si tratta di un socket, potrebbe essere necessaria l'autorizzazione INTERNET. Non ne sono sicuro.

Ecco un rapido po 'di codice di esempio client/server:

#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; 
} 
+0

questo potrebbe essere difficile da replicare in java - solo dicendo :) – KevinDTimm

+1

Vero, ma lo snippet di codice originale non è nemmeno Java, quindi ho pensato che non era un affare-interruttore. L'approccio socket è sicuramente più intensivo di JNI rispetto a una named pipe nel filesystem. – fadden

+0

A destra, la destinazione è C. Questo approccio sembra richiedere un thread separato per client connesso. –

8

Il file system predefinito di/sdcard è FAT32, che non supporta pipe denominate.

Su un dispositivo non rooted l'unica posizione possibile che è possibile provare a creare tali pipe è la directory dei dati dell'applicazione/data/data/com.example/. Nota: non è necessario codificare tale valore, utilizzare Context.getApplicationInfo(). DataDir.

Tuttavia, ogni volta che l'utente utilizza Apps2SD o quando Google implementa tale supporto ufficialmente, è necessario assicurarsi che l'utente sappia che l'app non può essere archiviata nel sistema di file vfat.

1

c'è anche /sqlite_stmt_journals (lo usiamo per il test, non so per quanto tempo questa directory sopravviverà aggiornamenti del sistema operativo)

Se avete bisogno di IPC, le migliori pratiche sono di utilizzare la Binders

Se avete solo bisogno di comunicazione inter-thread, è possibile utilizzare i tubi senza nome tramite JNI (questo funziona bene)

+0

Sì, questo è quello che ho finito usando. La chiave qui è di ottenere codice nativo parlare con codice nativo. Passare a Java lo renderebbe più complicato (JNI e simili) e potenzialmente più lento. L'interfaccia di Binder è disponibile sul C++, ma è necessaria una sorgente Android completa, il che rappresenta un grosso ostacolo. –

+0

Usiamo JNI sopra pipe senza nome perché abbiamo codice Java che invia in modo asincrono i dati ai nostri thread nativi; ma se devi semplicemente far dialogare i tuoi thread nativi con altri thread nativi all'interno dello stesso processo, non hai bisogno di JNI per questo. Dovrei aggiungere che i tubi sono più veloci e leggeri delle prese. –

0

Se stai codificando questo in Java, dovresti semplicemente usare PipedInputStream e PipedOutputStream.

+0

può PipedInputStream e PipedOutputStream essere utilizzati su più applicazioni come in C++ un namedPipe può essere utilizzato da 2 diverse applicazioni per la lettura e la scrittura. –

+1

No. È solo in-process. –

1

vorrei aggiungere alla risposta accettata:

1) sono in grado di utilizzare questo metodo per collegare un socket tra due moduli nativi di un app Android.

2) write() dovrebbe essere in un ciclo poiché potrebbe non scrivere l'intero importo richiesto al primo tentativo. Ad esempio, esso dovrà essere simile:

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

La gestione degli errori raffigurato è insufficiente, poiché diversi codici di errore indicano semplicemente per riprovare. Vedere la documentazione per write.

+0

Puoi usare 'TEMP_FAILURE_RETRY()' per riprovare su EINTR. – fadden