Cualquier cosa que valga la pena se hace en equipo

Cualquier cosa que valga la pena se hace en equipo

martes, 2 de noviembre de 2010

SOCKETS y PORTS




OBJETIVO


Sockets y Ports

Un Socket es una abstracción.-

Representa un punto de intercomunicación entre dos procesos (por ejemplo cliente y servidor). Los procesos pueden estar en el mismo host, nodo o computadora o en nodos diferentes.-

En un host pueden correr diferentes procesos brindando diferentes servicios. Por ejemplo servicios de HTTP, de Telnet, de FTP, de SMTP, etc. Cada uno de estos procesos tiene una dirección que lo identifica unívocamente respecto de los otros. Esta dirección se llama PORT.

Generalmente el servicio de HTTP se brinda en el port 80, el de Telnet en el 23, FTP en el 21 y SMTP en el 25. Decimos entonces que un PORT es una dirección relativa. Identifica al proceso pero no identifica al host en el que corre dicho proceso.

En una red TCP/IP como Internet cada host tiene una identificación única llamada número IP.

Un SOCKET es un número IP + un PORT. Por lo tanto es una dirección absoluta ya que identifica unívocamente un proceso en toda la red.


SOCKET

La especificación Windows Sockets define una interfaz de programación en Windows basada en el paradigma de sockets del BSD (Berkeley Software Distribution).-

Esta biblioteca, escrita en lenguaje C por ingenieros, profesores y estudiantes de la Universidad de Berkeley, fue pensada para la comunicación entre procesos que se ejecutan en la misma computadora o en computadoras de una misma red.-

EL software fue escrito originalmente como una API (Application Programming Interface) y el código que figura a continuación es un pequeño esqueleto que muestra como es la secuencia de inicio y cierre de un proceso de comunicación, pensando en un solo thread de ejecución:


#include <sys/types.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <stdio.h>

#include <stdlib.h>

#include <strings.h>

int main()

{

//Inicio

int32_t i32SocketFD = socket(AF_INET, SOCK_STREAM, 0);


if(0 == i32SocketFD)

{

printf("no se puede crear el socket");

exit(-1);

}

struct sockaddr_in stSockAddr;

bzero(&stSockAddr, sizeof(stSockAddr));


stSockAddr.sin_family = AF_INET;

stSockAddr.sin_port = htons(1100);

stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);


//Enlace

if(-1 == bind(i32SocketFD,(struct sockaddr*) &stSockAddr, sizeof(stSockAddr)))

{

printf("error: bind fallido");

exit(-1);

}


//Escucha

if(-1 == listen(i32SocketFD, 10))

{

printf("error: listen fallido");

exit(-1);

}


for(; ;)

{

int32_t i32ConnectFD = accept(i32SocketFD, NULL, NULL);


if(0 > i32ConnectFD)

{

printf("error accept fallido");

exit(-1);

}

// acá es donde se debe escribir el código especifico ...

…..

//cierre

shutdown(i32ConnectFD, 2);

close(i32ConnectFD);

}

return 0;

}


¿Con que contamos en C#?

En C#, encontramos la funcionalidad de esta especificación, encapsulada en una clase llamada Socket, que es la que provee los métodos para la comunicación a través de la red así como la transferencia (sincrónica o asíncrona de datos).-

La clase Socket se encarga de proveer métodos y propiedades para la comunicación en red.-

Lo realmente interesante de esta clase es que encapsula la complejidad de la comunicación y una vez establecida el manejo de lo entrante y saliente se hace manejando flujos (streams).-

Estos flujos de comunicación pueden ser sincrónicos o asíncronos.-

Procesos de un solo thread y sincrónicos.-

Igual que en el ejemplo en lenguaje C visto al inicio de este manual, para procesos de un solo thread podemos utilizar los métodos específicos para modo de operación asíncrona: Listen(), Accept(), Bind(), Send() y Receive().-

Protocolo TCP

Estos métodos se utilizan en protocolos orientados a conexión (connection-oriented) como por ejemplo TCP.-

En este caso el servidor de la comunicación puede escuchar los pedidos con el método Listen .-

EL método Accept() procesa cualquier pedido de conexión entrante y retorna un Socket que es el que se utilizara para comunicar datos con el host remoto.-

Es este Socket el que se usa para llamar a los métodos Send() o Receive().-

Como en el caso general se desea indicar la dirección IP y el numero de puerto, se debe llamar al método Bind(), previo a llamar a Listen().-

En caso de no seguir esta secuencia, el proveedor del servicio subyacente asignara estos valores por nosotros. Es posible luego obtener la dirección IP y port asignados a nuestro Socket, del modo recién indicado.-

En el caso de querer conectarse a un servidor que esta escuchando requisiciones, se debe llamar al método Connect() y luego utilizar Send() y Receive() para envío y recepción de datos.-

// crear un socket

Socket listenSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

// enlazar el socket que escucha

IPAddress hostIP = (Dns.Resolve(IPAddress.Any.ToString())).AddressList[0];

IPEndPoint ep = new IPEndPoint(hostIP, port);

listenSocket.Bind(ep);

// escuchar

listenSocket.Listen(backlog);


Protocolo UDP

Si usamos un protocolo como UDP (protocolo sin conexión: connectionless), entonces no es necesario "escuchar" para conectarse. El método ReceiveFrom() es el que debe usarse para recibir los datagramas entrantes y el método SendTo() es el que debe usarse para el envio de datagramas al host remoto.

Procesos de mas de un thread y asincrónicos.-

Para procesar comunicaciones utilizando threads separados, existen una serie de mtodos pensados al efecto, es decir en un modo de operacion asincrono.-

Protocolo TCP

Cuando se utilizan protocolos orientados a la conexión (connection-oriented) como TCP, entonces debe utilizar los métodos Socket(), BeginConnect() y EndConnect() para comenzar las tareas de conexiones con el server que atiende los pedidos.-

El asincronismo en el envío y la recepción de datos (en los casos donde trabajamos con mas de un thread) necesita del uso de los métodos específicos de comunicación asincrónica. Estos son: BeginSend(),EndSend() , BeginReceive() y EndReceive(). Los pedidos de conexión entrante los debe procesar con los métodos: BeginAccept() y EndAccept().

Si estamos usando UDP entonces los métodos adecuados son para enviar datagramas son BeginSendTo() y EndSendTo() y para la recepción se debe usar BeginReceiveFrom() y EndReceiveFrom().

Usar un socket para comunicaciones con el protocolo HTTP

El ejemplo siguiente utiliza una clase Socket para enviar y recibir respuestas de un servidor HTTP.

Pensemos en un servidor WEB que esta corriendo en algún punto de la red.

El modo en el que escribimos el código requiere de algunos conceptos de cómo trabaja el protocolo HTTP, damos un repaso que consideramos adecuado.-

El protocolo HTTP

El Protocolo de Transferencia de Hipertexto (Hypertext Transfer Protocol) es un protocolo para comunicación entre un nodo cliente y otro nodo servidor.-

Su uso central es en la WEB, pero aclaremos que un servidor HTTP, puede recibir pedidos y enviar información sin necesidad de la existencia de browsers. Es decir es un mecanismo de comunicación que puede llegar a utilizarse en procesos puramente batch , no solo para interactivos.-

El protocolo esta montado sobre el servicio de conexión TCP/IP.

HTTP se basa en sencillas operaciones de solicitud/respuesta.

Un cliente establece una conexión con un servidor y envía un mensaje con los datos de la solicitud.-

El servidor responde con un mensaje similar, que contiene el estado de la operación y su posible resultado.-

La primera parte de una transacción HTTP es cuando un cliente crea y envia una solicitud al servidor.

Por ejemplo:

GET /index.html /HTTP 1.1

