Archivo

Entradas Etiquetadas ‘comandos’

Creando un servidor que acepte múltiples clientes simultáneos en C

Domingo, 6 de Febrero de 2011 Gaspar Fernández Sin comentarios

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

Las teclas que utilizo más frecuentemente en Emacs / Guía para principiantes

Sábado, 11 de Diciembre de 2010 Gaspar Fernández 3 comentarios

Hace unos días hablé de la importancia de conocer los atajos de teclado de tu IDE favorito; hoy hablaré de las teclas que más utilizo en el mío, Emacs, y de paso hago una pequeña guía para principiantes en este editor.

I. Un apunte básico del uso del teclado en Emacs

Lo primero que hay que mencionar es que hay muchas combinaciones con la tecla Control y Alt (o Meta), cuando una tecla, por ejemplo, la “x” se pulsa junto con Control diremos C-x y cuando esa misma tecla la pulsamos junto con Alt diremos M-x cuando pulsamos una combinación por ejemplo (M-g) y luego tenemos que soltar las teclas y pulsar una tecla sola, por ejemplo, otra g, diremos (M-g g), lo mismo vale con combinaciones, si tenemos que pulsar primero C-x y luego C-c diremos: “C-x C-c”

Si por alguna casualidad no podemos pulsar la tecla Meta, o una combinación (como sucede en algunos tipos de terminales remotos), podemos, para pulsar M-x, pulsar “Escape x”

II. Acceder al comando que queremos ejecutar

Una de las muchas cosas buenas que tiene Emacs es que todo lo que podemos hacer por teclado tiene un comando asociado, incluso para muchas acciones que no tienen tecla asociada. Por otra parte, también tenemos que decir que las teclas se pueden personalizar, por lo que estas teclas que diré (si no digo lo contrario) son las que vienen por defecto, y que casi todos los usuarios mantenemos porque suelen ser cómodas de pulsar, aunque en algunas ocasiones serían cómodas en un teclado inglés… pero te terminas acostumbrando.

Como iba diciendo, para ejecutar un comando debemos pulsar M-x y podremos, desde el minibuffer escribir el comando que queremos ejecutar, eso sí, disponemos de completion por lo que en cualquier momento podemos pulsar tab y veremos las posibilidades que tenemos. Incluso podemos pulsar M-x tab y veremos todas las posibilidades que tenemos, hay que recordar que podemos cargar extensiones y éstas pueden añadir comandos nuevos. Es más, la lista de comandos se abrirá en un nuevo buffer de Emacs, con lo que podremos buscar en ese buffer (no siempre nos acordamos de cómo empieza un comando). Para buscar también podemos hacer M-h a.

III. Me he equivocado con tanta combinación de teclas. ¿ Qué hago ?

Pulsar C-g en cualquier momento, eliminará todo lo que hemos pulsado hasta ahora. Es cierto, que a veces hay combinaciones demasiado largas, o en ocasiones hemos empezado a pulsar algo y nos arrepentimos, bien C-g es la solución para seguir por donde estábamos.

También podemos pulsar C-g si queremos cancelar una acción en curso, como por ejemplo salir, cuando tenemos muchos buffers abiertos y nos pregunta si queremos guardar o no, podemos pulsar C-g para que deje de preguntar por archivos y no salir del programa.

