Categorías destacadas
programacion php    
Artículo
4
¡votar!

 Sockets en Windows Phone 7.5 - Servidor de Chat

En el primer artículo de esta serie, explicamos, de forma muy básica, que es un socket y la implementación que se ha realizado en Windows Phone 7.5. En la segunda parte, vimos cómo crear el cliente del ejemplo que mostramos al final de ese primer artículo. En esta tercera parte, veremos cómo implementar el servidor para Windows.

1.  El servidor

Se trata de un programa en WPF que se implementa en el proyecto “CharTest.Server” del ejemplo, que podéis descargar desde www.duefectucorp.com/programacion/ChatTest.zip

Como recordatorio, podemos ver la solución completa, tal como nos lo muestra el explorador de soluciones de Visual Studio:



a) MainWindow

La “MainWindow” implementa un sencillo interface de usuario, donde tenemos un TextBox que nos permite seleccionar el puerto del servidor, un botón para iniciar el servidor y otro para cerrarlo.

Como en el cliente, un control ListBox nos mostrará el historial de acciones.



Como ocurría en el cliente de chat, “MainWindow” no realiza acciones directas, sino que llama a la clase “SocketServer”.

b) SocketServer

Esta clase implementa tres métodos públicos: “Abrir”, “Cerrar” y “EnviarATodos”. Los datos recibidos los devuelve a través de un método delegado que se pasa al método “Abrir”.

2. Implementando el servidor

El servidor utiliza dos hilos de ejecución que se inician a través del método Abrir. El primer hilo de ejecución se encarga de aceptar las peticiones de conexión, mientras que el segundo se dedica a escuchar todos los sockets conectados.

a) Aceptando conexiones

El hilo de ejecución que se encarga de aceptar las peticiones de conexión, lo implementa el método privado “Escuchar”:

private void Escuchar()
{
// Se crea un TCPListener que escuchar cualquier IP local con el puerto indicado
TcpListener listener = new TcpListener(IPAddress.Any, puerto);
listener.Start();
// Mientras se esté escuchando...
while (escuchando)
{
// Esperar hasta que se producza una petición de conexión
if (listener.Pending())
{
// Iniciamos la conexión
listener.BeginAcceptSocket(new AsyncCallback(DoAcceptSocketCallback), listener);
}
// Una pausa refrescante
Thread.Sleep(100);
}
// Si ya no se escucha, detenemos el TCPListener
listener.Stop();
}


Utilizamos el objeto TCPListener para poder aceptar múltiples conexiones al mismo tiempo.

Esperamos una petición de conexión con el método “Pending()” de TCPListener, y cuando esta petición se produce, la aceptamos con el método “BeginAcceptSocket(…)”, que inicia el método “DoAcceptSocketCallback”.

Este método, acepta la petición, obteniendo un socket, que añade a un diccionario de sockets, empleando como clave la dirección IP remota (del llamante). Es importante ver que este método utiliza un bloqueo del tipo “Lock”, con el fin de evitar errores de concurrencia:

private void DoAcceptSocketCallback(IAsyncResult ar)
{
// Recuperamos el TCPListener
TcpListener listener = (TcpListener)ar.AsyncState;
// Extraemos el nuevo socket para la conexión
Socket clientSocket = listener.EndAcceptSocket(ar);
// Extraemos la IP remota (cliente) del socket
string IPRemota = clientSocket.RemoteEndPoint.ToString();
// Bloqueamos la escucha de todos sockets, para evitar errores de concurrencia
lock (bloqueoSockets)
{
// Añadimos la IP y el socket al cliente
sockets.Add(IPRemota, clientSocket);
}
}


b) Escuchando a los clientes

En el segundo hilo de ejecución nos dedicamos a escuchar todos los sockets abiertos, y a depurar los que se encuentren cerrados:

private void EscucharSockets()
{
// Mientras se esté escuchando...
while (escuchando)
{
// Interceptamos e ignoramos posibles errores de comunicación
try
{
// Si el proceso está bloqueado, esperamos hasta que termine
lock (bloqueoSockets)
{
// Recorremos todos los sockets del diccionario de sockets
foreach (var socD in sockets)
{
// En soc almacenamos el Socket
var soc = socD.Value;
// En IPRemota la IP remota (cliente) del socket
var IPRemota = socD.Key;
// Si el socket está conectado
if (soc.Connected)
{
// Si hay datos en el bufer
int len = soc.Available;
if (len > 0)
{
// Creamos un bufer local
byte[] bufer = new byte[len];
// Recibimos los datos
soc.Receive(bufer, len, SocketFlags.None);
// Convertimos el array de bytes a cadena de texto
string datos = Encoding.UTF8.GetString(bufer);
// Invocamos el método de recepción de datos
callBackDatosRecibidos(IPRemota, datos);
// Enviamos los datos a todos los sockets conectados
EnviarATodos(string.Format("{0}: {1}", IPRemota, datos));
}
}
else
{
// Si el socket no está conectado lo eliminamos del diccionario
sockets.Remove(IPRemota);
// Desconectamos el socket, lo cerramos y liberamos los recursos
soc.Disconnect(false);
soc.Close();
soc.Dispose();
soc = null;
}
}
}
}
catch { }
}
// Si termina la escucha, eliminamos todos los sockets
lock (bloqueoSockets)
{
foreach (var socD in sockets)
{
var soc = socD.Value;
var IPRemota = socD.Key;
sockets.Remove(IPRemota);
soc.Disconnect(false);
soc.Close();
soc.Dispose();
soc = null;
}
}
}


Básicamente, bloqueamos el proceso para evitar errores de concurrencia en el diccionario de sockets, y empezamos a recorrer todo la lista de sockets que existen en el diccionario.

Si el socket está conectado, miramos si tiene datos en el bufer. Si es así los recuperamos y se los enviamos a todos los clientes conectados utilizando el método “EnviarATodos”. Al mismo tiempo, se lo notificamos a “MainWindow” utilizando el método delegado definido en el método “Abrir”.

En caso de encontrarse desconectado, destruimos el socket y lo eliminamos del diccionario de sockets.

Nótese la inclusión de un Try…Catch, que se encargará de interceptar excepciones de comunicaciones. No se ha querido complicar más el ejemplo, pero se puede implementar un sistema de procesado y recuperación de errores de conexión.

Por último, cuando se cierra el servidor, procedemos a destruir todos los sockets y eliminarlos del diccionario.

c) Enviando a todos

Quizás el método más simple de todos, es el encargado de enviar los datos a todos los sockets conectados:

public bool EnviarATodos(string datos)
{
try
{
// Convierte la cadena de datos en un array de bytes
byte[] bufer = Encoding.UTF8.GetBytes(datos);
// Recorrer todos los sockets
foreach (var soc in sockets.Values)
{
// Enviar array de bytes por el socket
soc.Send(bufer);
}
// Proceso completado
return true;
}
catch
{
return false;
}
}


Simplemente, recorremos todos los sockets del diccionario, enviando los datos por ese socket.

Un Try…Catch, pobremente implementado en este caso, nos ayudará a “comernos” los errores de conexión.

d) Cerrar

El proceso de cierre consiste en poner a false la variable privada “escuchando”, y esperar a que los dos hilos de ejecución se paren y destruyan todos los sockets abiertos:

public bool Cerrar()
{
// Establecemos el campo de escucha a false, para que los métodos Escuchar y
// EscucharSockets finalicen
escuchando = false;
// Mientras queden sockets en el diccionario de sockets
while (sockets.Count > 0)
{
// Esperar 100 ms
Thread.Sleep(100);
}
// Se han cerrado todos los sockets
return true;
}


3. Conclusiones

En esta serie de artículos, he pretendido mostrar cómo implementar los sockets en Windows Phone 7.5, indicando sus limitaciones, y una forma de “saltárselas”.

Resumiendo: Los sockets en Windows Phone 7.5 solo se pueden usar en “modo cliente”.

Espero que esta serie de artículos haya sido de vuestro agrado, y estaré encantado de contestar a vuestras preguntas a través de mi cuenta de twitter (@Duefectu).

Autor del artículo: Juan Segura – Director de I+D+i de SD Assessors, S.A.
   
Publicado por:
Angel Carrero
Recomendar
a un amigo
Compartir
en redes
 
Comentarios
 
BBDD
Entornos de desarrollo
Entretenimiento
Herramientas
Internet
Lenguajes de script
Lenguajes imperativos
Lenguajes orientados a objeto
Otros lenguajes
Plataformas
Teoría
Varios
Copyright © 1998-2011 Programación en Castellano. Todos los derechos reservados
Datos legales | Politica de privacidad | Contacte con nosotros | Publicidad

Diseño web y desarrollo web. Un proyecto de los hermanos Carrero.

Red internet:
Juegos gratis | Servidores dedicados
Más internet: Password | Directorio de weblogs | Favicon