Es una pedido absolutamente correcto para un servidor HTTP activo

Existen tres verbos básicos que un cliente puede utilizar para dialogar con el servidor: GET, para recoger un objeto, POST, para enviar información al servidor y HEAD, para solicitar las características de un objeto (por ejemplo, la fecha de modificación de un documento HTML).

Todas las operaciones pueden adjuntar un objeto o recurso sobre el que actúan; cada objeto Web (documento HTML, fichero multimedia o aplicación CGI) es conocido por su URL (Uniform resources location podría concebirse como la extensión del concepto de nombre completo de un archivo (path)).

Cada operación HTTP implica una conexión con el servidor, que es liberada al término de la misma. (Esa es la verdadera esencia de Internet ¡!)

Es decir, en una operación se puede recoger un único objeto y no se mantienen estados.

Cada petición de un cliente a un servidor no es influida por las transacciones anteriores. El servidor trata cada petición como una operación totalmente independiente del resto.

El acceso a los servidores HTTP se realiza a través de las URLs, que empaquetan toda la información necesaria para localizar un determinado recurso en Internet. Cuando el servidor recibe una URL, a través del protocolo HTTP, la decodifica y devuelve al cliente la información correspondiente, que puede ser de dos tipos, en función del contenido de la URL:

Información estática: que se publica a base de situar los ficheros que la contienen en unos directorios especiales que define cada servidor. Este es el procedimiento que se ha utilizado hasta ahora.

Información dinámica: en este caso, el servidor HTTP arranca una aplicación utilizando la propia URL como parámetro de ejecución. El resultado de la ejecución de la aplicación será devuelto al cliente

El término 'información dinámica' se amplia cada vez más, a medida que se dispone de nuevas aplicaciones y tecnologías de publicación de información.

A través de estas nuevas tecnologías, es posible generar vistas a través del Web de elementos tan dispares como hojas de cálculo, bases de datos, agendas de reuniones, correos electrónicos e incluso periódicos completos, que se actualizan automáticamente en función de los cambios en el contenido de información original.

using System;

using System.Text;

using System.IO;

using System.Net;

using System.Net.Sockets;


public class mySocket

{

private static Socket connectSocket(string server, int port)

{

Socket s = null;

IPHostEntry iphe = null;

try

{

//

iphe = Dns.Resolve(server);

// Esta iteracion a traves de la lista de direcciones ( AddressList) es para obtener la

//llamada familia de direcciones ( AddressFamily) soportada.Esto evitara una excepcion en el caso

//en que la dirección IP no sea compatible con dicha familia.-

foreach (IPAddress ipad in iphe.AddressList)

{

IPEndPoint ipe = new IPEndPoint(ipad, port);

Socket tmpS =

new Socket(ipe.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

tmpS.Connect(ipe);


if (tmpS.Connected)

{

s = tmpS;

break;

}

else

continue;

}

}


catch (SocketException e)

{

Console.WriteLine("SocketException !!!");

Console.WriteLine("Origen : " + e.Source);

Console.WriteLine("Mensaje : " + e.Message);

}

catch (Exception e)

{

Console.WriteLine("Exception atrapada!!!");

Console.WriteLine("Origen : " + e.Source);

Console.WriteLine("Mensaje : " + e.Message);

}

return s;


}


// este método consulta el contenido de la pagina Home del servidor indicado.

// y muestra el contenido recibido

private static string socketSendReceive(string server, int port)

{


//Preparamos para escribir al server

Encoding ASCII = Encoding.ASCII;

string Get = "GET / HTTP/1.1\r\nHost: " + server +

"\r\nConnection: Close\r\n\r\n";

Byte[] ByteGet = ASCII.GetBytes(Get);

Byte[] RecvBytes = new Byte[256];

String strRetPage = null;


// Ahora creamos un socket como conneccion con el server y el port indicado

Socket s = connectSocket(server, port);


if (s == null)

return ("Conneccion fallida");


// Ahi viaja el pedido al servidor.

s.Send(ByteGet, ByteGet.Length, 0);


// Aca viene la respuesta de la pagina home del server

Int32 bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);


// Leer los primeros 256 bytes.Esto lo determina el uso de

//RecvBytes.Length en el método Receive

strRetPage = "Pagina Default HTML en el " + server + ":\r\n";

strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);


while (bytes > 0)

{

bytes = s.Receive(RecvBytes, RecvBytes.Length, 0);

strRetPage = strRetPage + ASCII.GetString(RecvBytes, 0, bytes);

}

return strRetPage;

}


public static void Main(string[] args)

{

string host;

int port = 91;


if (args.Length == 0)

// si no hay server indicado, usamos el nombre del host.

host = Dns.GetHostName();

else

host = args[0];

string result = socketSendReceive(host, port);

Console.WriteLine(result);

}

}

Vamos a hacer la prueba de este programa conectándolo contra un server WEB TOMCAT.


En mi equipo, como se observa, esta levantado en el puerto 8088.-

Si ejecuto el programa, colocando como parámetro en la línea de ejecución del programa, esta dirección.


Y lo ejecutamos, vemos la salida de texto que se muestra a continuación:


Por supuesto que es mejor verla con el browser ¡!


Usar sockets para armar un programa de mensajería entre pares (chat)

Vamos a construir una aplicación Windows que nos entregue una interfaz simple para comunicación. Queremos que sea un contenedor con dos paneles.

Uno que me muestre los mensajes que recibo y el otro que me permita escribir mensajes.-

La propia aplicación trabajara de dos modos diferenciados dependiendo de cómo se la invoque en la llamada. En un caso será servidor y en otro cliente.-

Alcanza con solo servidor para atender múltiples clientes.-

El esquema que armamos es el de "estrella". Es decir tal como armamos la aplicación todos los clientes pueden hablar con el servidor, pero no entre si.-

Una variante de este modo en el que el servidor atrape el pedido y lo derive u otro cliente de la red es relativamente sencillo de hacer basado en la tecnología mostrada en este ejemplo.-


Se habrá creado un proyecto con una serie de componentes como muestra el grafico


En este tipo de aplicaciones dentro del Form (Form1.cs), la IDE de .NET escribe algunos códigos de inicialización que no queremos para nuestro caso.-

Codigo en Form1.cs

Ingrese a código de Form1.Designer.cs y reemplácelo por el código que figura a continuación:


namespace UnChat

{

partial class Form1

{

/// <summary>

/// Required designer variable.

/// </summary>

private System.ComponentModel.IContainer components = null;



#region Windows Form Designer generated code


/// <summary>

/// Required method for Designer support - do not modify

/// the contents of this method with the code editor.

/// </summary>

private void InitializeComponent()

{


}


#endregion

}

}


Código en program.cs

Reemplace el código del modulo program.cs por el código que figura a continuación


using System;

using System.Collections.Generic;

using System.Windows.Forms;

using System.Net;

using System.Globalization;

using System.Net.Sockets;


namespace UnChat

