socket servidor que por enviar comando cliente archivos af_unix c++ c linux sockets network-programming

servidor - Enviar y recibir un archivo en la programación de socket en Linux con C/C++(GCC/G++)



sockets unix (4)

Me gustaría implementar una arquitectura cliente-servidor que se ejecute en Linux utilizando sockets y lenguaje C / C ++ que sea capaz de enviar y recibir archivos. ¿Hay alguna biblioteca que hace que esta tarea sea fácil? ¿Podría alguien dar un ejemplo?



Haga un man 2 sendfile . Solo necesita abrir el archivo fuente en el cliente y el archivo de destino en el servidor, luego llame a sendfile y el kernel cortará y moverá los datos.


Ejemplo POSIX mínimo ejecutable

Uso:

  1. obtener dos computadoras en una LAN

  2. En la computadora del servidor:

    1. Encuentre la IP local del servidor con ifconfig , por ejemplo, 192.168.0.10

    2. Correr:

      ./server output.tmp 12345

  3. En la computadora del cliente:

    printf ''ab/ncd/n'' > input.tmp ./client input.tmp 192.168.0.10 12345

  4. Resultado: se crea un archivo output.tmp en la computadora del servidor que contiene ''ab/ncd/n'' .

server.c

/* Receive a file over a socket. Saves it to output.tmp by default. Interface: ./executable [<output_file> [<port>]] Defaults: - output_file: output.tmp - port: 12345 */ #define _XOPEN_SOURCE 700 #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <fcntl.h> #include <netdb.h> /* getprotobyname */ #include <netinet/in.h> #include <sys/stat.h> #include <sys/socket.h> #include <unistd.h> int main(int argc, char **argv) { char *file_path = "output.tmp"; char buffer[BUFSIZ]; char protoname[] = "tcp"; int client_sockfd; int enable = 1; int filefd; int i; int server_sockfd; socklen_t client_len; ssize_t read_return; struct protoent *protoent; struct sockaddr_in client_address, server_address; unsigned short server_port = 12345u; if (argc > 1) { file_path = argv[1]; if (argc > 2) { server_port = strtol(argv[2], NULL, 10); } } /* Create a socket and listen to it.. */ protoent = getprotobyname(protoname); if (protoent == NULL) { perror("getprotobyname"); exit(EXIT_FAILURE); } server_sockfd = socket( AF_INET, SOCK_STREAM, protoent->p_proto ); if (server_sockfd == -1) { perror("socket"); exit(EXIT_FAILURE); } if (setsockopt(server_sockfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)) < 0) { perror("setsockopt(SO_REUSEADDR) failed"); exit(EXIT_FAILURE); } server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = htonl(INADDR_ANY); server_address.sin_port = htons(server_port); if (bind( server_sockfd, (struct sockaddr*)&server_address, sizeof(server_address) ) == -1 ) { perror("bind"); exit(EXIT_FAILURE); } if (listen(server_sockfd, 5) == -1) { perror("listen"); exit(EXIT_FAILURE); } fprintf(stderr, "listening on port %d/n", server_port); while (1) { client_len = sizeof(client_address); puts("waiting for client"); client_sockfd = accept( server_sockfd, (struct sockaddr*)&client_address, &client_len ); filefd = open(file_path, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (filefd == -1) { perror("open"); exit(EXIT_FAILURE); } do { read_return = read(client_sockfd, buffer, BUFSIZ); if (read_return == -1) { perror("read"); exit(EXIT_FAILURE); } if (write(filefd, buffer, read_return) == -1) { perror("write"); exit(EXIT_FAILURE); } } while (read_return > 0); close(filefd); close(client_sockfd); } return EXIT_SUCCESS; }

client.c

