¡Ya estamos en Twitter!

Perl en Español

  1. Home
  2. Tutoriales
  3. Foro
  4. Artículos
  5. Donativos
  6. Publicidad

Creando un servidor MP3 con perl

por Uriel Lizama

Introducción

En una de mis rutinarias navegaciones por perlmonks.org, me encontré un tutorial bastante interesante. En cuanto termine de leerlo tuve la necesidad de traducirlo y ponerlo en el sitio.

El tutorial es de perlmonkey y lo pueden encontrar en su forma original aquí.

Como siempre hago al traducir un tutorial, le cambio ciertas palabras y explico con mayor detalle lo que siento que necesita mayor explicación.

Creando nuestro servidor

Es impresionante lo sencillo que es crear un robusto servidor que dejará a tus amigos y colegas con la boca abierta usando perl.

Para esto usamos el módulo IO::Socket, que nos da una sencilla interface orientada a objetos para tomar control de nuestros sockets.

Para empezar, nuestro servidor necesita un socket para escuchar, comúnmente llamado "listening socket", a este socket es a donde nuestros clientes se conectan. No es cosa del otro mundo crear este socket:

#creando un socket para escuchar
my $listen_socket = IO::Socket::INET->new(LocalPort => 8000,
Listen => 10,
Proto => 'tcp',
Reuse => 1);

Esto creará un socket para escuchar en el host local bajo el puerto 8000, usando el protocolo TCP. El parámetro "Listen" es el máximo de clientes que podemos tener en "queue" o cola. Y "Reuse" te permite reiniciar el puerto 8000. Estos son los parámetros básicos que usaremos.

Para manejar un cliente intentando conectar, la siguiente línea creará el socket para el cliente:

my $connection = $listen_socket->accept;

Aquí la variable $connection es un objeto de socket, que puede ser manejado como un objeto de archivo. Así que puedes imprimir o leer de él como lo harías con un archivo:

#Escribir al socket del cliente
print $connection "Hello Client!";

#leer del socket del cliente
my $message = <$connection>;

Lo siguiente que debemo que ver tiene que ver con el "fork" de nuestro servidor. Cuando un proceso hijo muere, no libera los recursos del sistema hasta que el proceso padre reconoce que esta muerto con la llamada de las funciones "wait" o "waitpid".

Como los servidores por lo general trabajan por mucho tiempo, y se terminan varios procesos hijos, se hace necesario asegurarnos de que el proceso padre se este enterando que los procesos hijos se están muriendo, si no se da cuenta el proceso padre, entonces los procesos hijos se convierten en "zombies".

Los servidores normalmente se la pasan la mayoría de su tiempo en la llamada "accept" esperando llamadas de nuevos clientes esperando conectarse. Pero el problema, es que también está esperando que los procesos hijos se mueran, entonces ¿cómo puede hacer el servidor dos cosas a la vez? Fácil: con señales.

Cuando un proceso hijo se muere manda una señal SIGCHLD al proceso padre. Entonces nuestro servidor necesita registrar una llave de señal para llamar a la función "waitpid" cada vez que se manda la señal SIGCHLD:

#crear la llave de la señal
$SIG{CHLD} = \&REAPER;

#rutina para prevenir los zombies
sub REAPER{
#WNOHANG significa regresar inmediatamente si no hay hijo.
while ((waitpid(-1, WNOHANG)) >0 ){}

#resetea la señal para que el otro proceso hijo muera
$SIG{CHLD} = \&REAPER;
}

Entonces ya para nuestro player de MP3 no hay mucho que añadir.

Básicamente el servidor empieza, entonces un cliente llega (por ejemplo xmms o mpgl23) abriendo un socket. Entonces el servidor hace un "fork" y regresa un socket al proceso hijo. Finalmente en proceso padre regresa para seguir escuchando y esperando a otro cliente. El proceso hijo simplemente irá en un loop continúo tocando canciones de nuestro lista de canciones aleatoriamente hasta que el cliente deje de escuchar.

NOTA: Puedes crear una lista de canciones MP3 usando el Winamp. O usando algo como: find / -name "*.mp3" > playlist.m3u

Código del servidor

Aquí esta el código final de nuestro servidor:

#!/usr/bin/perl -w

use strict;
use IO::Socket;

#tomar el puerto a controlar o por default 8000
my $port = $ARGV[0] || 8000;

#ignorar procesos hijos para evitar zombies
$SIG{CHLD} = 'IGNORE';

