viernes, 14 de abril de 2017

Creación de Cluster de Linux – CentOS 7

Introducción

Este post tiene la finalidad de mostrar como crear un Cluster con dos servidores de Linux, utilzando el sistema operativo CentOS y Pacemaker.
  • El Cluster trabajará de la siguiente manera:
  • Cada servidor de Linux representa un Nodo en el cluster.
  • Los nodos del Cluster compartirán una IPVirtual, la cual es el primer recurso que se configura en el Cluster.
  • El nodo Activo será el primer servidor que se logre levantar.
  • Si el Nodo que tiene asignada la IPVirtual deja de responder entonces el otro Nodo tomara posesión de la IPVitual.
  • Solo se pude intercambiar la IPVirtual si el Nodo que tiene la posesión deja de responder, ya sea haya sido desactivando, apagando o por medio de una falla de red en el Nodo.

Instalación y Configuración de los Servidores

Este ejemplo utiliza es ejemplificado por medio de maquinas virtuales, utilizando en VMWare Fusion. Las características de los servidores y datos de las cuentas que serán utilizadas son:

  • Versión de Fedora: Centos 7 (x86_64 Bits)
  • Maquina virtual con:
  1. 2 cores
  2. 10 GB de DD
  3. 1.2 GB de RAM

Usuario de administración root
  • pwd: *********

Usuario de Middleware, con privilegios de administrador.
  • user: srvmdlw
  • pwd: **********

En este caso de nombra a la entidad Middleware, pensando que el Cluster será una capa intermedia para acceder a diversos servicios.
Nodo1
  • Hostname: mddlwr.n1
  • IP PUB: 192.168.13.231
  • MAC ADDRESS1: 00:50:56:31:5B:AA

Nodo 2
  • Hostname: mddlwr.n2
  • IP PUB: 192.168.13.232
  • MAC ADDRESS1: 00:50:56:3E:FA:54

Nodo Middleware, quien representa al Cluster.
  • IP Virtual: 192.168.13.230
  • User: hacluster
  • PWD: *********
  • ClusterName: mddlwr.cluste

Instalación y Configuración de Linux CENTOS 7

Los paquetes que se deben instalar en cada uno e las instalaciones de Linux.

Asignación de IP’s

Utilizar los datos de acuerdo al nodo del Middleware que se esté instalando (Nodo 1 o Nodo 2), descritos en la sección Datos de Instalación.
IP Pública
IP Privada

Instalación del Cluster

El comando para instalar los paquetes de High Availability debe ser realizado como usuario ROOT, ademas de tener los servidores conectados a Internet.
Instalar el paquete de Corosync
# yum -y install corosync pacemaker pcs
Si ya se tiene instalada la última actualización, se obtendrá el siguiente resultado:

Abrir puertos de Firewall

Abrir puertos UDP 5404 y 5405, para Corosync:
# iptables -I INPUT -m state --state NEW -p udp -m multiport --dports 5404,5405 -j ACCEPT
Abrir TCP 2224, para PCS:
# firewall-cmd --permanent --add-port=2224/tcp
Permitir el tráfico IGMP:
# iptables -I INPUT -p igmp -j ACCEPT
Permitir el tráfico MULTICAST:
# iptables -I INPUT -m addrtype --dst-type MULTICAST -j ACCEPT
Antes de guardar los cambios, asegúrese de actualizar el servicio de iptable.
# yum install iptables-services
Guardar los cambios hechos:
# service iptables save
iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ]
Para revisar las reglas de Firewall, utilice el siguiente comando:
# iptables -L -n

Preparando tabla de Hostnames

Para todos los nodos, se encontrarán por medio del host name y la IP privada, por lo que la edición debe ser en el archivo de Hosts, en ambos nodos.

Cambiar password del usuario hacluster

Ejecutar el comando en ambos nodos.
El usuario ya debería estar creado, el uso de este comando es para confirmar que está creado:
# useradd hacluster
Asignación de password
#passwd hacluster
Los datos del password están descritos en la sección Datos de Instalación.

Iniciar servicio PCS

Ejecutar el comando en ambos nodos.
# systemctl enable pcsd.service
# systemctl start pcsd.service

# systemctl enable corosync.service
# systemctl start corosync.service

# systemctl enable pacemaker.service
# systemctl start pacemaker.service

Autorizando los servidores