{


static
class
Program

{


// -


private
static
IPEndPoint endPoint;


//Indica si esta actuando en modo servidor o cliente


private
static
bool client;



// Entrada al programa


public
static
void Main(String[] args)

{


// -


if (ParseArgs(args))

{


//


Talker talker = new
Talker(endPoint, client);



// Referencia del Talker dentro del Form


TalkForm form = new
TalkForm(talker);



// Arranca el talker

talker.Start();



// Arranca la apliacion con el Form


Application.Run(form);

}

}





// Parser de la linea de comandos


private
static
bool ParseArgs(String[] args)

{


try

{


if (args.Length == 0)

{

client = false;

endPoint = new
IPEndPoint(IPAddress.Any, 5150);


return
true;

}


if (args[0][0] != '/' && args[0][0] != '-')

{

ShowUsage();


return
false;

}


switch (Char.ToUpper(args[0][1], CultureInfo.InvariantCulture))

{


case
'L': //Listen: Escucha


int port = 5150;


if (args.Length > 1)

{

port = Convert.ToInt32(args[1]);

}

endPoint = new
IPEndPoint(IPAddress.Any, port);

client = false;


break;


case
'C'://Call: Llama

port = 5150;


String address = "127.0.0.1";

client = true;


if (args.Length > 1)

{

address = args[1];

port = Convert.ToInt32(args[2]);

}

endPoint = new
IPEndPoint(Dns.GetHostEntry(address).AddressList[0], port);


break;


default:

ShowUsage();


return
false;

}

}


catch

{

ShowUsage();


return
false;

}



return
true;

}


// Como se usa

private static void ComoUsarme()

{


MessageBox.Show("UnChat [switch] [parametros...]\n\n" +

" /L [port]\t\t-- Escucha en el port. Default: 5150\n" +

" /C [direccion] [port]\t-- Conecta a una direccion y port.\n\n" +

"Ejemplo Server - \n" +

"UnChat /L\n\n" +

"Ejemplo Client - \n" +

"UnChat /C NombreDeServidor 5150", "Uso de UnChat");

}

}

}


Código en Talker.cs


Creemos ahora la clase que representara al Talker



Y coloquemos dentro del archivo de esta clase el código que se indica a continuación:


using System;

using System.Collections.Generic;

using System.Text;

using System.Net;

using System.Net.Sockets;

using System.IO;

using System.Threading;


namespace UnChat

{

// Esta clase es una capa que encpasula a la clase Socket y le agrega

// algunas habilidades de chateo.-

class Talker : IDisposable

{

private Socket socket;


private TextReader reader;

private TextWriter writer;


bool client;

IPEndPoint endPoint;


private String prevSendText;

private String prevReceiveText;

private String statusText;


private Status status;


// Constructor

public Talker(IPEndPoint endPoint, bool client)

{

this.endPoint = endPoint;

this.client = client;


socket = null;

reader = null;

writer = null;


statusText = prevSendText = prevReceiveText = String.Empty;

}


// -

~Talker()

{

Dispose();

}


// Este es un modo ordenado y seguro de liberar todos

//los recursos que se usaron en el momento en que se baja

//la aplicacion

public void Dispose()

{

GC.SuppressFinalize(this);

if (reader != null)

{

reader.Close();

reader = null;

}

if (writer != null)

{

writer.Close();

writer = null;

}

if (socket != null)

{

socket.Close();

socket = null;

}

}


// Clase delegate y evento

public delegate

void NotificationCallback(Notification notify, Object data);

public event NotificationCallback Notifications;


// enum de los tipos de notificaciones

public enum Notification

{

Initialized = 1,

StatusChange,

ReceivedRefresh,

ReceivedAppend,

End,

Error

}


// enum de los posibles estados

public enum Status

{

Escuchando,

Conectado,

Cliente

}



// EL inicio de una instancia de la clase es un thread

//que se encola en el pool de threads

public void Start()

{

ThreadPool.QueueUserWorkItem(new WaitCallback(EstablishSocket));

}


// Envio de texto a la coneccion remota

public void EnviaMensaje(String newText)

{

String send;

// es un agregado al texto (append)

if ((prevSendText.Length <= newText.Length) && String.CompareOrdinal(

newText, 0, prevSendText, 0, prevSendText.Length) == 0)

{

String append = newText.Substring(prevSendText.Length);

send = String.Format("A{0}:{1}", append.Length, append);


}// o reemplazo completo

else

{

send = String.Format("R{0}:{1}", newText.Length, newText);

}

// Envio datos y hace flush

writer.Write(send);

writer.Flush();

// Guardar el texto para comparar si es necesario

prevSendText = newText;

}


// Notifica un estado

private void SetStatus(Status status)

{

this.status = status;

Notifications(Notification.StatusChange, status);

}



// Establecer una coneccion via socket y comenzar la recepcion

private void EstablishSocket(Object state)

{

NetworkStream stream = null;

try

{

// Si no es un cliente, entonces levantar una Escucha (listener)

if (!client)

{

Socket listener;


try

{

listener = new Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.Tcp);

listener.Blocking = true;

listener.Bind(endPoint);

SetStatus(Status.Escuchando);

listener.Listen(0);

socket = listener.Accept();

listener.Close();

stream = new NetworkStream(socket);

reader = new StreamReader(stream);

writer = new StreamWriter(stream);

writer.Write("WINTALK .NET");

writer.Flush();

}

catch (SocketException e)

{


// Este error indica que alguien esta usando el puerto

//Asumamos que en ese caso se debe intentar conectarse como un cliente

//pensando que ya hay un server para los nodos de la red

if (e.ErrorCode == 10048)

{

client = true;

endPoint = new IPEndPoint(

Dns.Resolve("127.0.0.1").AddressList[0], endPoint.Port);

}

else

{

Notifications(

Notification.Error,

"Error al inicializar el Socket:\n" + e.ToString());

}

}

}



// Intenta coneccion como cliente

if (client)

{

SetStatus(Status.Cliente);

Socket temp = new

Socket(AddressFamily.InterNetwork,

SocketType.Stream, ProtocolType.Tcp);

temp.Blocking = true;

temp.Connect(endPoint);

socket = temp;

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 5000);

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 5000);

stream = new NetworkStream(socket);

reader = new StreamReader(stream);

writer = new StreamWriter(stream);

char[] handshake = new char[12];


try

{

reader.Read(handshake, 0, 12);

string sHandShake = new string(handshake);


if (!(reader.Read(handshake, 0, 12) > 0 && sHandShake == "WINTALK .NET"))

{

socket.Close();

socket = null;

}

else

{

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, 0);

socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, 0);

}

}

catch

{

socket.Close();

socket = null;

}

}


// Si establecimos el socket, comenzar a chatear

if (socket != null)

{

SetStatus(Status.Conectado);

Notifications(Notification.Initialized, this);

// -

//NetworkStream.Read()es un metodo llamado por el metodo ReceiveTalk

//El primer metodo genera una excepcion cuando se cierra la coneccion remota

//La excepcion se maneja en el catch

ReceiveTalk();


// Por aca pasa cuando NetworkStream.Read() retorna un cero (algo que hace

//en alguans versiones del SO

Notifications(Notification.End, "La coneccion remota se cerro.");

}

else

{

Notifications(Notification.Error,

"Falla al establecer el , Verifique si especifico el port correcto");

}

}

catch (IOException e)

{

SocketException sockExcept = e.InnerException as SocketException;

if (sockExcept != null && 10054 == sockExcept.ErrorCode)

{

Notifications(Notification.End, "La coneccion remota se cerro.");

}

else

{

if (Notifications != null)

Notifications(Notification.Error, "Error :\n" + e.Message);

}

}

catch (Exception e)

{

Notifications(Notification.Error, "Error:\n" + e.Message);

}

}



// Recibe la comunicacion del cliente remoto

private void ReceiveTalk()

