Para hacer una prueba de esto, crearemos un servidor al que nos podremos conectar por telnet y pedir cierta información a través de comandos. El ejemplo soporta los siguientes comandos (en mayúsculas):
- DATE: Pide la fecha al servidor
- TIME: Pide la hora al servidor
- HOLA: Saluda y me dice mi IP
- EXIT: Cierra el cliente actual
- CERRAR: Cierra el servidor.
- Cualquier otra cosa se manda repetida (ECHO)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 | /** ************************************************************* * @file servtcp.c * @brief Breve descripción * Ejemplo de un cliente TCP usando threads * * * @author Gaspar Fernández <blakeyed@totaki.com> * @version 0.1Beta * @date 13 ene 2011 * Historial de cambios: * 20110113 - Versión inicial * * *************************************************************/ #include <fcntl.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <stdio.h> #include <netinet/in.h> #include <resolv.h> #include <sys/socket.h> #include <sys/types.h> #include <sys/wait.h> #include <arpa/inet.h> #include <unistd.h> #include <pthread.h> #include <string.h> /** Puerto */ #define PORT 7000 /** Número máximo de hijos */ #define MAX_CHILDS 3 /** Longitud del buffer */ #define BUFFERSIZE 512 int AtiendeCliente(int socket, struct sockaddr_in addr); int DemasiadosClientes(int socket, struct sockaddr_in addr); void error(int code, char *err); void reloj(int loop); int main(int argv, char** argc){ int socket_host; struct sockaddr_in client_addr; struct sockaddr_in my_addr; struct timeval tv; /* Para el timeout del accept */ socklen_t size_addr = 0; int socket_client; fd_set rfds; /* Conjunto de descriptores a vigilar */ int childcount=0; int exitcode; int childpid; int pidstatus; int activated=1; int loop=0; socket_host = socket(AF_INET, SOCK_STREAM, 0); if(socket_host == -1) error(1, "No puedo inicializar el socket"); my_addr.sin_family = AF_INET ; my_addr.sin_port = htons(PORT); my_addr.sin_addr.s_addr = INADDR_ANY ; if( bind( socket_host, (struct sockaddr*)&my_addr, sizeof(my_addr)) == -1 ) error(2, "El puerto está en uso"); /* Error al hacer el bind() */ if(listen( socket_host, 10) == -1 ) error(3, "No puedo escuchar en el puerto especificado"); size_addr = sizeof(struct sockaddr_in); while(activated) { reloj(loop); /* select() se carga el valor de rfds */ FD_ZERO(&rfds); FD_SET(socket_host, &rfds); /* select() se carga el valor de tv */ tv.tv_sec = 0; tv.tv_usec = 500000; /* Tiempo de espera */ if (select(socket_host+1, &rfds, NULL, NULL, &tv)) { if((socket_client = accept( socket_host, (struct sockaddr*)&client_addr, &size_addr))!= -1) { loop=-1; /* Para reiniciar el mensaje de Esperando conexión... */ printf("\nSe ha conectado %s por su puerto %d\n", inet_ntoa(client_addr.sin_addr), client_addr.sin_port); switch ( childpid=fork() ) { case -1: /* Error */ error(4, "No se puede crear el proceso hijo"); break; case 0: /* Somos proceso hijo */ if (childcount<MAX_CHILDS) exitcode=AtiendeCliente(socket_client, client_addr); else exitcode=DemasiadosClientes(socket_client, client_addr); exit(exitcode); /* Código de salida */ default: /* Somos proceso padre */ childcount++; /* Acabamos de tener un hijo */ close(socket_client); /* Nuestro hijo se las apaña con el cliente que entró, para nosotros ya no existe. */ break; } } else fprintf(stderr, "ERROR AL ACEPTAR LA CONEXIÓN\n"); } /* Miramos si se ha cerrado algún hijo últimamente */ childpid=waitpid(0, &pidstatus, WNOHANG); if (childpid>0) { childcount--; /* Se acaba de morir un hijo */ /* Muchas veces nos dará 0 si no se ha muerto ningún hijo, o -1 si no tenemos hijos con errno=10 (No child process). Así nos quitamos esos mensajes*/ if (WIFEXITED(pidstatus)) { /* Tal vez querremos mirar algo cuando se ha cerrado un hijo correctamente */ if (WEXITSTATUS(pidstatus)==99) { printf("\nSe ha pedido el cierre del programa\n"); activated=0; } } } loop++; } close(socket_host); return 0; } /* No usamos addr, pero lo dejamos para el futuro */ int DemasiadosClientes(int socket, struct sockaddr_in addr) { char buffer[BUFFERSIZE]; int bytecount; memset(buffer, 0, BUFFERSIZE); sprintf(buffer, "Demasiados clientes conectados. Por favor, espere unos minutos\n"); if((bytecount = send(socket, buffer, strlen(buffer), 0))== -1) error(6, "No puedo enviar información"); close(socket); return 0; } int AtiendeCliente(int socket, struct sockaddr_in addr) { char buffer[BUFFERSIZE]; char aux[BUFFERSIZE]; int bytecount; int fin=0; int code=0; /* Código de salida por defecto */ time_t t; struct tm *tmp; while (!fin) { memset(buffer, 0, BUFFERSIZE); if((bytecount = recv(socket, buffer, BUFFERSIZE, 0))== -1) error(5, "No puedo recibir información"); /* Evaluamos los comandos */ /* El sistema de gestión de comandos es muy rudimentario, pero nos vale */ /* Comando TIME - Da la hora */ if (strncmp(buffer, "TIME", 4)==0) { memset(buffer, 0, BUFFERSIZE); t = time(NULL); tmp = localtime(&t); strftime(buffer, BUFFERSIZE, "Son las %H:%M:%S\n", tmp); } /* Comando DATE - Da la fecha */ else if (strncmp(buffer, "DATE", 4)==0) { memset(buffer, 0, BUFFERSIZE); t = time(NULL); tmp = localtime(&t); strftime(buffer, BUFFERSIZE, "Hoy es %d/%m/%Y\n", tmp); } /* Comando HOLA - Saluda y dice la IP */ else if (strncmp(buffer, "HOLA", 4)==0) { memset(buffer, 0, BUFFERSIZE); sprintf(buffer, "Hola %s, ¿cómo estás?\n", inet_ntoa(addr.sin_addr)); } /* Comando EXIT - Cierra la conexión actual */ else if (strncmp(buffer, "EXIT", 4)==0) { memset(buffer, 0, BUFFERSIZE); sprintf(buffer, "Hasta luego. Vuelve pronto %s\n", inet_ntoa(addr.sin_addr)); fin=1; } /* Comando CERRAR - Cierra el servidor */ else if (strncmp(buffer, "CERRAR", 6)==0) { memset(buffer, 0, BUFFERSIZE); sprintf(buffer, "Adiós. Cierro el servidor\n"); fin=1; code=99; /* Salir del programa */ } else { sprintf(aux, "ECHO: %s\n", buffer); strcpy(buffer, aux); } if((bytecount = send(socket, buffer, strlen(buffer), 0))== -1) error(6, "No puedo enviar información"); } close(socket); return code; } void reloj(int loop) { if (loop==0) printf("[SERVIDOR] Esperando conexión "); printf("\033[1D"); /* Introducimos código ANSI para retroceder 2 caracteres */ switch (loop%4) { case 0: printf("|"); break; case 1: printf("/"); break; case 2: printf("-"); break; case 3: printf("\"); break; default: /* No debemos estar aquí */ break; } fflush(stdout); /* Actualizamos la pantalla */ } void error(int code, char *err) { char *msg=(char*)malloc(strlen(err)+14); sprintf(msg, "Error %d: %s\n", code, err); fprintf(stderr, msg); exit(1); } |
La clave para poder atender varios clientes simultáneos es bifurcar (fork()) el programa cuando se conecta un nuevo cliente, de esta forma, el diálogo con el cliente se lleva a cabo desde un nuevo proceso mientras que el proceso padre (el primero) sigue escuchando en el puerto esperando conexiones.
Utilizamos select() para no detener la ejecución del programa cuando estamos esperando conexiones; tal vez el programa tenga que hacer algo mientras nadie está conectándose (y es que el programa principal permanecerá aquí la mayor parte del tiempo), por ejemplo lo que hacemos aquí es controlar los hijos que se mueren y ver cómo se han muerto. select() establece un tiempo máximo de espera (500000 microseguntos en este caso, ver la variable tv); si pasado ese tiempo el descriptor socket_host no ha cambiado, seguiremos la ejecución normal del programa, pero si durante ese tiempo cambia ejecutaremos accept().
La función select() funciona con grupos de descriptores y por eso utilizamos las macros FD_ZERO() para limpiar el grupo y FD_SET() para añadir un descriptor al grupo.
El primer parámetro de select() debe ser el descriptor de fichero más grande a comprobar+1; como sólo tenemos un descriptor (socket_host) debemos añadir este al grupo, y pasarle a select en su primer parámetro socket_host+1.
Cuando llega un cliente, como dijimos antes, se abrirá un proceso hijo que lo procesará con una de estas dos funciones: AtiendeCliente() o DemasiadosClientes() dependiendo de si el número máximo de clientes ha sido alcanzado o no. Además, la salida del proceso hijo se obtendrá de la salida de estas funciones, siendo el valor de salida clave 99 el que necesita el proceso padre para cerrar terminar el proceso servidor, es decir, si un hijo sale con el valor de retorno 99 provocará el cierre del servidor. Este valor se maneja debajo del switch(); primero con waitpid() esperamos la muerte de alguno de nuestros hijos aunque con el modificador WNOHANG provocamos que si no se ha muerto ninguno, no vamos a esperar, el programa seguirá su ejecución.
Desde AtiendeCliente() veremos un método muy rudimentario para implementar los comandos, aunque para un ejemplo y 4 comandos nos vale perfectamente.
DemasiadosClientes() devolverá únicamente un mensaje al cliente que se acaba de conectar avisando de que hay demasiados clientes conectados.
Reloj() es un pequeño reloj en modo texto que va girando, a cada iteración del bucle principal, está aquí para que nosotros veamos que el servidor está haciendo algo. Utiliza códigos ANSI, para volver a escribirse en el lugar que estaba, y fflush() para volcar el texto a pantalla antes de nada (puede ser que se acumulen varios mensajes y entonces el dibujado sería incorrecto).
Para probar este programa, debemos compilar con:
$ gcc -o servtcp servtcp.c
y ejecutarlo; y, en un terminal aparte, conectar con telnet, Si, por ejemplo lo queremos hacer todo desde el mismo ordenador; si el servidor está en un ordenador diferente y nos queremos conectar a él cambiamos «localhost» por la IP o el host del ordenador donde se esté ejecutando el cliente:
$ telnet localhost 7000
Foto principal: Seeweb
Pingback: Bitacoras.com /
Pingback: BlogESfera.com /
Esta muy limpio el codigo, pero al compilarlo en la consola de linuz ubuntu12.04 me da el siguiente error
265:3: aviso: el formato no es una cadena literal y no tiene argumentos de formato [-Wformat-security]
tal vez es que no se ha declarado la variable FILE,pero aun asi da el mismo error.
Alguien tiene idea de por que pasa esto.
Gracias Francisco.
En la línea del fprintf (), la antepenúltima pon:
fprintf(stderr, «%s», msg);
Con eso no debería darte el fallo.
Aun hay alguien por aca?
@Yeison
Aunque un poco tarde, pero si, me asomo por aquí de vez en cuando 🙂
Hola.
Gracias por el código.
Solo tengo una duda:
Osea que ésto teoricamente atendería 3 clientes al mismo tiempo? es decir, por ejemplo: si el servidor recibiera archivos, podría recibir 3 archivos 1 por cada cliente al mismo tiempo?
Saludos 😀
Exacto, de todas formas, siempre podemos ampliar el valor de MAX_CHILDS para que se pudieran atender más peticiones al mismo tiempo creando nuevos procesos hijos que las atiendan.
Hola…
Tengo una duda… como sería el codigo del cliente, modifiqué un poco este codigo para que resuelva un dominio con gethostbyname(); pero aun no se bien como conectar un clientes sin usar telnet.
Gracias.
@Calos
Me has dado una buena idea para un futuro post, porque veo que es algo muy largo de explicar en un comentario. Espero tener un rato para publicarlo pronto. Muchas gracias !
Pingback: Cliente TCP no bloqueante en C, en el que podemos enviar y recibir en cualquier momento | Poesía Binaria /
@Calos
Hola, el código sería algos así:
#include
#include
#include
#include
#include
#include
#include
#define MAXLINE 512 /*Longitud máxima de mensaje*/
#define SERV_PORT 5728 /*puerto servidor*/
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
char sendline[MAXLINE], recvline[MAXLINE];
if (argc !=2) {
perror(«No se indicó la dirección IP del Servidor»);
exit(1);
}
//Creamos socket para el cliente
//Si sockfd<0 hay un error en la creación del socket.
if ((sockfd = socket (AF_INET, SOCK_STREAM, 0)) <0) {
perror("Problem creando el Socket.");
exit(2);
}
//Ceación del socket
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr= inet_addr(argv[1]);
servaddr.sin_port = htons(SERV_PORT);
//Conexión del cleinte al socket del servidor
if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr))<0) {
perror("Problema al intentar la conexión con el Servidor");
exit(3);
}
while (fgets(sendline, MAXLINE, stdin) != NULL) {
send(sockfd, sendline, strlen(sendline), 0);
if (recv(sockfd, recvline, MAXLINE,0) == 0){
perror("La conexión con el Servidor se terminó prematuramente.");
exit(4);
}
printf("Cadena recibida del servidor: %s \n",recvline);
}
exit(0);
}
Lo compilas,.. gcc -g servidor -o servidor, y lo ejecutas ./servidor
@Andres Felipe
Perdón, le ejecutas así: ./servidor (dir. IP servidor)
@Andres Felipe
Gracias por el aporte!! Los #include no salieron bien, pero bueno, nos los podemos imaginar 🙂
@Gaspar Fernández
hola, no había notado, aquí están.
2
3
4
5
6
7
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <arpa/inet.h>
Muchas gracias ! 🙂 ¡Qué apañado!
Por cierto, hace unos meses publiqué: https://poesiabinaria.net/2014/01/cliente-tcp-no-bloqueante-en-c-en-el-que-podemos-enviar-y-recibir-en-cualquier-momento/ un cliente tcp no bloqueante en C. Para que le echéis un ojo
Buen dia, excelente post, gracias por la ayuda.
Gracias a ti Ingrid !
Excelente aporte, Felicidades!!
Muy buen aporte, gracias!
Excelente artículo!
Que tarde encontre este blog, desde ahora en mi cliente rss siguiéndolos.
Saludos
Muchas gracias pisculichi. Si te gustó este, tal vez te guste: https://poesiabinaria.net/2016/02/creando-un-cliente-para-un-servicio-de-red-con-pocas-lineas-en-c/
Es más, tengo en preparación varios posts de creación de servicios en C y C++
Un cordial saludo
No se si alguien siga comentando pero, si quisiera que se pudiera abrir un archivo .txt y que el cliente lo lea en su terminal, como se podria hacer? URGEEE!!!!