Ejecutar el comando en ambos nodos.
# pcs cluster auth mddlwr.n1 mddlwr.n2
El comando le solicitará identificarse como el usuario hacluster.  El resultado debe indicarse que se logró identificar en ambos nodos.

Creación de cluster con sus nodos

Ejecutar este comando en el nodo 1.
# pcs cluster setup --name mddlwr.cluster mddlwr.n1 mddlwr.n2
Este comando creará el archivo /etc/corosync.conf, el cual contendrá la configuración de clúster.
Después de la creación se puede iniciar el clúster, aunque todavía falte configurarlo. Para iniciarlo se utiliza el comando:
# pcs cluster start --all
Este comando iniciará los nodos del cluster:

Revisando el estado del Clúster

Para revisar el estado del Clúster se debe utilizar el siguiente comando:
# pcs status cluster
En este caso todavía no se tiene configurado algún recurso:
2 nodes and 0 resources configured
Online: [ mddlwr.n1 mddlwr.n2 ]
Para revisar el estado de los nodos se utiliza el siguiente comando:
# pcs status nodes
Para revisar el estado de los miembros del cluster:
# corosync-cmapctl | grep members
Para obtener información del grupo:
# pcs status corosync
Para obtener información completa del cluster y sus servicios:
# pcs status

Configuración del Cluster

Se debe utilizar el usuario root para ejecutar lo comandos de esta sección y deben ser ejecutados en ambos nodos, caso de este ejemplo.
Antes de iniciar se revisa si se tiene errores configuración en el cluster, por lo que se utiliza el comando:
# crm_verify -L -V
El cual nos muestra una lista de errores, en donde se indica que no tiene configurado la propiedad STONITH, el cual es un mecanismos que asegura que no se tengan dos nodos activos, lo que provocaría que se estaría compitiendo por la IP compartida.
En este caso se tiene una configuración sencilla de cluster, por lo que deshabilitamos esta propiedad con el siguiente comando:
# pcs property set stonith-enabled=false
Sobre la configuración del cluster, se tiene una propiedad que indica el quórum mínimo requerido para que el Cluster esté operando. Esta propiedad es útil en el caso de que se tengan varios nodos; pero en el caso de nuestro cluster solo se cuenta con dos nodos, por lo que es recomendable que se desactive esta propiedad, pues la cantidad mínima requerida de nodos levantados es de un (1) nodo.
El comando para ignorar el umbral de nodos mínimos es:
# pcs property set no-quorum-policy=ignore
Para revisar los cambios hechos, STONITH y mínimo quórum, utilice el siguiente comando:
# pcs property

Asignación de IP Virtual

Una vez configurado los aspectos previos del Cluster, se debe asignar l IP que compartirán en común los nodos.
En este caso, del cluster, se considera que la creación de la IP Virtual como un nuevo recurso, por lo que se crea un nuevo recurso con un tiempo de 15 segundos de poleo. El tiempo de poleo se ajusta de acuerdo a sus necesidades.
# pcs resource create virtual_ip ocf:heartbeat:IPaddr2 ip=192.168.13.230 cidr_netmask=32 op monitor interval=15s
En este ejemplo se ejecuto el comando desde el nodo 1, creando el recurso en ambos nodos.
Para revisar la configuración de los recursos se utiliza el siguiente comando:
# pcs status resources
Ahora la IP Virtual ya puede ser alcanzada por otro dispositivo dentro de la red:

Activando / Suspendiendo un Nodo del Cluster

Como procedimiento opcional, se puede desactivar o activar nodos con los siguientes comandos:
Desactivar un Nodo, en este ejemplo es el nodo 2.
# pcs cluster unstandby mddlwr.n2
Activar un Nodo, en este ejemplo es el nodo 2.
# pcs cluster standby mddlwr.n2
De esta manera se puede desactivar un nodo para que el otro se quede activo con la IP Virtual, dato que se puede consultar al consultar el estatus del cluster:

Implementando HTTP Server de Apache como un recurso del Cluster