{

char[] commandBuffer = new char[20];

char[] oneBuffer = new char[1];

int readMode = 1;

int counter = 0;

StringBuilder text = new StringBuilder();


while (readMode != 0)

{

if (reader.Read(oneBuffer, 0, 1) == 0)

{

readMode = 0;

continue;

}


switch (readMode)

{

case 1:

if (counter == commandBuffer.Length)

{

readMode = 0;

continue;

}

if (oneBuffer[0] != ':')

{

commandBuffer[counter++] = oneBuffer[0];

}

else

{

counter = Convert.ToInt32(

new String(commandBuffer, 1, counter - 1));

if (counter > 0)

{

readMode = 2;

text.Length = 0;

}

else if (commandBuffer[0] == 'R')

{

counter = 0;

prevReceiveText = String.Empty;

Notifications(Notification.ReceivedRefresh, prevReceiveText);

}

}

break;

case 2:

text.Append(oneBuffer[0]);

if (--counter == 0)

{

switch (commandBuffer[0])

{

case 'R':

prevReceiveText = text.ToString();

Notifications(Notification.ReceivedRefresh, prevReceiveText);

break;

default:

string newText = text.ToString();

prevReceiveText += newText;

Notifications(Notification.ReceivedAppend, newText);

break;

}

readMode = 1;


}

break;

default:

readMode = 0;

continue;

}

}

}

}

}

Como ejecutar la aplicación


Usar una sesión como cliente y otra como servidor

Para eso generar dos archivos .bat (llamémoslos ChatCliente.bat y ChatServer.bat) que inicien cada uno una sesión del programa


Por ejemplo para ChatCliente.bat

UnChat.exe /C m3nb02 5150

Que significa que el programa desea actuar como Cliente (de allí la letra C) del equipo m3nb02 (es el nombre de mi equipo en esta red en la que estoy trabajando) y "hablar" por el port 5150.-


Por ejemplo para ChatServer.bat


UnChat.exe /L 5150

Que significa que el programa desea actuar como Server (La letra L por Listen) y "hablar" por el port 5150.-


Sockets y Ports

El protocolo FTP

Codigos de retorno de FTP

Code

Explanation

100

Series: The requested action is being initiated, expect another reply before proceeding with a new command.

110

Restart marker replay . In this case, the text is exact and not left to the particular implementation; it must read: MARK yyyy = mmmm where yyyy is User-process data stream marker, and mmmm server's equivalent marker (note the spaces between markers and "=").

120

Service ready in nnn minutes.

125

Data connection already open; transfer starting.

150

File status okay; about to open data connection.

200

Command okay.

202

Command not implemented, superfluous at this site.

211

System status, or system help reply.

212

Directory status.

213

File status.

214

Help message.On how to use the server or the meaning of a particular non-standard command. This reply is useful only to the human user.

215

NAME system type. Where NAME is an official system name from the list in the Assigned Numbers document.

220

Service ready for new user.

221

Service closing control connection.

225

Data connection open; no transfer in progress.

226

Closing data connection. Requested file action successful (for example, file transfer or file abort).

227

Entering Passive Mode (h1,h2,h3,h4,p1,p2).

228

Entering Long Passive Mode (long address, port).

229

Entering Extended Passive Mode (port).

230

User logged in, proceed. Logged out if appropriate.

231

User logged out; service terminated.

232

Logout command noted, will complete when transfer done.

250

Requested file action okay, completed.

257

"PATHNAME" created.

331

User name okay, need password.

332

Need account for login.

350

Requested file action pending further information

421

Service not available, closing control connection. This may be a reply to any command if the service knows it must shut down.

425

Can't open data connection.

426

Connection closed; transfer aborted.

434

Requested host unavailable.

450

Requested file action not taken.

451

Requested action aborted. Local error in processing.

452

Requested action not taken. Insufficient storage space in system.File unavailable (e.g., file busy).

500

Syntax error, command unrecognized. This may include errors such as command line too long.

501

Syntax error in parameters or arguments.

502

Command not implemented.

503

Bad sequence of commands.

504

Command not implemented for that parameter.

530

Not logged in.

532

Need account for storing files.

550

Requested action not taken. File unavailable (e.g., file not found, no access).

551

Requested action aborted. Page type unknown.

552

Requested file action aborted. Exceeded storage allocation (for current directory or dataset).

553

Requested action not taken. File name not allowed.


domingo, 8 de agosto de 2010

clases abstractas, Interfaces y enumeraciones PARTE II

LA IMPLEMENTACION
Creemos un proyecto de consola:

Y generemos en el 4 módulos denominados AnalizaArgumentos, Analizador, OcurrenciaPalabra y WordCount.-
El código de las clases que se montan en cada modulo esta en las paginas siguientes.-
Luego para utilizarlo genere archivos de texto e invoque en la línea de comandos a los mismos con las opciones que desee.-


Por ejemplo colocando esto en la ventana de propiedades del proyecto en la opción DEBUG:
-a -o Texto.txt Conecciones.txt -fSalida.txt


Esto es interpretado por el programa asi:
-a: Un switch que significa ORDENADO ALFABETICAMENTE


-o: Un switch que significa ORDENADO por OCURRENCIA y luego ALFABETICAMENTE


Texto.txt Conecciones.txt: Son dos archivos de texto, que estan en el paso de ejecucion (o en el directorio de trabajo en el que estamos ya que en el ejemplo marcamos C:\M3)


-fSalida.txt: Es un archivo de salida de la información que genera WordCount. La alternativa por defecto es la pantalla de texto.-






La clase Abstracta Analizador
/*=====================================================================
Archivo: Analizador.cs
Esta clase tiene el cuerpo basico para trabajar como un analizador de
* argumentos para un programa que admite parametros en su invocacion
* desde la linea de comandos. Los parametros, se asume, comienzan con
* llaves (switchs) que indican alguna particularidad del mismo.
* Un ejemplo valido seria
* c:\prog1.exe -eArchivo.txt -sSalida.txt
* Donde prog1.exe es el programa que se ejecuta y tanto -eArchivo.txt
* como -sSalida.txt son los argumentos
* -e (antes de Archivo.txt) y -s (antes de Salida.txt) son las llaves.
*

=====================================================================*/

//
using System;
using System.Globalization;


///////////////////////////////////////////////////////////////////////////////


public abstract class Analizador {
private String[] separadores; // Por defecto: "/", "-"
private String[] argumentos; // Estos son los argumentos validos
//que el analizador debe reconocer
private Boolean argSensiblesAMayOMin; // Indica si los argumentos deben ser sensibles a Mayusculas/Minusculas


//
protected enum SwitchStatus { NoError, Error, ShowUsage };


// Constructor 1
public Analizador(String[] switchSymbols)
: this(switchSymbols, false, new string[] { "/", "-" }) {
}


// Constructor 2
public Analizador(String[] switchSymbols, Boolean caseSensitiveSwitches)
: this(switchSymbols, caseSensitiveSwitches, new string[] { "/", "-" }) {
}


// Constructor 3, sin valores por defecto
public Analizador(String[] _argumentos, Boolean _argSensiblesAMayOMin, String[] switchChars)
{
this.argumentos = _argumentos;
this.argSensiblesAMayOMin = _argSensiblesAMayOMin;
this.separadores = switchChars;
}


// -
public abstract void ModoDeUso(String errorInfo);



protected virtual SwitchStatus OnSwitch(String switchSymbol, String switchValue) {
return(SwitchStatus.Error);
}



// Cada clase derivada debe implementar el metodo OnNonSwitch
//observe que sino por defecto un switch se considera un error
protected virtual SwitchStatus OnNonSwitch(String value) {
return(SwitchStatus.Error);
}


// Informamos a la clase derivada despues que se hizo todo el analisi
// -
protected virtual SwitchStatus OnDoneParse() {
// -
return(SwitchStatus.Error);
}


// Solo sabemos analizar argumentos de la linea de comandos
public Boolean Parse() {
return(Parse(Environment.GetCommandLineArgs()));
}


// Este es el analizador
public Boolean Parse(String[] args) {
SwitchStatus ss = SwitchStatus.NoError; //

int ArgNum;
for (ArgNum = 0; (ss == SwitchStatus.NoError) && (ArgNum < fisswitch =" false;" n =" 0;" fisswitch =" (0" flegalswitchsymbol =" false;" n =" 0;" flegalswitchsymbol =" (0" flegalswitchsymbol =" (0"> salir
if (!fLegalSwitchSymbol) {

ss = SwitchStatus.Error;
break;
} else {
// Switch valido -> notificar a la clase derivada el switch y su valor
ss = OnSwitch(
argSensiblesAMayOMin ? argumentos[n] : argumentos[n].ToLower(CultureInfo.InvariantCulture),
args[ArgNum].Substring(1 + argumentos[n].Length));
}
}
else // Esto no es un switch, retornar a la clase derivada para que lo analice
{
ss = OnNonSwitch(args[ArgNum]);
}
}

// Fin del analisis de los argumentos
if (ss == SwitchStatus.NoError) {
// -
ss = OnDoneParse();
}

if (ss == SwitchStatus.ShowUsage) {
//
ModoDeUso(null);
}

if (ss == SwitchStatus.Error) {
//
ModoDeUso((ArgNum == args.Length) ? null : args[ArgNum]);
}

// -
return(ss == SwitchStatus.NoError);
}
}