#crear el socket a escuchar
my $listen_socket = IO::Socket::INET->new(LocalPort => $port,
Listen => 10,
Proto => 'tcp',
Reuse => 1);

#asegurarnos que estamos controlando el puerto
die "Cant't create a listening socket: $@" unless $listen_socket;

warn "Server ready. Waiting for connections ... \n";

#esperar conexiones
while (my $connection = $listen_socket->accept){
my $child;
# crear el fork para salir
die "Can't fork: $!" unless defined ($child = fork());

#¡el hijo!
if ($child == 0){

#cerrar el puerto para escuchar el proceso hijo
$listen_socket->close;

#llamar la funcion principal del hijo
play_songs($connection);

#si el hijo regresa salte
exit 0;
}
#¡soy el padre!
else{

#¿quién se conectó?
warn "Connecton recieved ... ",$connection->peerhost,"\n";

#cerrar la conexión, ya fue mandada a un hijo
$connection->close();

}
#regresa a escuchar otras conexiones
}

sub play_songs{

my $socket = shift;

#sacar todas las canciones posibles
open PLAYLIST, "playlist.m3u" or die;
my @songs = <PLAYLIST>;
close PLAYLIST;
chomp @songs;

#crear el creador de canciones random
srand(time / $$);

#crear un loop eterno hasta que el cliente deje de escuhar
while(){

#Mandar el header necesario
print $socket "HTTP/1.0 200 OK\n";
print $socket "Content-Type: audio/x-mp3stream\n";
print $socket "Cache-Control: no-cache \n";
print $socket "Pragma: no-cache \n";
print $socket "Connection: close \n";
print $socket "x-audiocast-name: My MP3 Server\n\n";

#seleccionar una canción aleatoria de tu lista
my $song = $songs[ rand @songs ];

#que cancion estamos tocando
warn( "play song: $song\n");

#abrir la cancion o intentar con otra
open (SONG, $song) || next;

binmode(SONG); #para usarios de windows

my $read_status = 1;
my $print_status = 1;
my $chunk;

#Esta parte imprime el binario al socket
#Lo hace lo más rápido posible
while( $read_status && $print_status ){
$read_status = read (SONG, $chunk, 1024);

if( defined $chunk && defined $read_status){
$print_status = print $socket $chunk;
}

undef $chunk;
}
close SONG;

unless( defined $print_status ){
$socket->close();
exit(0);
}
}
}


Conectando a tus clientes

Ya tienes tu servidor listo, pero ahora, ¿cómo conectas a tus clientes? Es realmente muy sencillo.

Si estas usando Winamp todo lo que tienes que hacer es ir a Play->Location y poner:

http://localhost:8000

Aquí debes de cambiar "localhost" por el ip de tu computadora si quieres conectarte remotamente, y si usaste otro puerto lo debes de poner en vez de 8000. Lo mismo haces con cualquier player de mp3 como el xmms.

Preguntas Frecuentes

¿Cuántos clientes a la vez puedo tener conectados?

En realidad todo depende de tu ancho de banda. Recuerda que cada cliente que se conecta usa ancho de banda, así que debes de calcular para que no tengas clientes que puedan usar tan poco ancho de banda que les sería imposible bajar el mp3.


Tengo firewall en mi computadora ¿Esto afecta?

Si. Muchos de tus clientes podrían tener bastantes problemas al conectar debido a esto.


No tengo IP único, mis clientes aún así podrían conectar a mi servidor

No. Desgraciadamente es necesario que tengas un ip único para que tu computadora pueda ser accesada.

¿Quiéres más tutoriales como este? Escribir tutoriales toma una gran cantidad de tiempo y esfuerzo. Si este tutorial te ayudó a aprender o a solucionar algo, por favor considera dejar alguna donación en apoyo a Perl en Español.

Cliquea en el botón de abajo para dejar tu donación por medio de PayPal.

Comparte:
Categorías de Tutoriales:
En Nuestros Foros:

    Software error:

    junk after document element at line 1, column 32, byte 32 at /usr/lib64/perl5/vendor_perl/XML/Parser.pm line 187.
    

    For help, please send mail to the webmaster ([email protected]), giving this error message and the time and date of the error.

  • Entra a los foros »
Socializa:
Síguenos por Twitter

Suscríbete GRATUITAMENTE al Boletín de Perl en Español

Perl en Español es mantenido con Movable Type
Todo el contenido de Perl en Español está bajo una licencia CC:
Creative Commons License