Firmado  con super usuario ROOT
En este caso implementaremos el servicio de Apache HTTP para ser parte de los recursos del cluster, de tal manera que si se llegase a detener el servicio httpd, se active el siguiente nodo.
Instalación del software de Apache:
# yum install httpd
Abriendo en el Firiwall el Puerto 80:
# firewall-cmd --permanent --add-port=80/tcp
# firewall-cmd --reload
Habilitar servicio
# systemctl enable httpd
Detener el servicio, si se encuentra ejecutándose
# systemctl stop httpd
Creamos el mecanismo que permitirá al cluster monitorear el http server. Este mecanismo consiste en un archivo de configuración, llamada serverstatus.conf, ubicado en la ruta /etc/httpd/conf.d, con el siguiente contenido:
Listen 127.0.0.1:80

SetHandler server-status
Order deny,allow
Deny from all
Allow from 127.0.0.1

Este archivo debe estar contenido en ambos nodos, o los que formen parte del cluster.
Ahora se debe comentar, del archivo de configuración del http server, la configuración correspondiente al Listener, para evitar que se trate de escuchas en múltiple ocasiones sobre el mismo puerto. Esta paso se lleva a cabo con el sigueinte comando:
# sed -i ‘s/Listen/#Listen/’ /etc/httpd/conf/httpd.conf
Antes del comando:
Después del comando:
Iniciamos el servidor http, en ambos nodos:
# systemctl start httpd
Debido a que se tiene desactivado el Listener, no será posible realizar peticiones al servidor http. La manera de ver que este trabajando el http server es po comando, realizando una petición al localhost, solicitando el estado del servidor, mecanismo que se implemento con el archivo serverstatus.conf:
# wget http://127.0.0.1/server-status
Esta validación se debe llevar a cabo en ambos nodos.
El siguiente paso es el de crear en el http server un Listener hacia el puerto 80 y utilizando la IP Virtual. Para esto se modificará el archivo de configuración del http server, en ambos nodos, utilizando los siguientes comandos, en ambos nodos:
# echo "Listen 192.168.13.230:80"|tee --append /etc/httpd/conf/httpd.conf
Antes del comando:
Después del comando:
Reiniciar el servicio de http server. Una vez reiniciado el servicio será posible realizar peticiones desde equipos externos.
Una vez que se tiene este mecanismo/interfaz, el cluster tendrá la capacidad de monitorear y controlar el servidor http. Por lo que el siguiente paso es incluir el servidor http como un recurso más del cluster, utilizando el siguiente comando en cualquiera de los nodos disponibles del cluster:
# pcs resource create webserver ocf:heartbeat:apache configfile=/etc/httpd/conf/httpd.conf 
statusurl="http://localhost/server-status" op monitor interval=25s
Antes de ejecutar el comando se puede consultar el estado del cluster y apreciar que se encuentra configurado un solo recurso:
Después de la ejecución se pueden apreciar el nuevo recurso:
En este punto el cluster solo puede asignarle la IP a un solo nodo, por lo que al tratar de levantar el http server al nodo no activo se tendrá el error de que no se pudo asociar el puerto a la IP establecida en la configuración. Esta situación se debe arreglar configurando una restricción en el cluster, utilizando el siguiente comando:
# pcs constraint colocation add webserver virtual_ip INFINITY
Este comando indica que los recursos dependen de la IP Virtual, pero ademas se requiere evitar que el webserver inicie antes que se asigne una IP Virtual. Para esto se debe crear otra restricción, la cual indica cuál debe ser el orden de arranque de los recursos:
# pcs constraint order virtual_ip then webserver
Para revisar los recursos configurados hasta el momento, utilice el comando:
# pcs resource
Para revisar las restricciones configuradas hasta el momento, utilice el comando:
# pcs constraint
Pare que inicie por completo el cluster:
Revise el estado del cluster en ambos nodos:
Nodo 1:
Nodo 2:
En ambos nodos se aprecia que el Cluster está operando exitosamente, así como también se aprecia que los recursos están trabajando en el nodo 1:

Instalando Tomcat

Versión de Tomcat: 9.1
Ruta de instalación de Tomcat:
/home/srvmdlw/tomcat
Ruta de instalación de JRE:
/lib/jvm/jre
Abrir puertos 8080:
 firewall-cmd --permanent --add-port=8080/tcp
 firewall-cmd --reload
Crear recurso de Tomcat en Pacemaker:
# pcs resource create tomcat ocf:heartbeat:tomcat params java_home=/lib/jvm/jre catalina_home=/home/srvmdlw/tomcat op monitor interval=10s
La restricción para que arranque después de la IP Virtual:
# pcs constraint colocation add webserver virtual_ip INFINITY
# pcs constraint order virtual_ip then webserver