///////////////////////////////// End of File /////////////////////////////////

clases abstractas, Interfaces y enumeraciones-PARTE I

OBJETIVO

Vamos a hacer un ejercicio clásico: Un programa que lee un archivo de texto y cuenta los caracteres, palabras y líneas que hay en el.-
Este programa clasico suele llamarse (como era de esperar) WordCount y su ductilidad es tal que permite utilizar muy distintas estructuras de datos para lograr el cometido.-
En nuestro caso pondremos el acento en generar una clase abstracta que se encargara de analizar la entrada que acompaña a la invocación del programa (los argumentos de línea de comando).-
Por otro lado utilizaremos unas interfaces útiles para el tipo de estructura de datos que nos interesa utilizar.-
Creemos que WordCount al ser un programa clásico de algoritmos permite visualizar aspectos poderosos de la programación orientada a objetos así como sacar provecho de algunas de las bibliotecas estándar de C#.-




El requerimiento funcional

WordCount es un programa que:
A) Debe tomar como entrada un archivo de texto.Entiendase por esto que puede elegir distintos CodePages (encoding) pero que el archivo en si debe ser un flujo de caracteres en el que exista el concepto de palabra como unidad de construcción
B) De esa entrada debe producir un conteo:
· Lineas (grupos de caracteres separados por “nueva linea”).-
· Palabras (grupos de caracteres entre separadores de texto).-
· Caracteres (son los que conforman las palabras y los separadores de texto).-
· Bytes (son los caracteres mas las “nueva linea”).-
C) La salida dr la información puede ser por consola o puede ser hacia un archivo (de texto) que debera ser especificado.-
D) La salida admite ciertos ordenamientos:
· Por palabra indicando la cantidad de ocurrencias de la misma
· Por ocurrencia y luego alfabeticamente por palabra
E) La aplicación sera una aplicación de consola por lo que desde la linea de comandos debe poder especificarse lo que se desea que realice la aplicación. No se desea que la aplicación necesite de un archivo de propiedades dado que su funcionalidad es reducida y se adecua a recibir parametros dedes la linea de comandos del sistema operativo.-

Mapeo de funcionalidad con mi toolbox:




Este problema nos pone frente a una variedad de situaciones conocidas que enunciaremos como lista de requerimientos y funcionalidades que la aplicación debe tener. A cada comentario de funcionalidad mapearemos una tecnica o modo de resolucion. Esta es una buena practica para cualquier programador que permite probar la riqueza del lenguaje que utiliza asi como las bibliotecas y el Framework sobre el que esta trabajando.-




Siendo esta aplicación de consola, manejando caracteres y esencialmente algoritmica, veremos que las colecciones e interfases del CLR de .NET haran gran parte del trabajo.-




1- Analisis de parametros recibidos desde la linea de comando. Es una aplicación de consola sin interfaz grafica con el usuario. De modo que parece razonable utilizar parametros desde la linea de comandos en vez de utilizar un archivo de propiedades. Al fin de cuentas no son tantas las propiedades de configuracion que la aplicación deba contemplar.-
Como estamos acostumbrados a que estos parametros sean letras precedidas por separadores (como por ejempo leerdatos.exe –ac:\datos.txt –fc:\analisis.prn) haremos que esta clase sea una base utilizable para tareas similares.-
Cada letra indica una posible accion de la aplicación y es posible que exista una combinacion de letras, por lo que la tarea basica del analizador (Parser) es trabajar con separadores y argumentos y retornar arrays de estas categorias para que el programa que usa estos servicios pueda realizar su tarea especifica de interpretacion de los parametros y las llaves.-
Sin embargo esta clase no podra resolver todos los casos que plantee el analisis de argumentos, aun cuando usemos el esquema simple de llaves(switchs) y argumentos.
Por otro lado si pensamos en una interfaz, solo enunciaremos los metodos y deberemos escribir en cada implementacion codigo que es comun.-
Es decir una clase no nos resuelve todos los casos y una interfaz no cumple una funcion util.-
Para eso vamos a hacer una clase abstracta que hara las tareas de analisis de parametros en una linea de comandos.-
Esta es la real utilidad de una clase abstracta: Presentar la posibilidad de herencia de los propios metodos de la clase, tener metodos que deben ser escritos por el implementador de la clase que hereda, Y NO PERMITIR LA INSTANCIACION DIRECTA.
Es decir no podemos hacer




MiClaseAbstracta ca = new MiClaseAbstracta();




Este no es un detalle menor como elección de ingeniería. Al no permitir la instanciación directa obliga a que otra clase:
Herede :usando lo que ya esta definido y es común.-
Redefina : (override)

2- Lectura desde un flujo plano de caracteres. Esto es leer un archivo de texto. Esta tarea la realizaremos con la clase FileStream del namespace FileStream y un lector de flujo (StreamReader):

FileStream fsIn = new FileStream(pathname, FileMode.Open, FileAccess.Read, FileShare.Read);

StreamReader sr = new StreamReader(fsIn, fileEncoding, true);

