Fai un man 2 sendfile
. Devi solo aprire il file di origine sul client e il file di destinazione sul server, quindi chiamare sendfile e il kernel taglierà e sposterà i dati.
La soluzione più portatile è solo leggere il file in blocchi, quindi scrivere i dati nel socket, in un ciclo (e allo stesso modo, viceversa quando si riceve il file). Allochi un buffer, read
in quel buffer e write
da quel buffer nel tuo socket (puoi anche usare send
e recv
, che sono modi specifici del socket di scrivere e leggere i dati). Lo schema sarebbe simile a questo:
while (1) {
// Read data into buffer. We may not have enough to fill up buffer, so we
// store how many bytes were actually read in bytes_read.
int bytes_read = read(input_file, buffer, sizeof(buffer));
if (bytes_read == 0) // We're done reading from the file
break;
if (bytes_read < 0) {
// handle errors
}
// You need a loop for the write, because not all of the data may be written
// in one call; write will return how many bytes were written. p keeps
// track of where in the buffer we are, while we decrement bytes_read
// to keep track of how many bytes are left to write.
void *p = buffer;
while (bytes_read > 0) {
int bytes_written = write(output_socket, p, bytes_read);
if (bytes_written <= 0) {
// handle errors
}
bytes_read -= bytes_written;
p += bytes_written;
}
}
Assicurati di leggere la documentazione per read
e write
con attenzione, soprattutto quando si gestiscono gli errori. Alcuni dei codici di errore indicano che dovresti semplicemente riprovare, ad esempio semplicemente ripetere il ciclo con un continue
dichiarazione, mentre altri significano che qualcosa è rotto e devi fermarti.
Per inviare il file a un socket, c'è una chiamata di sistema, sendfile
che fa proprio quello che vuoi. Dice al kernel di inviare un file da un descrittore di file a un altro, e quindi il kernel può occuparsi del resto. C'è un avvertimento che il descrittore del file sorgente deve supportare mmap
(come in, essere un file effettivo, non un socket) e la destinazione deve essere un socket (quindi non puoi usarlo per copiare file o inviare dati direttamente da un socket a un altro); è progettato per supportare l'uso che descrivi, di inviare un file a un socket. Tuttavia, non aiuta a ricevere il file; dovresti fare tu stesso il ciclo per quello. Non posso dirti perché c'è un sendfile
call ma nessun analogo recvfile
.
Attenzione a quel sendfile
è specifico per Linux; non è portabile su altri sistemi. Altri sistemi hanno spesso la propria versione di sendfile
, ma l'interfaccia esatta può variare (FreeBSD, Mac OS X, Solaris).
In Linux 2.6.17, il splice
è stata introdotta la chiamata di sistema e dalla 2.6.23 è utilizzata internamente per implementare sendfile
. splice
è un'API più generica rispetto a sendfile
. Per una buona descrizione di splice
e tee
, vedi la spiegazione piuttosto buona dello stesso Linus. Sottolinea come utilizzare splice
è fondamentalmente proprio come il ciclo sopra, usando read
e write
, tranne per il fatto che il buffer si trova nel kernel, quindi i dati non devono essere trasferiti tra il kernel e lo spazio utente, o potrebbero anche non passare mai attraverso la CPU (noto come "I/O a copia zero").