# pcs constraint colocation add tomcat virtual_ip INFINITY
# pcs constraint order webserver then tomcat
Para conocer el Id de los Constrains:
# pcs constraint list --full
Para remover la restricción anterior:
# pcs constraint order remove virtual_ip webserver
Asignar el nuevo orden de arranque:
# pcs constraint order virtual_ip then webserver
# pcs constraint order webserver then tomcat

Procesos para evaluar el funcionamiento del Cluster

Recomiendo utilizar los siguientes comandos para revisar el funcionamiento del Cluster:
Estado General:
#pcs status
Estado del Cluster:
#pcs status cluster
Estado de los Recursos:
#pcs status resource
Los logs del CoroSync se encuentran en la siguiente ruta:
/var/log/cluster/corosync.log

Referencias

Guía para crear un Cluster sencillo:
Monitoreo de Apache:
Manual de Pacemaker:
Configuración de Tomcat para PCS

lunes, 10 de abril de 2017

Uso de Sockets con Alto Desempeño (High Performance)

Recientemente me tope con un problema que desafío mi manera de atender multiples conexiones por  Sockets (Mi Paradigma),  en este caso C#; y es que ese fue el problema principal: Usar un paradigma sin considerar la carga de trabajo que recibiría el servicio.

El Entorno de Trabajo


  • La aplicación es un Windows Service, hecho en C#, el cual levanta un Socket Server para atender multiples peticiones (Requests) de manera simultánea, por medio de TCP.
  • Cada Request implica leer el comando de entrada, el cual tiene un tamaño fijo de 612 bytes.
  • Una vez que se haya terminado de atender el Request se procede a enviar la respuesta (Response), con un tamaño fijo de 612 bytes.
  • Una vez que se termino de enviar el response se debe cerrar la conexión de socket.
  • En hora pico se tiene una carga promedio de 15 mil Requests por Hora.

Mi Paradigma

El paradigma que utilizaba para atender cada una de las peticiones era:


  1. Crear un Thread para levantar un Socket Server.
  2. Por cada petición de conexión que recibía el Socket Server se crea un nuevo Thread para atender la petición del Socket Client.
  3. Dentro del nuevo Thread se procede a leer los datos que envío el Socket Cliente (Request).
  4. Una vez que se tiene el Request se procede a trabajarlo dentro del mismo thread.
  5. Una vez que se haya procesado el Request se envía la respuesta (Response).
  6. Cuando se haya terminado de enviar el Response se cierra el Socket.
  7. Una vez terminado el envío del Response se cierra el Socket.


El problema que tiene este paradigma es que bajo una carga de trabajo, como la que se describe en la sección "El Entorno de Trabajo" es que se presento una degradación en la atención de las peticiones y el servicio, presentando los siguientes sintomas:


  • Retraso de hasta 40 segundos para recibir y responder una petición.
  • Consumo de procesador de hasta 50%, antes de colapsar la atención de los Request.
  • Las aplicaciones que realizaban la operación de Socket Client, no recibían la respuesta a tiempo y generaban una excepción de TimeOut en la lectura del Response.

Considere que la atención de la petición puede llevar al Thread a esperar, por medio de los metodos Sleep y Wait.

La Solución


Después de batallar por algunos días, la respuesta al dilema fue atender las peticiones de manera Asíncrona y no por medio de un nuevo Thread dedicado.

El crear un Thread para atender una petición de Socket es una solución viable, siempre y cuando la carga de trabajo y el tiempo de respuesta no seam factores determinantes en el desempeño de tu aplicación. Pero en el caso de una aplicación de Alto Desempeño, en el que se requiere atender varias peticiones simultáneas en el menor tiempo posible, no es una solución que pueda funcionar.

Para el caso de C# se requiere del uso de BeginRead, EndRead, BeginWrite y EndWrite, de NetworkStream. Estos métodos permiten al Socket Clientes, en conjunto con el Socket Server, de recibir la y enviar datos de manera asincrónica, sin necesidad de crear un nuevo Thread.