3- Los archivos que leeremos como entrada debemos colocarlos en un contenedor (Un array de strings que implementaremos como un ArrayList:
private ArrayList archivosALeer = new ArrayList();

4- Este arreglo deberá después enviarse de algún modo al programa que llama de modo que pueda barrer esa lista de archivos e ir procesándolos y llevando su estadística de líneas, palabras, etc.
Acá es valido pensar en un getter que devuelva dicho array al programa para que opere sobre el o bien utilizar una Interfaz de enumeración.
Esta es una aproximación muy interesante al problema que nos permite devolver el enumerador de esa colección y luego el programa que llama se encarga de iterar sobre dicho enumerado y tomar su valor asociado. Esto permite que si luego internamente en la clase que implementa lo indicado en el punto 3, deseamos cambiar la estructura, el programa que llama no se entera ya que el siempre barrera un iterador.-

// Retorna un enumerator incluyendo todos los archivos indicados
public IEnumerator GetEnumeradorDeArchivos() {
return archivosALeer.GetEnumerator(0, archivosALeer.Count);
}
IEnumerator que es la interfaz que aquí utilizamos es la Interfaz base de todas las enumeraciones genéricas.-
5- Analizador de cada línea leída, cortando esa línea en palabras. Este es un algoritmo clásico en el que el concepto de palabra es el conjunto consecutivo de caracteres entre separadores. Los separadores son los propios del sistema LEXICO que se esta utilizando (lo que en jerga técnica se llama ENCODING o CONJUNTO DE CARACTERES). Observemos que esto implica que para un lenguaje dado, existe un sistema LEXICO dado (un ENCODING) y por lo tanto un conjunto de separadores ya definidos que es el aceptado por los lenguajes que usan dicho ENCODING.-
Esta tarea es habitual y en las clases de C# (en realidad en las de .NET y el CLR) tenemos funcionalidad de cortar una línea de textos en palabras (String.Split()) que contiene todo lo que necesitamos para realizar esta tarea de separación.-
6- Necesitamos un contenedor especial: Un diccionario. Este es un contenedor de dos posiciones. Una es la clave y la otra el valor que nos interesa. Esta estructura de datos es de utilidad para llevar la cuenta de palabras encontradas que es uno de los objetivos primarios de la aplicación.
El Diccionario es un contenedor clásico y en las bibliotecas de .NET y el CLR tenemos una implementación de especial utilidad denominada SortedList.-

// La lista de palabras (ordenada alfabeticamente)
System.Collections.SortedList ctdorDePalabras = new SortedList();
Lo interesante de este contenedor es que es una implementación de una interfaz genérica denominada IDictionary. Esta interfaz es la base de todas aquellas estructuras que deban cumplir el patrón: par clave/valor.-
Otra característica interesante de este contenedor es que también es una implementación de la Interfaz de listas no genéricas (IList). Esto nos permite tener en el mismo constructor las facilidades de agregar un par clave/valor del diccionario, asi como las búsquedas de inclusión que tienen las listas.-
Advertencia: No esta demás decir que tanta funcionalidad es al precio de la performance. Sea cauto en el uso de SortedList para implementación de par/valor. En otros casos un HashTable puede ser más eficiente en términos de velocidad. Acá elegimos las SortedList ya que se adecuan a las necesidades de ordenamiento

7- El ordenamiento solicitado de las listas que contienen las palabras leídas y sus ocurrencias, se ha solicitado de modo doble: A) Alfabéticamente y B) Por Ocurrencia y Alfabéticamente.-
Esto nos propone una idea: Que exista una clase que exprese la ocurrencia de las palabras y que sirva para que nuestra lista resulte ordenada según las reglas del punto B) (por ocurrencia y alfabéticamente). Siendo que las listas se armaran como SortedLIst que es una lista ordenada, resulta necesario que esta clase que vamos a crear tenga alguna relación con SortedList.
Es aquí donde comienza a jugar un papel importante la idea de polimorfismo. SortedList implementa entre otras cosas la interfaz IComparable. Esta interfaz tiene un método virtual llamado CompareTo que implementa.

Para entender como enlazar todo esto veamos lo que dicen los ingenieros que implementaron SortedList:


Lo elementos de una SortedList estan ordenados por sus claves, ya sea de acuerdo a una implementacion de IComparer especificada cuando se crea la SortedList o de acuerdo a una implemenatcion de IComparable provista por las claves mismas. En ambos casos una SortedList no permite la duplicacion de llaves.


Esto significa que podemos resolver el tema de ordenamiento por ocurrencia y alfabéticamente haciendo una clase que IMPLEMENTE IComparable y que se creen objetos que sean de dicha clase:


public class OcurrenciaPalabra : IComparable
{
….
}




1- Para mostrar la información solicitada es necesario barrer las listas ordenadas. Las listas estarán ordenadas o bien Alfabéticamente o bien por cantidad de ocurrencias y luego alfabéticamente.-
Este caso es interesante si lo miramos más abstractamente. Lo que hace falta es un enumerador, que nos permita barrer la colección. Estos enumeradores tienen un método llamado MoveNext() y se conocen como genéricos, una de las funcionalidades de lenguajes como C#.
Tenemos para nuestra colección (que es un Diccionario: par/valor) un enumerador especifico denominado: IDictionaryEnumerator

Para el caso de la enumeración de las palabras ordenadas alfabéticamente:

// Retorna un enumerador para las palabras ordenadas alfabeticamente
public IDictionaryEnumerator GetPalabrasAlfabeticamenteEnumerator() {
return (IDictionaryEnumerator) ctdorDePalabras.GetEnumerator();
}
Observamos que la posibilidad de obtener un enumerador de una SortedList() esta a través del método GetEnumerator() (que es un IEnumerator).-
Y para el caso de la enumeración de las palabras ordenadas por ocurrencia y alfabéticamente:
public IDictionaryEnumerator GetPalabrasPorOccurrenciaEnumerator() {
…
}


viernes, 4 de junio de 2010

Programacion Reflexiva . PARTE III

Algo más al respecto

Los assemblies contienen módulos, los módulos contienen tipos y los tipos contienen funciones miembro o métodos.
-
Lo que Reflection provee son objetos que encapsulan a estas unidades (assemblies, módulos y tipos) de modo de crear dinámicamente una instancia de un tipo, ligar ese tipo a un objeto existente u obtener el tipo de un objeto existente.-

De este modo podemos invocar los métodos del tipo y/o acceder a sus campos y propiedades. El uso típico de reflection debería seguir estas reglas:

1. Usar la clase Assembly para definir y cargar assemblies, cargar los módulos que el assembly contiene y que se encuentran en el Manifiesto, ubicar el tipo del assembly en si y crear una instancia del mismo.-

Esto lo podemos hacer con:

Assembly a = Assembly.LoadFrom(args[0]);
O
Assembly a = Assembly.GetExecutingAssembly();

El primer modo es genérico y solo requiere que se indique el assembly. (El paso completo del mismo será obligatorio si no se encuentra en la GAC). El segundo método está restringido a el trabajo con clases que se encuentran en el assembly actual.-

2. Usar el método CreateInstance() para instanciar un objeto de un tipo dado:

Object o = assem.CreateInstance("Ejemplo2", false,
BindingFlags.ExactBinding, null, new Object[] { 2 }, null, null);


· Alternativamente observemos que esto puede hacerse así:

MethodInfo m = assem.GetType("Ejemplo2").GetMethod("MetodoDeEjemplo");
Object ret = m.Invoke(o, new Object[] { 42 });

El método GetType retorna un objeto de la clase MethodInfo.
Observemos que igual que en el caso anterior, el nombre de la clase y el de su metodo, SON LITERALES lo que indica que pdrian ser cargados en tiempo de ejecucion en variables string y la llamada a Assembly.GetType o a Assembly.CreateInstance se podria hacer con datos dinamicos.
Desde ya que esta es la funcion ultima de Reflection.-
Observe de todos modos que debe conocer “algo” del tipo o del metodo de modo de invocar correctamente a los constructores o pasar parametros adecuados a los metodos.-

EJEMPLO II

En este ejemplo haremos una variante que nos llevara a un caso más parecido al que planteamos al principio de esta y que mostramos con un ejemplo en lenguaje C.-
Construiremos 3 proyectos, ambos en la misma solucion solo a los efectos de la simplicidad.
Uno, al que llamaremos biblioteca1, correspondera al armado de una biblioteca donde estan las clases de las que crearemos objetos a los que a accederemos.-
El segundo, al que llamaremos pruebabiblioteca, sera una aplicacin intermedia de ayuda. Esto no es necesario para el proyecto final que es una aplicación que usando reflection hara uso de los metodos de la biblioteca. Pero es una biena idea probar la biblioteca SEPARADAMENTE antes de darle un uso generico. Es por eso que colocamos este proyecto dentro del ejemplo.-
El tercero, que es el que nos importa, sera la aplicación que a traves de Reflection accede a estos objetos y sus metodos en modo dinamico.-

El proyecto biblioteca1

El primer proyecto sera del tipo Class Library y en el colocaremos una clase con dos metodos.-
Luego esta sera nuestra biblioteca armada como Assembly.-
Vamos con el primer proyecto que consiste en armar la biblioteca de la clase que expondra funcionalidad a ser utilizada por el segundo proyecto:
Este es un proyecto Class Library de modo de armar una biblioteca con una clase que exporte sus metodos.-

