Haz un man 2 sendfile
. Solo necesita abrir el archivo de origen en el cliente y el archivo de destino en el servidor, luego llamar a sendfile y el kernel cortará y moverá los datos.
La solución más portátil es simplemente leer el archivo en fragmentos y luego escribir los datos en el socket, en un bucle (y al revés al recibir el archivo). Asignas un búfer, read
en ese búfer, y write
de ese búfer a tu socket (también puedes usar send
y recv
, que son formas específicas de socket de escribir y leer datos). El esquema se vería así:
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;
}
}
Asegúrate de leer la documentación de read
y write
cuidadosamente, especialmente al manejar errores. Algunos de los códigos de error significan que debe volver a intentarlo, por ejemplo, repetir el bucle de nuevo con un continue
declaración, mientras que otros significan que algo está roto y debe parar.
Para enviar el archivo a un socket, hay una llamada al sistema, sendfile
que hace justo lo que quieres. Le dice al kernel que envíe un archivo de un descriptor de archivo a otro, y luego el kernel puede encargarse del resto. Hay una advertencia de que el descriptor del archivo fuente debe ser compatible con mmap
(como en, ser un archivo real, no un socket), y el destino debe ser un socket (por lo que no puede usarlo para copiar archivos o enviar datos directamente de un socket a otro); está diseñado para admitir el uso que describe, de enviar un archivo a un socket. Sin embargo, no ayuda a recibir el archivo; tendrías que hacer el bucle tú mismo para eso. No puedo decirte por qué hay un sendfile
llamada pero no análoga recvfile
.
Cuidado con que sendfile
es específico de Linux; no es portable a otros sistemas. Otros sistemas suelen tener su propia versión de sendfile
, pero la interfaz exacta puede variar (FreeBSD, Mac OS X, Solaris).
En Linux 2.6.17, el splice
Se introdujo una llamada al sistema y, a partir de la versión 2.6.23, se usa internamente para implementar sendfile
. splice
es una API de propósito más general que sendfile
. Para una buena descripción de splice
y tee
, vea la explicación bastante buena del propio Linus. Señala cómo usar splice
es básicamente como el ciclo anterior, usando read
y write
, excepto que el búfer está en el kernel, por lo que los datos no tienen que transferirse entre el kernel y el espacio del usuario, o es posible que nunca pasen a través de la CPU (lo que se conoce como "E/S de copia cero").