La implementación fue la siguiente:


  1. Se conserva el Thread dedicado a aceptar las peticiones de conexión de los clientes. 
  2. Por cada petición de conexión se creará un nuevo objeto Request, asignándole el Socket Client que se encargará de atender la comunicación Client-Server.
  3. El objeto Request será el encargado de obtener el Request, procesarlo y enviar el Response, de manera asincrónica.
  4. El proceso de recepción inicia con la invocación del método Request::startReceiveAsync.
  5. El método startReceiveAsync invoca al método BeginRead, de NetworkStream, especificando cuantos bytes debe leer antes de invocar el método de retroalimentación Request::requestReceived, el cual se especifica en el método BeginRead.
  6. Al momento de invocar BeginRead se continua con la ejecución del programa sin esperar a que se haya recibido el Request, permitiendo con esto terminar la ejecución del método startReceiveAsync y regresando el CallStack al punto en donde se espera una nueva conexión de socket.
  7. Una vez que es invocado el método requestReceived, que es cuando se termino de leer los datos,  es importante invocar el método EndRead, del NetworkStream, para asegurarse de que se haya terminado de recibir toda la cantidad de datos solicitados en el método BeginRead. Se recomiendo utilizar EndRead en el caso de que se espera recibir grandes cantidades de información.

La secuencia de invocación entre de los métodos startReceiveAsyncrequestReceived se lleva a cabo de manera asincrónica, pues el objeto NetworkStream se encarga de invocarlos a medida que los datos se encuentren disponibles para su lectura. Todo esto sin tener que utilizar un bloqueo en los sockets o en el Thread de atención al cliente.

El código para aceptar la conexión y recepción del Request es:



 

/* Metodo Server::run()*/
public void run()
{
    server = new TcpListener(IPAddress.Any, port);
    server.Start();
    
    log.Info("Iniciando espera de peticiones en el puerto [" + port + "]");

    while (keepRunning)
    {
        try
        {
            TcpClient socket = server.AcceptTcpClient();
            if (keepRunning)
                RequestManager.createRequestForEvalue(socket, idLayout);
        }
        catch (Exception ex)
        {
            log.Error("Se detecto un error en el puerto para atencion de peticiones. ERR: " + ex.Message);
            log.Error(ex.StackTrace);
        }
    }

    log.Info("Servicio deteniendo.");
}


/* Metodo RequestManager.createRequestForEvalue*/
public static bool createRequestForEvalue(TcpClient socket, int idLayout)
{
    Request req = null;
    req = new Request(socket,idLayout);

    registerRequest(req.ID,req); //Registra el Request, para su posterior uso.

    req.startReceiveAsync(); //Since V4.20
    return true;
}


/*Metodo Request.startReceiveAsync*/
public void startReceiveAsync()
{
    try
    {
        log.Info("[" + id + "] Iniciando la lectura del Request.");
        requestBuffer = new byte[BUFFER_SIZE];
        NetworkStream nst = socket.GetStream();
        nst.BeginRead(requestBuffer, 0,BUFFER_SIZE, this.requestReceived, nst);
    }catch(Exception ex)
    {
        log.Error("[" + id + "] Se presento un problema al iniciar la lectura del Request: " + ex.Message);
        closeSocket();
    }
}


/*Metodo Request.startReceiveAsync*/
public void requestReceived(IAsyncResult ar)
{

    try
    {   
        NetworkStream nst = socket.GetStream();
        nst.EndRead(ar);
        string request = Encoding.UTF8.GetString(requestBuffer, 0, BUFFER_SIZE);
        log.Info("[" + id + "] Request recibido: [" + message +"]");
        RunForIVR(request);
    }
    catch (Exception ex)
    {
        log.Error("[" + id + "] Se presento un problema al recibir el Request: " + ex.Message);
        closeSocket();
    }

}

Dentro del procede de atención se puede requerir el mantener en espera el envío del Response hasta que se tenga una condición predeterminada En este caso se sugiere crear métodos asincrónicos y evitar utilizar los métodos Wait o Sleep, por que la convinación Thread-Socket-Wait/Sleep tiene altas probabilidades de bloquear el socket socket y, por consecuencia, la recepción y escritura de otros sockets.

Para estos casos se debe hacer uso de los métodos asincrónicos (async y await) para evitar detener la ejecución de los threads y evitar bloquear los sockets. Un ejemplo:

Suponga que dentro del método RunForIVR se debe invocar un Webservices por medio de un método estático llamado setSegment, de la clase TrackingRequest, que se encuentra en otra clase. La ejecución del método setSegment no debe interrumpir la ejecución de RunForIVR, pues no es importante el resultado de la operación setSegment para la fabricación del Response, pero se debe considerar que la invocación del Webservice puede retrasar la ejecución por un tema de Timout, por ejemplo.

