Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Práctica 1. Introducción a .NET, aplicaciones básicas, namespaces,
paquetes y código intermedio.
El objetivo de esta práctica es programar una aplicación sencilla para familiarizarnos con el
entorno de programación de .NET. Durante el desarrollo de las prácticas se van realizar
prácticas para dispositivos limitados (PDA).
Conociendo el Entorno de Desarrollo Comenzamos por crear un nuevo proyecto con Visual Studio 2005 o 2008:
Una vez pulsemos, aparecerá
en la pantalla los posibles
proyectos. El número y tipo de
proyectos dependerá de los
SDKs instalados en el sistema:
Vamos a crear un proyecto
Visual C#, para dispositivo
limitado (Smart Device), usando
el SDK de Windows Mobile 5.0
o 6.0 dependiendo del que
tengamos instalado.
La plantilla del proyecto es Empty Project. Le damos un nombre, por ejemplo MiAplicacion o
como uno desee. Seleccionamos la ubicación que más nos convenga.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Todos los proyectos que hagamos, se incorporan a una solución, daremos el nombre
MiSolución o el que nos apetezca a la solución. Ten en cuenta que la solución es un conjunto
de proyectos y el nombre debería representar algo común a todos los proyectos incluidos en
él.
Al crear el proyecto, podemos ver en el explorador de soluciones (Si no aparece, menú
View[Ver]>Explorador de soluciones) que el proyecto está vacío completamente.
Como podemos ver, la solución no contiene ningún fichero de
código. Para introducir un nuevo fichero de código, que será el
programa principal, sobre el
proyecto MiAplicación,
haremos click con el botón
derecho y pulsaremos sobre
añadir‐añadir elemento nuevo.
Al hacer esto, aparecen varias
posibilidades, entre las que se
encuentra Archivo de código.
Hay otras plantillas, como la
de clase o interfaz, que
generan parte del código, la
estructura etc…
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Llamaremos a este
fichero programa.cs. En
él vamos a escribir el
código de la primera
aplicación para Windows
Mobile.
El fichero está vacío,
dado que no se ha
utilizado ninguna
plantilla de clase o de
interfaz.
El programa principal
Vamos a escribir el programa principal. Para ello se pueden utilizar las siguientes clausulas:
Vamos a probar el siguiente código, que calcula el factorial de un número entero de 64 bits.
Copiad este código en el fichero programa.cs. Para compilar,
podeis hacer click con el botón derecho en el proyecto y pulsar en
public class MiAplicacion { public static Int64 factorial(Int64 num) { if ((num == 1) || (num == 0)) return 1; return num * factorial(num - 1); } public static void Main(string[] args) { MiAplicacion.factorial(2); factorial(6); } }
public static int Main(),
public static void Main(),
public static void Main(string[] args)
public static int Main(string[] args)
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
generar o build. Si hacéis esto mismo con la solución, compilará todos los proyectos
contenidos en la solución.
En la ventana de resultados (si no se ve la pestaña, pulsad en el menú Ver‐Resultados) podréis
ver si la compilación ha sido correcta o no.
Si vamos a la carpeta donde hemos guardado el proyecto, en la ruta
MiSolucion\MiAplicacion\Bin\Debug podemos ver dos ficheros, MiAplicación.exe y
MiAplicación.pdb. Estos ficheros tienen información de depuración, pueden ser un poco más
lentos que los programas sin información de depuración.
Si cambiáramos la configuración a Release (sin debug) crearía un fichero más eficiente, pero
en el caso que nos ocupa, vamos a aprender, así que dejamos el sistema en modo debug.
Depurar el programa
A continuación veremos cómo se depura un programa con Visual Studio. Para depurar,
podemos poner puntos de interrupción pulsando en la parte gris del editor de texto, justo en
la línea en la que queremos insertar el
breakpoint o bien mediante el menú
debug. Si lo que queremos es ejecutar
paso a paso pulsamos F10 o para
meternos dentro de las funciones F11 (menú debug StepOver, StepInto).
Para probarlo usaremos un emulador. En Visual Studio hay un desplegable que permite ver los
emuladores instalados.
En ese menú desplegable seleccionaremos el emulador que queramos ejecutar (es posible que
al depurar nos pregunte de nuevo por el emulador a utilizar).
Pulsaremos ahora F10 y comenzará la ejecución. Al comenzar la depuración el entorno de
desarrollo cambia y nos muestra nuevas ventanas:
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Automático:
Variables en
uso en cada
momento
Variables
locales:
Variables en
el método
Pila de llamadas Ventana de
comandos…
Línea donde se
encuentra la
ejecución
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Si pulsamos F10, ejecutará la primera instrucción MiAplicacion.factorial(2); y
continuará por la siguiente. Si en la siguiente en lugar de pulsar F10, pulsamos F11, se
introducirá en el método factorial, si posteriormente, en el método factorial volvéis a pulsar
F11 en la llamada recursiva, volverá a llevaros a factorial.
Comprobadlo, echadle un vistazo a la pila de llamadas y podréis ver el efecto de la
recursividad en la pila de llamadas:
Observad también como varía en valor de las variables mediante las ventanas de
Automático y Variables Locales. Estas ventanas permiten cambiar el valor de las
variables que se están utilizando.
Las tripas del programa, MSIL (MS Intermediate Language)
Para ver el código intermedio, generado por el compilador de C#, tendremos que usar una
herramienta llamada ildasm.exe que es un desensamblador de MSIL. Como es posible que sea
difícil de localizar dentro de la maraña de directorios de Windows y como probablemente no
esté en el PATH, una buena forma de usar las utilizades asociadas a Visual Studio es mediante
la consola Visual Studio Command Prompt. Cuando aparezca esta consola, ejecuta ildasm.exe
y aparecerá una utilidad gráfica.
Buscamos el fichero binario MiAplicación.exe y lo analizamos con el desensamblador de
lenguaje intermedio. Como se pude ver, no existe el método factorial, pero si nos fijamos en el
main, podremos comprobar que, como medida de eficiencia, el compilador lo ha incluido en el
interior de main.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Más adelante veremos cómo varía el código intermedio con programas más complejos.
Acceso a ficheros para depurar
Ahora que sabemos cómo depurar un programa, ver el contenido del código intermedio etc,
vamos a continuar. Las plataformas Windows Mobile no disponen de consola (se puede
instalar usando algunas utilidades GNU) por lo que no es posible hacer depuración sobre
consola, pero si en fichero de texto, que además es muy útil.
Para hacer esto vamos a crear un ensamblado o librería de log. En primer lugar vamos a dar un
espacio de nombres a nuestra aplicación. Hasta el momento, no hemos necesitado que
nuestra aplicación tuviera un nombre único, que la diferenciara de las demás a efectos de
reutilizar código, pero ahora va a ser necesario; por esta razón, cambiamos el código de la
práctica por el siguiente:
using System; namespace ComputacionRed.MiAplicacion { public class MiAplicacion { public static void Main(string[] args) { MiAplicacion ma = new MiAplicacion(); /* de momento no hace nada */ } } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Acto seguido miramos las propiedades de la aplicación y cambiamos el nombre del
ensamblado en la configuración:
Una vez hecho esto vamos a crear un nuevo proyecto. Sobre MiSolución, botón derecho
Añadir nuevo proyecto. Tipo Class Library. El nombre que le daremos será UtilidadLog.
Cambiamos el namespace de la aplicación tal y como aparece bajo estas líneas.
Actualice la información en las propiedades, de forma que el nombre del ensamblado y el
espacio de nombres predeterminado sean Utilidades.UtilidadLog
Observa que el nombre de la clase es independiente del nombre del fichero, no ocurre
igual en java.
A continuación daremos funcionalidad a la clase de Log, de forma que podamos escribir
información a un fichero que permita depurar el programa. Para ello, es necesario declarar en
using System; using System.Collections.Generic; using System.Text; namespace Utilidades.UtilidadLog { public class Log { } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
el código mediante using qué parte de la Base Class Library se utilizará. En concreto usaremos
System.IO.
Para escribir en un fichero usaremos la clase TextWriter, cuya documentación puede
encontrarse en http://msdn.microsoft.com/es‐es/library/system.io.textwriter(VS.80).aspx. La
función de log se utilizará en adelante para probar el correcto funcionamiento del programa y
debido a que las aplicaciones gráficas que veremos más adelante son multihilo (si pulsas un
botón y la ejecución tarda, puede que al pulsar otro botón se genere otro hilo de ejecución) y a
que el fichero sobre el que vamos a escribir es un recurso compartido: hay que usar un Mutex.
El paquete que tiene la implementación de TextWriter es System.IO y el que contiene la
implementación de Mutex es System.Threading. A continuación se muestra como utilizar
ambos en el programa de la clase Log. Esta clase muestra cómo usar un mutex y como escribir
en un fichero de texto.
using System; using System.IO; using System.Collections.Generic; using System.Text; using System.Threading; namespace Utilidades.UtilidadLog { public class Log { /* TextWriter */ TextWriter tw = null; Mutex fileMutex; int indent = 0; public Log(string fileName) { tw = new StreamWriter(fileName,true); fileMutex = new Mutex(); }
~Log() { tw.Flush(); tw.Close(); } public void Trace(String msg) { fileMutex.WaitOne(); for (int i = 0; i < indent; i++) tw.Write("\t"); tw.Write(msg); tw.Flush(); fileMutex.ReleaseMutex(); }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Los atributos de la clase Log son una referencia a la clase Textwriter, que permite
escribir al fichero; un semáforo Mutex, que controlará el acceso al recurso compartido
(fichero) ; y una variable de tipo entero que almacena la indentación a añadir a cada
línea.
El constructor de la clase crea una instancia de la clase Textwriter proporcionándole el
nombre del fichero a utilizar. Si compruebas la documentación del constructor de
StreamWriter podrás ver qué implica el segundo parámetro (true).
A continuación podemos ver una función cuanto menos extraña para aquellos sin
experiencia en C++. Es la función ~Log() que se conoce como destructor. En C++ esa
función se utiliza para liberar memoria una vez concluye la ejecución de la clase y el
objeto se destruye. E n los lenguajes como Java o cualquiera de los presentes en .NET,
no es necesario liberar memoria, eso lo hace el recolector de basura; en cambio, se
permite el uso de esta función para realizar una serie de tareas antes de destruir el
objeto (como en este caso, hacer flush y cerrar el fichero). Aunque no es necesario, en
ocasiones es útil.
El siguiente método, es Trace. Este método escribe una traza de log en el fichero.
Primero comprueba el semáforo y si es necesario espera un tiempo dado hasta que
deje de ser usado. En ese momento tabula el texto, escribe el mensaje, hace flush y
por último libera el semáforo para que otros métodos puedan usar el fichero.
El resto de los métodos no requieren explicación.
public void BeginTrace(String fname) { indent++; Trace(DateTime.Now.Hour +":"+ DateTime.Now.Minute + ":: Entrando en " + fname + "\n"); } public void EndTrace(String fname) { indent--; Trace(DateTime.Now.Hour + ":" + DateTime.Now.Minute + ":: Entrando en " + fname + "\n"); } public void Trace(String fname, String msg) { Trace(DateTime.Now.Hour + ":" + DateTime.Now.Minute + "::" + fname + "::" + msg + "\n"); } } } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Utilizando la clase Log desde otro programa
Para poder utilizar la clase Log desde MiAplicación, hacemos click con el botón derecho sobre
la carpeta References contenida en el proyecto aplicación y luego sobre Add Reference.
Vamos a la pestaña Proyectos y ahí encontraremos UtildadLog. La seleccionamos y pulsamos
aceptar.
A partir de ese momento podremos utilizar la
clase Log, contenida en el espacio de
nombres Utilidades.UtilidadLog.
A continuación probaremos la clase Log, pero
antes vamos a facilitar la tarea modificando
las propiedades del emulador. Para ello, en el
menú File del emulador, seleccionamos
configure… cuando aparece el cuadro de
diálogo en la caja de texto Shared Folder
navegamos hasta la ruta donde se encuentre
la aplicación compilada, es decir, carpetaDelProyecto\bin\debug.
A partir de ese momento si abrimos el explorador de ficheros en la PDA emulada y navegamos,
veremos que existe un directorio llamado Storage Card que simula una tarjeta SD introducida
en el slot. Si consultamos los ficheros contenidos en ella, veremos cómo aparecen los
contenidos en el directorio seleccionado.
Ahora vamos a modificar el programa para probar la clase de Log. Utilice el siguiente código:
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Como se puede apreciar, el fichero seleccionado para guardar los resultados del log se
encuentra en la carpeta de la aplicación dentro del PC (no de la PDA).
Lo siguiente que haremos, será probar la aplicación. Para ver el fichero con comodidad (puede
verse directamente en la PDA emulada, pero debido a que el tamaño de la pantalla no es muy
grande, es preferible hacerlo en el PC) lo abrimos con visual studio. A partir de este momento
no hace falta cerrarlo y volverlo a abrir para ver los cambios, si el fichero cambia, Visual Studio
lo notificará.
El resultado de la ejecución debe ser algo similar a esto:
using System; using Utilidades.UtilidadLog; namespace ComputacionRed.MiAplicacion { public class MiAplicacion { Log log; public MiAplicacion() { log = new Log("\\Storage Card\\milog.txt"); } public void mifuncion2() { log.BeginTrace("mifuncion2"); log.Trace("mifuncion2", "Un mensaje de mifuncion2"); log.EndTrace("mifuncion2"); } public void mifuncion() { log.BeginTrace("mifuncion"); log.Trace("mifuncion2", "Un mensaje de mifuncion1"); mifuncion2(); log.EndTrace("mifuncion"); } public static void Main(string[] args) { MiAplicacion ma = new MiAplicacion(); ma.mifuncion(); } } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
2:34:: Entrando en mifuncion 2:36:: Entrando en mifuncion 2:36::mifuncion2::Un mensaje de mifuncion1 2:36:: Entrando en mifuncion2 2:36::mifuncion2::Un mensaje de mifuncion2 2:36:: Entrando en mifuncion2 2:36:: Entrando en mifuncion
Observe que una llamada a una función dentro de otra función aumenta la
indentación.
Práctica 2. Introducción a .NET, programación gráfica.
El objetivo de esta práctica es programar una aplicación sencilla para familiarizarnos con el
entorno visual de programación de .NET con Formularios de Windows.
Conociendo el Entorno de Desarrollo Comenzamos por crear un nuevo proyecto con Visual Studio 2005 o 2008:
Una vez pulsemos, aparecerá
en la pantalla los posibles
proyectos. El número y tipo de
proyectos dependerá de los
SDKs instalados en el sistema:
Vamos a crear un proyecto
Visual C#, para dispositivo
limitado (Smart Device), usando
el SDK de Windows Mobile 5.0
o 6.0 dependiendo del que
tengamos instalado.
La plantilla del proyecto es Device Application. Le damos un nombre, por ejemplo miApp.
Seleccionamos la
ubicación que más
nos convenga.
Todos los proyectos
que hagamos, se
incorporan a una
solución,
utilizaremos la
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
solución que ya teníamos creada.
Una vez creado, aparecerá el interfaz de usuario. En el que podemos ver una imagen de lo que
será el programa. Además existen menús útiles para el desarrollo como son (Están
identificados en la imagen):
El explorador de soluciones
Vista de clases
Propiedades
Toolbox
Resultados
Lista de errores
Si no ves alguno de las pestañas marcadas con círculos sobre las imágenes, puedes usar el
menú View (ver) y pulsar sobre cada una de las que necesitas. Luego puedes arrastrarlas por la
pantalla para colocarlas donde te resulten más cómodas de usar.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Aplicación Hello world Se trata de la aplicación que todo el mundo ha hecho alguna vez para comenzar a aprender un
lenguaje. La aplicación tiene el efecto positivo de mostrar al usuario que algo funciona, a partir
de ahí… lo que imagines
Un vistazo al código
Antes de comenzar con la aplicación vamos a echar un vistazo al código generado por Visual
Studio y que nos permitirá programar la aplicación.
Vamos a ver el programa principal, la clase que permite la ejecución del formulario o ventana
sobre la que colocaremos controles como botones o cajas de texto. Para ver el código pulse
sobre program.cs como indica la figura:
Al hacer esto, pulsar sobre Ver Código o hacer doble click sobre
Program.cs, aparece el código en la pantalla principal.
El código que se verá será este (o muy parecido):
Este código es un programa principal como el que hemos visto en la anterior práctica, la única
diferencia es que ahora existe un atributo llamado MTAThread que se usa en las aplicaciones
de Formularios para conocer el lugar en el que comienza el programa.
Consulta el código de la clase Form1.cs, comprobarás que es una clase que hereda de
Form (la que gestiona los formularios en Windows).
A continuación vamos a añadir funcionalidad a la aplicación. Para comenzar, debes poder usar
el ToolBox o caja de herramientas. En ella encontrarás componentes gráficos para añadir a tu
aplicación.
using System; using System.Collections.Generic; using System.Windows.Forms; namespace miApp { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [MTAThread] static void Main() { Application.Run(new Form1()); } } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Para añadir nuevos controles gráficos, simplemente arrástralos desde el toolbox directamente
a la pantalla.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Añade varios elementos:
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Un textbox
Un botón con el nombre hola
Otro con el nombre mundo
Cada uno de los controles que se colocan en la pantalla tiene una serie de propiedades. Una es
el nombre dentro del programa Name otra es la información que aparece en pantalla Caption.
Al hacer dobre click sobre un botón, Visual Studio creará un método. Este método puede
usarse para cambiar las propiedades de los controles.
Añada también unas etiquetas Label de modo que el interfaz de usuario quede de la siguiente
manera:
Tu turno:
Da un nombre adecuado al nameSpace
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Coloca entradas de Log para ver qué ocurre con la aplicación. Pon una al comienzo de
cada clase y método, de forma que veas el flujo de la ejecución.
Para ello, tendrás que importar la clase de Log (añadir referencia…)
Tendrás que pasar la instancia de Log de una clase a otra para que todos
escriban sobre el mismo fichero. Para ello, añade un atributo Log a la clase
Form1.
public partial class Form1 : Form { Log log; public Form1() { InitializeComponent(); log = new Log("\\Storage Card\\fichero.txt"); } }
Añade funcionalidad a los métodos de los botones Hola y Mundo de forma que al
pulsar el primero aparezca la palabra Hola en el cuadro de texto. Para modificar el
texto utiliza la propiedad Text de del cuadro de texto.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Práctica 3. Introducción a sockets en .NET
Ahora que conoces el API de sockets de otros sistemas operativos y lenguajes como Java,
vamos a aprender cómo usar sockets desde .NET con el lenguaje C#.
En esta práctica, usaremos el Framework de .NET en lugar del Compact Framework, en
cualquier caso, el contenido de estas prácticas es trasladable a pocket pc directamente.
Estructura de clases
Para utilizar sockets es necesario importar las librerías de la class library System.Net y System.Net.Sockets. Para ello, creamos un proyecto de tipo aplicación visual con el SDK correspondiente y le damos el nombre de VistaCliente.cs al fichero de código con el formulario. El otro fichero será program.cs. Introducimos el siguiente código en los ficheros:
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
/* Programa.cs */ using System; using System.Text; using System.Collections.Generic; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using System.Diagnostics; namespace ComputacionRed.Sockets.Cliente { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [MTAThread] static void Main() {
} }
}
/* VistaCliente.cs [Formulario-Ver código]*/ using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; namespace ComputacionRed.Sockets.Cliente { public partial class VistaCliente : Form { public VistaCliente() { InitializeComponent(); } } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
La clase VistaCliente, es una clase que hereda de Form, por tanto se utiliza para proporcionar
una GUI al usuario. Por otro lado, tendremos una clase controlador ClienteConnection que
crearemos dentro del fichero programa.cs:
/* Programa.cs */ using System; using System.Text; using System.Collections.Generic; using System.Windows.Forms; using System.Net; using System.Net.Sockets; using System.Diagnostics; namespace ComputacionRed.Sockets.Cliente { static class Program { /// <summary> /// The main entry point for the application. /// </summary> [MTAThread] static void Main() {
} }
public class ClienteConnection { IPAddress dirServidor; Int32 serverPort = 0; IPEndPoint endPointServidor; Socket socket; Form vv = null; writeLog log = null; public ClienteConnection(Form vv) { this.vv = vv; } /* destructor */ ~ClienteConnection() { try { if (socket != null) if (socket.Connected) socket.Disconnect(false); } catch (Exception ex) { /* lo hemos intentado ... */ } }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
En la clase ClienteConnection tendremos los siguientes atributos:
IPAddress dirServidor : Estructura de dirección IP para conectar con el servidor
o http://msdn.microsoft.com/en‐us/library/system.net.ipaddress.aspx
IPEndPoint endPointServidor: Contiene tanto la dirección IP como el puerto
local etc necesarios para conectar con el servidor
o http://msdn.microsoft.com/en‐us/library/system.net.ipendpoint.aspx
Int32 serverPort: puerto del servidor
Socket socket: estructura donde alojar el estado del socket
o http://msdn.microsoft.com/en‐us/library/system.net.sockets.socket.aspx
o API de sockets de Berkeley
Form vv : Para poder controlar la visualización
writeLog log: Lo veremos más adelante, es un callback (delegado en .NET)
Localice el constructor de la clase ClienteConnection. ¿Qué parámetro recibe?
¿Qué hace el destructor de la clase ClienteConnection?
En la clase VistaCliente usaremos el siguiente código para el constructor y los métodos de
inicialización:
public delegate void writeLog(string msg); public partial class VistaCliente : Form { ClienteConnection cc = null; byte[] sendBytes; byte[] receiveBytes = new byte[2048]; public VistaCliente() { InitializeComponent(); } public void setController(ClienteConnection cc) { this.cc = cc; }
}
La clase VistaCliente tiene los siguientes atributos:
ClienteConnection cc = enlace con el controlador
byte[] sendBytes: buffer de datos a enviar al servidor
byte[] receiveBytes: buffer de datos con la respuesta del servidor.
Si echamos un vistazo al código anterior, veremos una declaración similar a un tipo:
public delegate void writeLog(string msg);
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Consiste en un puntero a función, es decir, existe un tipo de función llamada writeLog, que
recibe una String y la imprime para que el usuario tenga información a modo de log.
Interfaz gráfico
A continuación vamos a diseñar el siguiente interfaz gráfico:
Como puedes comprobar, hay varios controles (cajas de texto, botones, checkbox…) y algunos
de ellos están introducidos dentro de un contenedor. Esto es opcional, en cualquier caso, el
control que engloba a los demás (como por ejemplo Connection que engloba 3 cajas de texto,
tres botones y un label) puedes localizarlo en el toolbox como GroupBox.
Para que el código que se proporciona en los siguientes apartados funcione correctamente,
debes asegurarte de que los diferentes controles tienen la propiedad Name (dentro del
apartado Design) que se indica en las cajas de texto apuntadas por las diferentes flechas.
serverNameTextBox
textCheckBoxUserInput
sendDataTextB
oxUserInput
updateBinary
Button
binarySendData
responseText
responseBinary
logTextbox
testConnectionButton
ConnectionState
sendButton
button1
serverPort
IPTextbox
conectButton
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Función de log
A continuación vamos a resolver el problema del log. En la clase VistaCliente creamos la
función writeLog como se muestra a continuación:
public void setController(ClienteConnection cc) { this.cc = cc; } public void writelog(string msg) { logTextbox.AppendText(msg); }
Dicha función escribe añade texto en la caja de texto logTextbox cuando se la invoca.
Si prestamos atención, veremos que dicho método tiene los mismos tipos definidos en la
declaración del apartado anterior public delegate void writeLog(string msg) por
lo que puede usarse como delegado.
En la clase ClienteConnection, inmediatamente después del destructor, creamos los métodos:
/* set log */ public void setLogFn(writeLog fn) { this.log = fn; } /* write log */ public void trace(string msg) { StackTrace st = new StackTrace(false); string caller = st.GetFrame(1).GetMethod().Name; log(caller + " : " + msg + "\r\n"); } /* presenta la vista */ public Form getVista() { return vv; }
El método setLogFn recibe un puntero a una función de tipo writeLog y la guarda en el atributo
log. En método trace recibe una string, contruye un pila de llamadas y accede a la anterior para
conocer desde que función ha sido llamada la función de log para así incluirlo en el texto de la
línea de log. Finalmente, escribe la línea y le añade al final un retorno de carro y vuelve al
comienzo de la línea ( \r\n es equivalente al \n de C/C++). La función getVista devuelve una
instancia de la clase Form, así tanto el controlador como la vista, permanecen unidos.
Compruebe como ambas clases VistaCliente y ClienteConnection están enlazadas
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Sustituye el código del main por el siguiente:
static class Program { /// <summary> /// The main entry point for the application. /// </summary> [MTAThread] static void Main() { ClienteConnection cc = new ClienteConnection(new VistaCliente()); ((VistaCliente)cc.getVista()).setController(cc); cc.setLogFn(((VistaCliente)cc.getVista()).writelog); Application.Run(cc.getVista()); } }
¿Qué hace el código?
Comprobando la conectividad
En este apartado vamos a comprobar la conectividad de la red antes de usarla. Para ello, el
usuario dispone de un botón con el mensaje Test connection que cambiará el label
ConnectionState indicando ok o error dependiendo del problema.
En primer lugar vamos a diseñar dicha función. Para dar un error detallado sería necesario
interrogar al API de NDIS de Windows (controla los dispositivos de red) de forma que se
pudiera averiguar si existe conectividad o no, pero lo vamos a hacer desde el nivel más alto,
desde sockets. Lo primero que haremos será definir un tipo enumerado con los posibles
errores o estados:
public enum connState { ok = 0, dnsProblem = 1, socketProblem = 2, dnsAndSocketProblem = 3, networkErrorOrUnreachable = 4 }
Los posibles estados son:
1. No hay error
2. Existe un problema con el DNS (lo cual no significa que no haya conexión)
3. Problema con la librería de sockets, con independencia del DNS no se puede abrir una
conexión.
4. Hay problemas con el DNS y con los sockets
5. Es posible crear un socket pero probablemente la conexión sólo es local o un firewall
bloquea el tráfico
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
A continuación, vamos a plantear la estrategia del test:
1. Tratamos de resolver el nombre www.google.es
a. Si falla dnsProblem y continuamos
2. Tenemos o no DNS, pero puede que sólo local
a. Tratamos de conectar a una IP y si hay éxito continuamos
b. Tratamos de conectar a una IP y si no hay éxito salimos con error
socketProblem o dnsAndSocketProblem.
3. Tratamos de descargar una página web
a. Si hay éxito: ok
b. Si falla: networkErrorOrUnreachable
Por lo tanto, el código del método para probar la conectividad es el siguiente (inclúyelo como
métodos de la clase ClienteConnection:
private connState getError(connState current, connState promoteTo) { trace("fetch error"); if (current == connState.ok) return promoteTo; if (current == connState.dnsProblem) if (promoteTo == connState.socketProblem) { return connState.dnsAndSocketProblem; } else { return promoteTo; } return promoteTo; } public String getTestConnectionResultString() { return testSocketConnection().ToString(); } private connState testSocketConnection() { trace("testing client connection"); connState res = connState.ok; String testHttp = "GET /index.html HTTP/1.0\n\n"; String httpDoc = null; int recvLength = 0; Byte[] SendBytes = Encoding.ASCII.GetBytes(testHttp); Byte[] RecvBytes = new Byte[1024]; IPAddress testIP = null; IPEndPoint testEndPoint = null; Socket testSocket = null; try { testIP = Dns.GetHostEntry("www.google.es").AddressList[0]; testEndPoint = new IPEndPoint(testIP, 80); }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
catch (Exception ex) { trace("dns seems to be unavailable"); res = getError(res, connState.dnsProblem); } try { testIP = Dns.GetHostEntry("163.117.139.128").AddressList[0]; testEndPoint = new IPEndPoint(testIP, 80); testSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); } catch (Exception ex) { trace("something wrong with sockets"); return getError(res, connState.socketProblem); } try { testSocket.Connect(testEndPoint); testSocket.Send(SendBytes, SendBytes.Length, SocketFlags.None); recvLength = testSocket.Receive(RecvBytes, RecvBytes.Length, SocketFlags.None); } catch (Exception ex) { trace("error connecting"); return getError(res, connState.networkErrorOrUnreachable); } httpDoc = Encoding.ASCII.GetString(RecvBytes, 0, recvLength); //Codificamos la respuesta trace("finished"); return res; } }
Acabas de ver tu primer programa con sockets en .NET:
¿Cuál es el proceso para abrir una conexión con otro equipo?
¿Qué NameSpaces se utilizan?
¿Qué clases?
¿Notas diferencias con otros APIs?
Ahora incorpora la funcionalidad al botón de prueba (doble click y Visual Studio generará el
método, el código es el siguiente:
private void testConnectionButton_Click(object sender, System.EventArgs e) { ConnectionState.Text = cc.getTestConnectionResultString(); }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Interactuando con un servidor, preparando los datos
Antes de comenzar a enviar datos al servidor, es necesario prepararlos. El cliente que estamos
diseñando permite introducir datos de dos formas:
1. En modo texto: cualquier letra introducida se codifica con ASCII y se envía salvo el
código \n que se traduce a retorno de carro. Esto es interesante por si se quiere usar
HTTP directamente o para probar un servidor que estamos programando
a. Para ello, el usuario introduciría GET /index.html HTTP/1.0\n\n
directamente en el cuadro de texto sendDataTextBoxUserInput, haría click en
la casilla textCheckBoxUserInput (mirar la figura del GUI) y luego en
updateBinaryButton
2. Modo hexadecimal: Se introduce la información en hexadecimal. Si se quiere
introducir un buffer de datos 2FC487, se teclea 2F C4 87 y no se marca la casilla de
modo texto. En cualquier caso, siempre es necesario pulsar el botón Update Binary
antes de enviar algo al servidor.
Por lo tanto, necesitamos un método que prepare los datos del usuario para su envío al
servidor. El método se invoca cuando se pulsa el botón updateBinaryButton. Por tanto, para
programarlo, haga doble click sobre dicho botón. Analiza el siguiente código y úsalo:
private void updateBinaryButton_Click(object sender, System.EventArgs e) { sendBytes = null; binarySendData.Text = ""; String sendDataText = ""; int posicion = -1; if (textCheckBoxUserInput.Checked && sendDataTextBoxUserInput.Text.Length != 0) { if (sendDataTextBoxUserInput.Text.IndexOf("\\n") != -1) { /* hay retorno(s) de carro (http/telnet) */ String[] subs = sendDataTextBoxUserInput.Text.Split(new String[] { "\\n" }, StringSplitOptions.None); for (int i = 0; i < subs.Length; i++) { if (subs[i].Equals("")) sendDataText += "\n"; else sendDataText += subs[i]; } } else sendDataText = sendDataTextBoxUserInput.Text; sendBytes = Encoding.ASCII.GetBytes(sendDataText); } else { if (sendDataTextBoxUserInput.Text.Length >= 2) { string delimiter = " ";
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
byte result = 0x00; char[] number; string[] bytesString = sendDataTextBoxUserInput.Text.Split(delimiter.ToCharArray()); sendBytes = new byte[bytesString.Length]; for (int i = 0; i < bytesString.Length; i++) { try { number = bytesString[i].ToCharArray(); if (number.Length != 2) throw new Exception(""); byte.TryParse(number[0].ToString(), out result); sendBytes[i] = (byte)(result << 4); byte.TryParse(number[1].ToString(), out result); sendBytes[i] |= (byte)result; } catch (Exception ex) { cc.trace("Hay un error en el formato, recuerda: si es binario, debes escribir, por ejemplo, 12 34 AB, siendo estos numeros hexadecimales"); } } } } if (sendBytes != null) for (int i = 0; i < sendBytes.Length; i++) { binarySendData.AppendText(sendBytes[i].ToString("x") + " "); } }
¿Qué hacen los métodos de la clase String llamados Split, IndexOfAny y Equals?
¿Qué hace Encoding.ASCII.GetBytes?
¿Cómo se comprueba si los datos son texto o hexadecimales?
¿Cómo se comprueban los errores de formato en el caso hexadecimal?
Comprueba si todos los errores se corrigen
Utiliza la depuración línea por línea para ver qué hace cada parte del código. Lo
mejor es poner un breakpoint al comienzo del método y luego ir línea por línea con
F10.
Interactuando con un servidor, iniciando la conexión
Del apartado de prueba de conectividad, habrás aprendido a abrir un socket, ahora lo haremos
paso por paso.
Funcionalidad del botón Resolve
Cuando se pulsa el botón Resolve (button1), debe usarse este código:
private void button1_Click(object sender, EventArgs e)
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
{ if (serverNameTextBox.Text.Length > 0) { IpTextbox.Text = cc.getServerIP(serverNameTextBox.Text).ToString(); } else cc.trace("por favor, incluye el nombre del servidor"); }
Por lo tanto, puede comprobarse que la funcionalidad está en la clase ClienteConnection pese
a que los resultados (IP traducida) se muestren en la caja de texto IpTextbox.
Utiliza los siguientes métodos dentro de la clase ClienteConnection:
public IPAddress getServerIP(String serverName) { return Dns.GetHostEntry(serverName).AddressList[0]; } /* dada una ip o nombre de maquina, cambia la direccion del servidor */ public void setServerIP(String serverIPString) { dirServidor = Dns.GetHostEntry(serverIPString).AddressList[0]; } public void setServerPort(String port) { Int32 result = 0; try{ Int32.TryParse(port, out result); serverPort = result; }catch(Exception ex) { trace("es correcto el puerto?"); } }
Razona sobre lo que hace cada uno de ellos.
¿Cuáles cambian el valor de atributos de la clase ClienteConnection?
Pruébalo con www.google.es y con www.uc3m.es
Claramente google usa balanceo de carga con DNS (puedes comprobarlo desde un
intérprete de comandos (cmd) con el comando nslookup www.google.es
Si lo ejecutas varias veces puedes ver cómo cambian el grupo de IPs que
proporcionan el servicio de google.
¿Por qué el programa devuelve sólo la primera?. ¿Cómo lo cambiarías para
que te diera de las tres aleatoriamente?
Funcionalidad del botón connect! (conectButton)
Este método debe proporcionar al controlador todos los datos de dirección del servidor,
puerto necesario para conectar y además realizar control de errores. Utiliza el siguiente
código:
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
private void conectButton_Click(object sender, EventArgs e) { try { if (IpTextbox.Text.Length != 0 && serverNameTextBox.Text.Length != 0 && serverPort.Text.Length != 0) { cc.setServerIP(IpTextbox.Text); cc.setServerIP(serverNameTextBox.Text); cc.setServerPort(serverPort.Text); cc.connect(); } else throw new Exception(); } catch (Exception ex) { cc.trace("por favor, comprueba la dirección IP o el nombre de servidor"); } }
El único método que quedaría por programar sería connect() dentro de la clase
ClienteConnection. Este método debe hacer lo siguiente (para nuestros propósitos):
1. Comprobar si el socket es nulo.
a. Si es null, lo creará
b. Si no, comprueba si está conectado, y en ese caso lo desconecta
2. Crea un objeto IPEndPoint con la información necesaria
3. Crea un socket
4. Conecta
Analiza el siguiente código y úsalo en la aplicación:
/* conecta al servidor */ public void connect() { try { if (socket != null) if (socket.Connected) { trace("cerrando antiguas conexiones..."); socket.Disconnect(true); } trace("creando el endpoint..."); endPointServidor = new IPEndPoint(dirServidor, serverPort); trace("creando el socket..."); socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); trace("conectando"); socket.Connect(endPointServidor); } catch (Exception ex) { trace("error en la conexión" + ex.Message); }
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
}
Interactuando con un servidor, enviando y recibiendo datos
Finalmente debemos dar funcionalidad al botón send (sendButton) para que, a través de la
clase ClienteConnection, envíe los datos al servidor.
Utiliza el siguiente código para el botón send:
private void sendButton_Click(object sender, EventArgs e) { int recibido = cc.sendReceive(sendBytes, ref receiveBytes); cc.trace("recibidos " + recibido + " bytes."); responseBinary.Text = ""; for (int i = 0; i < recibido; i++) { responseBinary.AppendText(receiveBytes[i].ToString("X")); } String respuesta = Encoding.ASCII.GetString(receiveBytes, 0, recibido); //Codificamos la respuesta responseText.Text = ""; responseText.AppendText(respuesta); }
¿Qué hace este método?
¿Qué elementos de la GUI están involucrados?
Utiliza el siguiente código para la clase ClienteConnection:
public int sendReceive(byte[] sendBytes, ref byte[] receiveBytes) { try { int bytes_returned = 0; /* enviamos los bytes */ socket.Send(sendBytes, sendBytes.Length, SocketFlags.None); bytes_returned = socket.Receive(receiveBytes, receiveBytes.Length, SocketFlags.None); return bytes_returned; } catch (SocketException sExec) { trace("Error: " + sExec.Message); } return 0; }
¿Sabías que C#, a diferencia de Java, permite parámetros por referencia en los
métodos?
¿Qué palabra reservada crees que le indica al compilador que es por referencia y no
por valor?
Haz peticiones HTTP a varios servidores, prueba que todo funcione bien.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Práctica 4. Servidor en .NET
En esta práctica vamos a crear un sencillo servidor para atender las peticiones de los clientes.
Para ello crearemos una aplicación de consola:
Y usaremos el siguiente código como base para implementar el protocolo que comentaremos
a continuación:
using System; using System.Collections.Generic; using System.Text; using System.Net; using System.Net.Sockets; using System.IO; namespace ComputacionRed.Sockets.Servidor { class Program { static void Main(string[] args) { IPAddress direc = Dns.GetHostEntry("localhost").AddressList[0]; IPEndPoint Ep = new IPEndPoint(direc, 12345); Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); socket.Bind(Ep);
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
socket.Listen(100); Socket handler = socket.Accept(); byte[] bytes = new byte[1024]; //Declaramos un array de bytes de longitud 1024 int count; String data = ""; //Declaramos data, que sera donde se almacenaran los datos try { do { count = handler.Receive(bytes); data = System.Text.Encoding.ASCII.GetString(bytes, 0, count); /* Procesado de los mensajes del protocolo aqui */ bytes = System.Text.Encoding.ASCII.GetBytes(response); handler.Send(bytes, System.Text.Encoding.ASCII.GetByteCount(response), SocketFlags.None); } while (data != "Exit"); Console.WriteLine("Conexion finalizada"); handler.Shutdown(SocketShutdown.Both); handler.Close(); } catch (Exception ex) { Console.WriteLine("Excepcion" + ex.Message); } } } }
Analiza las líneas de código suministradas
Ahora debes crear un protocolo con el siguiente formato. Lo que se enviarán serán cadenas de
texto con la estructura $Comando$Valor. El protocolo es sin estado y los comandos y sus
posibles valores son:
Hello: Debe ir acompañado del nombre del cliente (ej. $Hello$Dani ). El servidor debe
responder $Hello$NiceToSeeYouAgain
Echo: Debe ir acompañado de un texto de longitud variable (ej. $Echo$Texto a repetir).
El servidor debe contestar $Echo$ + el texto mandado por el cliente
Date: No tiene valor, se envía únicamente el comando. El servidor debe responder
$Date$Dia/Mes/Año
Apuntes de Computación en la Red. Prácticas demostrativas de .NET
Daniel Díaz Sánchez, Andrés Marín López, Florina Almenarez (http://pervasive.it.uc3m.es) Departamento de Ingeniería Telemática. Universidad Carlos III de Madrid.
Time: No tiene valor, se envía únicamente el comando. El servidor debe responder
$Date$Hora:Minuto
Random: No tiene acompañamiento. El servidor debe devolver 20 bytes aleatorios.
Exit: finaliza la conexión
Para hacerlo, puede necesitar las siguientes Clases/métodos (usa google y el código
proporcionado hasta ahora para conseguirlo):
1. System.DateTime.Now
2. String.Split
3. System.Text.Encoding.ASCII.GetBytes
4. Random
5. System.Text.Encoding.ASCII.GetByteCount
Cambia ahora a protocolo con sesión, usa el mensaje de Hello para ello.
Cliente en consola
Ahora que has probado el servidor, crear un cliente basado en consola que interactúe con el
servidor, esta vez, sin ayuda…
Top Related