IV. Comandos básicos de un editor de texto

  • Abrir archivo: C-x C-f nos preguntará qué archivo abrir, podemos navegar por directorios desde el minibuffer, tenemos además la posibilidad de completar el nombre del archivo. (A lo bash)
  • Nuevo archivo: C-x C-f ¡ igual que antes ! y es que no hacemos nada diferente, abrimos un nuevo buffer. El archivo no se creará hasta que no lo salvemos por primera vez, así que, sin problema.
  • Salvar archivo: C-x C-s Salvamos el archivo con el nombre que tiene.
  • Salvar como: C-x C-w Nos pregunta con qué nombre nuevo queremos salvar el archivo
  • Salir: C-x C-c Salimos, nos preguntará
  • Buscar un texto: C-s (busca hacia adelante) C-r (busca hacia atrás) La primera vez que pulsamos cualquiera de las teclas, nos preguntará qué texto buscar, luego podemos jugar y buscar adelante y atrás como queramos.
  • Deshacer: Es posíblemente la opción peor implementada en Emacs, pero a veces hace el apaño. Podemos pulsar C-x u para deshacer un carácter o C-u C-x u para deshacer un conjunto de caracteres.
  • Seleccionar texto: Podemos hacerlo con el ratón o pulsar C-espacio, soltar y movermos con las teclas (flechas, re-pag, av-pag, inicio, fin y combinaciones.
  • Copiar: Con un texto seleccionado, pulsamos M-w
  • Cortar: Con un texto seleccionado, pulsamos C-w
  • Pegar: Pulsar C-y, pero si queremos pegar alguno de los textos copiados antes del último, justo después de C-y pulsamos M-y, e iremos navegando por todo lo que hemos ido copiando anteriormente.
  • Cambiar de ventana: C-b y seguidamente escribimos el nombre del fichero que queremos editar (de los que previamente están abiertos. O también C-x C-b para ver un listado de ficheros abiertos.
  • Cerrar buffer: C-x k Cerramos el buffer abierto ahora mismo, si correspondía con un fichero y no hay más buffers con ese fichero abierto, cerraremos el fichero y si tiene algún cambio, nos preguntará si queremos salvarlo.

V. Comandos para movernos más rápidamente

Algunos de estos son utilizados en más lugares:

  • C-k Elimina desde la posición actual del cursor hasta fin de línea. Estamos cortando el texto, por lo que la línea se copiará automáticamente.
  • M-< Saltamos al principio del buffer
  • M-> Saltamos al final del buffer
  • C-arriba / C-abajo : Nos moveremos verticalmente en bloques, es decir entre líneas en blanco o entre funciones, entre comentarios, etc
  • C-l Centra el buffer actual en la posición del cursor. Si se nos ha perdido el cursor es muy útil, o si estamos leyendo un documento.
  • M-g g nos preguntará a qué línea nos queremos ir del texto.
  • C-x h Selecciona el buffer completo
  • M-h Selecciona el párrafo actual

VI. Visualizando varios buffers a la vez

Emacs nos permite, en la misma ventana (aunque aquí se llame frame) tener varios cuadros de texto (que Emacs llama windows):

  • C-x 2 dividimos horizontalmente la pantalla, para tener un buffer arriba y otro abajo
  • C-x 3 dividimos verticalmente la pantalla, así tenemos un buffer a la derecha y otro a la izquierda.
  • C-x 0 (cero) eliminamos el buffer seleccionado
  • C-x o (letra O) cambiamos de buffer, nos vamos al de al lado, aunque también podemos seleccionarlos con ratón

VII. A la hora de programar nos será muy útil…

  • C-; Si tenemos un texto seleccionado, comentará ese texto, si no introducirá un comentario al final de la línea.
  • C-M-@ Si tenemos un texto seleccionado, se auto-indentará todo dependiendo del lenguaje de programación que estemos escribiendo.
  • M-/ Se completará automáticamente la palabra que estamos escribiendo ahora mismo.
  • Cuando hacemos C-espacio, C-s, C-r, etc vamos a irnos a otra posición diferente a la que estábamos, por lo que necesitamos una tecla para regresar: C-x C-x ; es útil cuando programamos y tenemos que escribir algo arriba del todo, y luego regresar a la línea donde seguimos escribiendo.
  • C-x r espacio Nos hará una pregunta, pulsamos una letra cualquiera, por ejemplo a. Guardará la posición en esa a
  • M-x register-to-point Nos preguntará una posición, por ejemplo, la de antes: a; y nos lanzará a esa parte del texto. (Podremos vincularlo a una tecla si lo usamos bastante.

Aunque parece que tenemos que estudiar para utilizar el editor, basta con una semana para habituarse a las teclas básicas, y poco a poco, llamando a los comandos M-x  indent-region (por ejemplo) nos dirá la tecla que debemos pulsar para acceder rápidamente.

Curioso e interesante (2): Fragmentación en Linux, WikiUnix, convertir documentos desde la línea de comando, Ubuntu más seguro que Windows by Dell

Domingo, 13 de Junio de 2010 Gaspar Fernández Sin comentarios

Estos días he estado un poco liado, pero he dejado varias pestañas del navegador abiertas con algunas curiosidades que he encontrado estos días y me han llamado la atención:

  • Lo leí en Muy Linux. Hay controversia en este tema… casi todos decimos que no es necesario, y otros dicen que sólo somos geeks talibanes que ocultamos las carencias de nuestro sistema operativo preferido; pero la realidad es que la fragmentación en los sistemas de archivos más populares de Linux es significativamente menor que en Windows. Podemos ver más información en: Why doesn’t Linux need defragmenting?
  • Conversor de documentos de OpenOffice desde la línea de comandos: unoconv. Es un script en python que nos permite hacer conversiones de documentos en cualquier formato soportado por OpenOffice (necesita OpenOffice instalado y pyUno). Esto nos permitirá por ejemplo automatizar la conversión de formatos.
  • WikiUnix: Es una gran wiki de referencia de comandos y características de los sistemas Unix; un proyecto de la Oficina de Software Libre de la Universidad de Cádiz.
  • Dell afirma que: Ubuntu es más seguro que Windows; mientras Dell, sigue recomendando insistentemente en su web Windows 7…
  • Microsoft se mofa del fondo de Google. Vale que Google se pasó, metiendo un fondo automáticamente, a modo de presentación de su nueva característica; a la gente no le suelen gustar los cambios. Pero Microsoft aprovecha la más mínima para meter baza. Ya vale con las cadenas de Televisión que muestran el logotipo de Windows o una imagen de Internet Explorar cada vez que emiten la imagen de un ordenador; o que invierta en product placement en series como Crónicas Vampíricas y en varios episodios, Jericho, Entre Fantasmas, Smallville, El Internado, Los Protegidos… qué pastón para promocionarse, y la verdad, espero que todos los responsables de esas series hayan cobrado bien, porque si no, tontos de ellos…

Saludos

Un fondo de escritorio para Linuxeros

Domingo, 14 de Marzo de 2010 Gaspar Fernández 2 comentarios

Acabo de verlo en MuyLinux y me ha encantado. Lo han sacado de Be Linux My Friend. Es una chuleta para la línea de comandos, con todas esas órdenes útiles que vamos a necesitar.
Dada su distribución, bien podemos tener iconos de escritorio a la izquierda o un emulador de terminal para ir practicando :)
Encontramos los comandos básicos para manejo de ficheros, búsquedas procesos, compresión, instalación de programas, permisos, y más.
cli_commands-1400x900

Categories: Bash, General Tags: , , ,

Visita otras webs de la red