En este caso se debe realizar lo sisguiente

  1. El método setSegment, de la clase TrackingRequest, debe declararse como asincrónico, usando el prefijo async.
  2. Utilizar el método asincrónico del Webservice en conjunto con la palabra await.
  3. El uso de await, del cliente de Webservice, junto con la declaración del método asincrónico seetSegment provocará que la secuencia del programa se regrese un nivel superior al CallStack, RunForIVR, para que continue su ejecución.
  4. Una vez que el Webservice haya terminado, el método setSegment continua su ejecución.
  5. Mientras se esta ejecutando el Webservice, el método RunForIVR invoca el método SendResponse.
  6. SendReposne invoca el BeginWrite, el cual invocará el método closeSocket.
  7. Sobre el método closeSocket se debe invocar el método EndWrite, el cual bloqueará el socket hasta que la transmisión de datos haya terminado.
  8. Terminada la transmisión de datos se cierra el socket, mientras que a la par se esta ejecutando el método setSegment, o ya termino de ejecutarse.

El código es el siguiente:

Clase TrackingRequest:

public static async Task setSegment(TrackingBean bean)
{
   System.Console.WriteLine("Inicia Async");
    try
    {
        BBVATrackingSoapClient ws = new BBVATrackingSoapClient("BBVATrackingSoap", url);
        ws.InnerChannel.OperationTimeout = TimeSpan.FromSeconds(1.5); // Se da un segundo de toleracncia para llevar a cabo la execucion del

        AddSegmentWS1 result = null;
        
        //Al momento de invocar el método AddSegmentAsync, se regresa el control al metodo superiro que invoco al metodo setSegment
        result = await ws.AddSegmentAsync(bean._key, bean._entity, bean._service, ACTION.OPEN_SEGMENT,CLOSE_DESC.NONE);

        if(result.AddSegmentWSResult.ERR_CODE == 0)
        {
            if (CFGManager.GetCFGManager().DebugActive)
                log.Debug("[" + bean._id + "] Operacion de segmento realizada con exito.");
        }       
        else
            log.Error("[" + bean._id + "] No se pudo registar la operacion del segmento, se obtuvo la siguiente respuesta: " + result.AddSegmentWSResult.ERR_DESC);

        result = null;
        ws = null;
    }
    catch(Exception ex)
    {
        log.Error("[" + bean._id + "] No se pudo registar la operacion del segmento. Error: " + ex.Message );
    }

    bean = null;
    
    System.Console.WriteLine("Termina Async");
    return 0;
}


El código restante para atender el Request e invocar el Response:

public void RunForIVR(string Request)
{
    string Response
 //Procesa el Request
 
 TrackingBean bean;
 //...
 TrackingRequest.setSegment(bean); //Este metodo es asincronico, por lo que no esperara a que finalice.

 //...
 //Obtiene el Response
 
 Response = "Informacion que sera enviada como respuesta";


 //Envia la respuesta
 SendResponse(Response);
}



public void SendResponse(string Response)
{
    StringBuilder sb = new StringBuilder();
    sb.Append(Response);
    sb.Append('\0', BUFFER_SIZE - Response.Length);
    string message = sb.ToString();

    log.Info("[" + id + "] ivrTrans CMD: [" + idCMD + "] RESPONSE: [" + Response + "]");

    NetworkStream nst = socket.GetStream();
    byte[] buffer = new byte[BUFFER_SIZE];
    for (int i = 0; i < BUFFER_SIZE; i++)
        buffer[i] = (byte)message.ElementAt(i);

    nst.BeginWrite(buffer, 0, BUFFER_SIZE, this.closeSocket, nst);// (new AsyncCallback(this.closeSocket));
}

public void closeSocket(IAsyncResult ar = null)
{
    
    try
    {
        if (ar != null) //Since 4.24
        {
            NetworkStream nst = socket.GetStream();
            nst.EndWrite(ar);
        }

        socket.Close();
        socket = null;
    }catch(Exception ex)
    {
        log.Warn("[" + id + "] Se presento un problema al cerrar el socket. Error: " + ex.Message + Environment.NewLine + ex.StackTrace);
    }
    log.Info("[" + id + "] Socket cerrado.");
}