/* Send a file over a socket. Interface: ./executable [<input_path> [<sever_hostname> [<port>]]] Defaults: - input_path: input.tmp - server_hostname: 127.0.0.1 - port: 12345 */ #define _XOPEN_SOURCE 700 #include <stdio.h> #include <stdlib.h> #include <arpa/inet.h> #include <fcntl.h> #include <netdb.h> /* getprotobyname */ #include <netinet/in.h> #include <sys/stat.h> #include <sys/socket.h> #include <unistd.h> int main(int argc, char **argv) { char protoname[] = "tcp"; struct protoent *protoent; char *file_path = "input.tmp"; char *server_hostname = "127.0.0.1"; char *server_reply = NULL; char *user_input = NULL; char buffer[BUFSIZ]; in_addr_t in_addr; in_addr_t server_addr; int filefd; int sockfd; ssize_t i; ssize_t read_return; struct hostent *hostent; struct sockaddr_in sockaddr_in; unsigned short server_port = 12345; if (argc > 1) { file_path = argv[1]; if (argc > 2) { server_hostname = argv[2]; if (argc > 3) { server_port = strtol(argv[3], NULL, 10); } } } filefd = open(file_path, O_RDONLY); if (filefd == -1) { perror("open"); exit(EXIT_FAILURE); } /* Get socket. */ protoent = getprotobyname(protoname); if (protoent == NULL) { perror("getprotobyname"); exit(EXIT_FAILURE); } sockfd = socket(AF_INET, SOCK_STREAM, protoent->p_proto); if (sockfd == -1) { perror("socket"); exit(EXIT_FAILURE); } /* Prepare sockaddr_in. */ hostent = gethostbyname(server_hostname); if (hostent == NULL) { fprintf(stderr, "error: gethostbyname(/"%s/")/n", server_hostname); exit(EXIT_FAILURE); } in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list))); if (in_addr == (in_addr_t)-1) { fprintf(stderr, "error: inet_addr(/"%s/")/n", *(hostent->h_addr_list)); exit(EXIT_FAILURE); } sockaddr_in.sin_addr.s_addr = in_addr; sockaddr_in.sin_family = AF_INET; sockaddr_in.sin_port = htons(server_port); /* Do the actual connection. */ if (connect(sockfd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) { perror("connect"); return EXIT_FAILURE; } while (1) { read_return = read(filefd, buffer, BUFSIZ); if (read_return == 0) break; if (read_return == -1) { perror("read"); exit(EXIT_FAILURE); } /* TODO use write loop: https://.com/questions/24259640/writing-a-full-buffer-using-write-system-call */ if (write(sockfd, buffer, read_return) == -1) { perror("write"); exit(EXIT_FAILURE); } } free(user_input); free(server_reply); close(filefd); exit(EXIT_SUCCESS); }

Más comentarios

Posibles mejoras:

  • Actualmente, output.tmp se sobrescribe cada vez que se realiza un envío.

    Esto exige la creación de un protocolo simple que permita pasar un nombre de archivo para que se puedan cargar múltiples archivos, por ejemplo: nombre de archivo hasta el primer carácter de nueva línea, nombre máximo de 256 caracteres y el resto hasta que el contenido sea el cierre del socket. Por supuesto, eso requeriría saneamiento para evitar una vulnerabilidad transversal en el camino .

    Alternativamente, podríamos crear un servidor que codifique los archivos para encontrar nombres de archivos y mantenga un mapa de las rutas originales en hash en el disco (en una base de datos).

  • Solo un cliente puede conectarse a la vez.

    Esto es especialmente dañino si hay clientes lentos cuyas conexiones duran mucho tiempo: la conexión lenta detiene a todos.

    Una forma de evitar esto es bifurcar un proceso / hilo para cada accept , comenzar a escuchar nuevamente de inmediato y usar sincronización de bloqueo de archivos en los archivos.

  • Agregue tiempos de espera y clientes cercanos si tardan demasiado. De lo contrario, sería fácil hacer un DoS.

    poll o select son algunas opciones: ¿Cómo implementar un tiempo de espera en la llamada de función de lectura?

Este ejemplo en GitHub .

Probado en Ubuntu 15.10.


La solución más portátil es simplemente leer el archivo en fragmentos, y luego escribir los datos en el zócalo, en un bucle (y del mismo modo, al revés cuando se recibe el archivo). Asigna un búfer, read en ese búfer y write desde ese búfer en su zócalo (también puede usar send y recv , que son formas específicas de escritura y lectura de 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úrese de leer la documentación para read y write cuidado, especialmente cuando maneje errores. Algunos de los códigos de error significan que debe volver a intentarlo, por ejemplo, solo hacer un bucle de nuevo con una instrucción de continue , mientras que otros significan que algo está roto y debe detenerse.

Para enviar el archivo a un socket, hay una llamada al sistema, sendfile que hace justo lo que usted desea. 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 de archivo fuente debe admitir mmap (como en, ser un archivo real, no un socket), y el destino debe ser un socket (para que no pueda usarlo para copiar archivos, o enviar datos directamente desde un socket a otro); está diseñado para admitir el uso que describes, de enviar un archivo a un socket. Sin embargo, no ayuda con la recepción del archivo; tendrías que hacer el loop por eso. No puedo decirte por qué hay una llamada a sendfile pero ningún recvfile análogo.

Tenga cuidado de que sendfile sea ​​específico de Linux; no es portátil para 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, se introduced llamada al sistema de splice , y desde 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 buena explicación del propio Linus . Señala cómo usar el splice es básicamente como el ciclo anterior, utilizando read y write , excepto que el buffer está en el kernel, por lo que los datos no tienen que transferirse entre el kernel y el espacio del usuario, o puede que ni siquiera pasen a través de la CPU (conocida como "E / S de copia cero").