El codigo que escribiremos para crear tal clase es el que se muestra a continuacion:

using System;
using System.Collections.Generic;
using System.Text;

namespace biblioteca1
{
public class UnaClase
{
public static Boolean SinNumeros(string dato)
{
char []numeros = new char[]{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
if (dato.LastIndexOfAny(numeros) == -1)
return (true);
else
return (false);
}

public static Boolean SinÑuflos(string dato)
{
char []ñuflos = new char[] { '@', '^', '`', '¨', '~' };
if (dato.LastIndexOfAny(ñuflos) == -1)
return (true);
else
return (false);
}

}
}


Ahora vamos a armar la biblioteca (biblioteca1) como una dll que expondrá la clase UnaClase y sus métodos públicos.-
Esto generara el archivo biblioteca1.dll .-
Revise en la pantalla de salida donde queda generada:

El proyecto PruebaBiblioteca

Ahora vamos a construir el segundo proyecto

Vamos a hacer una aplicación de consola que utilice la biblioteca creada en el proyecto anterior.
Lo hara del modo habitual, es decir sin utilizacion de reflection.
EL codigo C# de este proyecto esta en un solo modulo al que llamaremos Program y es el que se ve a continuacion:

using System;
using System.Collections.Generic;
using System.Text;
using biblioteca1;

namespace PruebaBiblioteca
{
class Program
{
static void Main(string[] args)
{
string dato = "ABCDE";
if (biblioteca1.UnaClase.SinNumeros(dato) == true)
Console.WriteLine("{0}: No tiene numeros");
else
Console.WriteLine("{0}: Tiene numeros");

dato = "AB1C2DE";
if (biblioteca1.UnaClase.SinNumeros(dato) == true)
Console.WriteLine("{0}: No tiene numeros");
else
Console.WriteLine("{0}: Tiene numeros");

dato = "A~CDE";

if (biblioteca1.UnaClase.SinÑuflos(dato) == true)
Console.WriteLine("{0}: No tiene Ñuflos");
else
Console.WriteLine("{0}: Tiene Ñuflos");

}
}
}

Programacion Reflexiva . PARTE II



El enfoque en C#
El cargador del lenguaje común administra lo que se denominan DOMINIO de APLICACIONES. El lenguaje común es el CONTENEDOR que aísla al programa del SO. Cualquier aplicación desarrollada en .NET no se comunicara directamente con APIs de WIN32, tal como lo hacíamos anteriormente sino a través de funciones, servicios, etc. del contenedor.- EL DOMINIO de la APLICACIÓN es el contorno o los limites en los que trabajan aquellos objetos que pertenecen al mismo alcance de aplicación.- La administración de este cargador incluye la carga de cada assembly en el dominio de aplicación adecuado y controla la disposición y arreglo en memoria del tipo de jerarquía en cada assembly.-
Veamos unos ejemplos.-

Ejemplo I:
En este caso haremos un proyecto que contendra las clases que queremos acceder a traves de Reflection.- Es por eso que utilizamos el metodo estatico: Assembly.GetExecutingAssembly(); para poder tener un objeto de la clase Assembly. Este es el modo de hacerlo cuando lo que necesitamos es un objeto de una clase que esta dentro del codigo actualmente en ejecucion.- Obtenemos un objeto de la clase Assembly de este modo : Assembly assem = new Assembly()) Este el objeto creado nos permitira, por ejemplo, instanciar objetos de las clases contenidas en el assembly.- Para eso el primer paso es:


¿Como acceder a una clase del assembly?

El objeto de la clase Assembly tiene un metodo llamado CreateInstance().
Este método admite como parámetro el nombre de una clase así como los parámetros que necesitase su constructor.
Este método es uno de los métodos capitales de la programación reflexiva ya que nos permite a través de un texto que representa el nombre de la clase, obtener un objeto que es una instancia de la misma.-
El codigo siguiente muestra la utilizacion del metodo

CreateInstance():

// Crear un objeto de la clase Ejemplo2, desde el assembly que la contiene // El constructor de Ejemplo2 necesita un parametro que es un entero //

Object o = assem.CreateInstance("Ejemplo2", false, BindingFlags.ExactBinding, null, new Object[] { 2 }, null, null);

Observe que se accede a una clase y se instancia un objeto de la misma a traves de un parametro que es un texto ("Ejemplo2").-
Observe tambien que uno de los parametros de CreateInstance() es un array de Object al que se le envian los parametros QUE EL CONSTRUCTOR de la clase necesita.
Esto es: No es suficiente con conocer el nombre de clase en el assembly sino que es preciso conocer algo mas de ella.
Esto es similar a lo que nos sucede en el programa C que vimos al inicio en el que debemos conocer algo del PROTOTIPO de la funcion para hacer una llamda exitosa.-

¿Como acceder a un metodo de la clase?

Ahora bien, tenemos un objeto O que es una instancia de una clase del assembly. ¿Como hacemos ahora para acceder a un metodo de dicha clase?
El handler al assembly (assem) tiene un metodo llamado GetType que obtiene información sobre el tipo exacto que se produce en tiempo de ejecucion para el objeto corriente.-
Es decir GetType retorna un tipo (Type). La clase Type es la base de reflection para acceder a la metada del objeto. Type es una interfaz que tiene distintas implementaciones. La que aquí no sinteresa, tiene un metodo llamado GetMethod() que, tomando como parametro el nombre del metodo, nos trae información del mismo. Asi:

MethodInfo m = assem.GetType("Ejemplo2").GetMethod("MetodoDeEjemplo");

Obtenida la información sobre el metodo, se lo puede invocar a traves del metodo Invoke de MethodInfo .
Igual que en el caso anterior, si bien es posible obtener acceso al metodo a traves de un literal (en este caso el nombre del metodo es : MetodoDeEjemplo ), el uso posterior del metodo requiere conocer cuales son los parametros adecuados.

Object ret = m.Invoke(o, new Object[] { 42 });

En este caso el entero 42 se corresponde al tipo de datos que corresponde al Metodo, MetodoDeEjemplo EL retorno que produce Invoke es un Object.

EL uso posterior del mismo sera parte de la logica de negocios del programa.-

Arme un proyecto de consola simple y coloque el código indicado en un modulo cs (C#)
















Y pruebe este codigo:

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Security.Permissions;
using System.Configuration;



class Ejemplo2
{
private int factor;

public Ejemplo2(int f)
{
factor = f;
}

public int MetodoDeEjemplo(int x)
{
Console.WriteLine("\nEjemplo2.MetodoDeEjemplo({0}) ejecutado.", x);
return x * factor;
}

public string OtroMetodo(string a)
{

StringBuilder st = new StringBuilder("A este dato le concateno: ");
Console.WriteLine("\nEjemplo2.OtroMetodo({0}) ejecutado.", a);
st.Append(a);
return st.ToString();
}


}

public class ejecuta
{

public static void Main()
{

Assembly assem = Assembly.GetExecutingAssembly();

Console.WriteLine("Nombre completo del Assembly:");
Console.WriteLine(assem.FullName);

//
AssemblyName assemName = assem.GetName();
Console.WriteLine("\nName: {0}", assemName.Name);
Console.WriteLine("Version: {0}.{1}",
assemName.Version.Major, assemName.Version.Minor);

Console.WriteLine("\nAssembly CodeBase:");
Console.WriteLine(assem.CodeBase);

// Crear un objeto de la clase Ejemplo2, desde el assembly que la contiene
//El constructor de Ejemplo2 necesita un parametro que es un entero
//
Object o = assem.CreateInstance("Ejemplo2", false,
BindingFlags.ExactBinding,
null, new Object[] { 2 }, null, null);

// Ahora apuntamos a un metodo del objeto:

MethodInfo m = assem.GetType("Ejemplo2").GetMethod("MetodoDeEjemplo");
Object ret = m.Invoke(o, new Object[] { 42 });
Console.WriteLine("MetodoDeEjemplo retorno: {0}.", ret);

MethodInfo m2 = assem.GetType("Ejemplo2").GetMethod("OtroMetodo");
ret = m2.Invoke(o, new Object[] { "Este Texto" });
Console.WriteLine("OtroMetodo retorno: {0}.", ret);

Console.WriteLine("\nPunto de entrada del Assembly:");
Console.WriteLine(assem.EntryPoint);
}
}

Ejecute el codigo y vera que nos muestra lo siguiente:

El nombre del assembly
El metodo que hemos ejecutado y el valor que retorna el mismo
El punto de entrada del assembly

La programacion Reflexiva-PARTE I

Reflection

Para la ejecucion de una aplicación .NET debemos tener presente el concepto de CONTENEDORES. Este es un concepto que se ha popularizado los ultimos años a traves de Java y difiere de los habituales run-times en que no es un agregado que lleva el ejecutable o un conjunto de bibliotecas que resuelven aspectos especificos (como graficos), SINO QUE ES UNA MAQUINA VIRTUAL dentro de la cual corre la aplicación.-
Es decir esta cascara es la que separa a la aplicación del Sistema operativo.-
Sus ventajas en el mundo Java son obvias en tanto estos contenedores aislan al programador de especificidades de tal o cual sistema operativo.-
No nos resultan tan obvias en el mundo Microsoft donde existe UN SOLO sistema operativo.-
Sin embargo hay otras decisiones que seguramente habrán pesado en la decisión de Microsoft de hacer un esquema de contenedores.-
El primero es que este esquema permite que exista un nivel de compilación que no es la generación del ejecutable mismo, sino de un lenguaje intermedio (MSIL para Microsoft, Bytecodes para Java y… P-CODE para quienes tuvimos oportunidad de trabajar profesionalmente con el maravilloso Pascal ¡!).-
El segundo es que varios lenguajes (al menos Visual Basic, C# y C++ en modo manejado) pueden trabajar sobre esta maquina virtual y compartir aquellos servicios, bibliotecas y assemblies que la maquina virtual necesita. Esto es un enorme avance desde el punto de vista de cómo cada lenguaje expone funcionalidades comunes a los programadores.-
Tambien aca podemos decir que esto existía, para determinados servicios, en los sistemas operativos propietarios de Digital e IBM.-
Sobre todo en los del primero, su sistema operativo VMS (hoy propiedad de Hewlett Packard) presentaba para la familia de lenguajes (Fortran, C, Cobol y Basic) idénticos nombres de rutinas para todo el sistema de I/O y de archivos ISAM.-
Solo que en estos casos un runtime que se adjuntaba al programa compilado era el encargado de proveer esta funcionalidad comun. Esto por supuesto duplicaba el codigo en cierto punto, aunque los servicios que utilizaba el runtime estaban en el kernel del SO por lo que no era tan problemático.-
¿Como funciona esto y para que sirve realmente?
Vamos a hacer un par de aplicaciones que nos muestren como funciona esto.
Antes de avanzar nos gustaría que repasemos como, en un lenguaje como el C, resolvemos un problema común:
Queremos invocar un método, que esta en una biblioteca CUYO ENLACE no se produce cuando compilamos, sino en tiempo de ejecución.-
Esto da a nuestros programas otra versatilidad. Es decir nuestro programa es capaz de:
Cargar una biblioteca a traves de su nombre LITERAL. Es decir un string en tiempo de ejecucion (por ejemplo leido de un archivo de configuración) dira cual es la biblioteca a a cargar)
Colocar un apuntador a una funcion de dicha biblioteca (definible tambien como un string)
Hay alguna habilidad que debe tener el SO en el que trabajamos y es la de aceptar bibliotecas compartidas. En Windows estas tienen la extension .DLL y en UNIX la extension .SO
Es decir la biblioteca que invocaremos desde nuestra aplicación sera del tipo compartida (Shared) y no del tipo estatica (STATIC).-
El ejemplo lo haremos para UNIX solo para ver alguna pequeña diferencia de cómo lo hariamos en Windows.-
El programa que haremos sabra cargar una biblioteca por nombre. En nuestro caso los nombres son lib1.so y lib2.so. Estos nombres son totalmente arbitrarios y podrian ser cualquier otro valido para el Sistema operativo.-
El programa cargara alguna de estas 2 bibliotecas y llamara la funcion saludo().
Esta funcion existe en ambas bibliotecas y el programa es capaz de trabajar con esta redefinicion (override) ya que dependera de la biblioteca que cargue lo que determinara cual es la tarea que termine realizando.-
Este es el codigo de una biblioteca escrito en ANSI C:

#include "stdio.h";
#include "dlfcn.h";/* bibliotecas de manejo dinamico de funciones:
dlopen(), dlclose(), dlsym() ... */

/* Explicamos como usar el programa. */
void usage(int argc, char* argv[])
{
fprintf(stderr, "Usar asi: %s [12] a los efectos de llamar a la lib1 o a la lib2 \n", argv[0]);
exit(1);
}

int main(int argc, char* argv[])
{
int lib_num;
/* Que biblioteca usar ? */
char biblioteca[100]; /* nombre del archivo de la biblioteca */
void* lib_handle; /* manejador para la biblioteca compartida cargada*/
void (*fn_de_la_biblioteca)(); /* puntero a UNA FUNCION de la biblioteca*/
const char* error; /* por si hay errores... */

/* necesito un argumento */
if (argc != 2)
usage(argc, argv);
/* Chau!! */

/* Esta es la biblioteca a cargar. */
lib_num = atoi(argv[1]); /* */

if (lib_num < 1 lib_num > 2)
usage(argc, argv); /* Chau !! */

/* ahora armo el nombre de la biblioteca concatenando la palabra "lib" con el numero 1 o 2
para obtener lib1.so o lib2.so */
sprintf(biblioteca, "lib%d.so", lib_num);

/* carga la biblioteca que desea (lib1.so o lib2.so) */
lib_handle = dlopen(biblioteca, RTLD_LAZY);



if (!lib_handle)
{
fprintf(stderr, "Error: %s\n", dlerror());
exit(1);
}

/* cargar la funcion saludo QUE ESTA EN CUALQUIERA
DE LAS DOS BIBLIOTECAS !*/
fn_de_la_biblioteca = dlsym(lib_handle, "saludo");

error = dlerror();
if (error) {
fprintf(stderr, "Error: %s\n", error);
exit(1);
}

/* Llamar a la funcion. */
(*fn_de_la_biblioteca)();

/* liberar el recurso */
dlclose(lib_handle);

return 0;
}

Este modulo es LIB1.SO:

#include ;

/* Esta es la funcion de la biblioteca*/
void saludo()
{
printf("Que puedo decir que no sea HOLA MUNDO?\n");
}

/* cleanup de la biblioteca */
void _fini()
{
printf("Limpiando la biblioteca 'lib2.so'\n");
}

Este modulo es LIB2.SO:
#include ;

/* funcion de inicializacion: OBLIGATORIO llamarla '_init'. */
void _init()
{
printf("Inicializando biblioteca 'lib1.so'\n");
}

/* Esta es lo funcion que nos importa */
void saludo()
{
printf("HALOAA !!\n");
}

Como vemos este problema responde a un patron comun: La resolucion en tiempo de ejecucion de una funcionalidad determinada.-
Los lenguajes modernos como C# y Java resuelven esto con el paradigma denominado programacion reflexiva.
Como en muchos otros conceptos de estos lenguajes modernos, las ideas se generaron en Smalltalk y C++.-