Entendiendo La Programación de Sockets TCP Con GTK# y

Está en la página 1de 15

Entendiendo la programacin de Sockets con

GTK# y .NET
por Martn A. O Mrquez <xomalli@gmail.com>

Qu son los sockets?


Los Sockets o mejor dicho los Berkeley Sockets (BSD IPC) son la combinacin exclusiva de una
direccin IP y un numero de puerto TCP que habilita a los servicios (procesos en ejecucin) de una
computadora el poder intercambiar datos a travs de la red. Los servicios de red utilizan los sockets
para comunicarse entre los equipos remotos. Por ejemplo el siguiente comando:
$ telnet 192.168.1.14 80

Este comando solicita una conexin desde un puerto aleatorio en el cliente (por ejemplo: 5643) al
puerto 80 en el servidor (que es el puerto asignado para el servicio HTTP). Con la siguiente figura (fig
1) se ilustra a detalle este esquema denominado cliente-servidor.
Fig 1 Una comunicacin utilizando nmeros de puerto para transmitir datos.

Histricamente los sockets son una API (Application Programming Interface) de programacin
estndar utilizada para construir aplicaciones de red que vienen desde el sistema UNIX BSD. Esta
interface de programacin para la capa 4 del modelo OSI (OSI Model Layer 4) permite a un
programador tratar una conexin de red como un flujo de bytes que puede escribirse o leerse sin
demasiada complejidad. Con un socket se pueden realizar siete operaciones bsicas:
1.
2.
3.
4.
5.
6.
7.

Conectarse a una mquina remota.


Enviar datos.
Recibir datos.
Cerrar una conexin.
Escuchar para los datos entrantes.
Aceptar conexiones desde maquinas remotas en el puerto enlazado.
Enlazarse a un puerto.

Los Sockets pueden ser orientados a la conexin (Stream Socket) o no (Message-based Socket).
Los Stream Sockets son ideales para transmitir grandes volmenes de informacin de manera
confiable. Una conexin Stream Socket se establece mediante el mecanismo three-hand shake de TCP,
los datos se transmiten y cada paquete se revisa para asegurarse de la exactitud en la transmisin.
Los Datagram Sockets son apropiados para transferencias de datos cortas, rpidas y sin necesidad de
un chequeo de errores. Los desarrolladores de aplicaciones los prefieren por ser rpidos y muy fciles
de programar.
Fig 2 Tipos de Socket

El Framework .NET posee las clases de alto y bajo nivel que encapsulan la funcionalidad de un Socket
(tanto TCP como UDP) para construir aplicaciones de red con relativa facilidad y sin preocuparse por

todo el intricado mecanismo de comunicacin que necesitara muchas lneas de cdigo. La siguiente
lista describe las clases principales:
NetworkStream: Una clase derivada de la clase Stream representa el flujo de datos de entrada
o de salida desde la red.
TcpClient: Crea conexiones TCP de red para conectarse a un socket de servidor.
TcpListener: Se utiliza para escuchar peticiones de red TCP.
UdpClient: Crea conexiones UDP de red con posibilidad de multicasting.
Socket: Es una clase de bajo nivel que envuelve a la implementacin winsock, las clases
TcpClient, TcpListener y UDPClient utilizan esta clase para sus operaciones, se puede afirmar
que la clase Socket tiene las operaciones de estas clases ms otras funcionalidades mucho ms
avanzadas y de ms bajo nivel.

Pasos para la construccin de un Servidor TCP GTK#


Un servidor TCP siempre est ejecutndose de forma continua hasta que recibe una solicitud de
conexin por parte de un cliente, cuando se recibe esta solicitud, el servidor establece una conexin con
el cliente y utiliza dicha conexin para el intercambio de datos. Si los programas se comunican a travs
de TCP los datos que se procesan se envan y se reciben como flujo de bytes.
Para demostrar estos conceptos escrib dos programas: el de un servidor y el de un cliente TCP. Ambos
utilizan una interfaz de usuario (GUI) en GTK# para comunicarse entre ellos mediante mensajes de
texto.
Fig 3 Ejemplo de un servidor TCP con una GUI GTK#.

Fig 4 Ejemplo de un cliente TCP con una GUI GTK#.

Pasos para la construccin de un Servidor TCP GTK#


El proyecto del servidor TCP GTK# se compone de 2 clases:
1. La clase MainWindowServer.cs es la clase que construye la GUI del programa, maneja los
eventos para enviar los mensajes al cliente y las excepciones o mensajes que el programa
notifique.
2. La clase Program.cs es la clase principal que donde se ejecuta el servidor.
Para construir el servidor TCP se requieren de los siguientes pasos:
1) Crear un objeto IPEndpoint que asocia una direccin IP y un nmero de puerto.
IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any,6000);

2) Crear un objeto TcpListener que reciba como argumento un objeto IPEndpoint. (Aqu el objeto
TcpListener oculta la intricada programacin de un Server Socket para una facilidad en la
programacin)
listener = new System.Net.Sockets.TcpListener(ipEndPoint);

3) Iniciar el objeto TcpListener para que escuche las peticiones.


listener.Start();

4) Se utilizar un ciclo para que el Server Socket escuche o espere indefinidamente hasta recibir una
peticin, cuando el servidor recibe la peticin crea una conexin hacia el cliente y regresa un objeto
Socket del ensamblado System.Net.Sockets.Socket.
connection = listener.AcceptSocket();

5) Se obtiene el flujo de comunicacin del Socket.


System.Net.Sockets.NetworkStream socketStream =
new System.Net.Sockets.NetworkStream(connection);

6) Finalmente se asocia el flujo de comunicacin del Server Socket con un escritor y un lector binario
para transferir y recibir datos a travs del flujo de comunicacin.
using(writer = new BinaryWriter(socketStream))
{
using(BinaryReader reader = new BinaryReader(socketStream))
{
//the stream goes here
}
}

Es muy importante que una vez que se finaliza la comunicacin con el cliente cerrar el flujo y la
conexin mediante con el mtodo Close de cada uno de los objetos.
socketStream.Close();
connection.Close();

El cdigo fuente completo de la clase MainWindowServer.cs.


using
using
using
using
using

System;
Gtk;
System.IO;
System.Threading;
System.Net;

namespace Samples.GtkNetworking
{
public class MainWindowServer : Gtk.Window
{
VBox mainLayout = new VBox();
HBox controlLayout = new HBox(false,2);
Entry txtMsg = new Entry();
Button btnSend = new Button(Stock.Ok);
TextView txtChat = new TextView();
TextView txtLog = new TextView();
Label msgLabel = new Label("Message: ");
System.Net.Sockets.Socket connection = null;
BinaryWriter writer = null;
System.Net.Sockets.TcpListener listener = null;
Thread readThread = null;
public MainWindowServer() : base(WindowType.Toplevel)
{
this.Title = "GTK# Network Server";
this.SetDefaultSize(343, 288);

this.DeleteEvent += new DeleteEventHandler(OnWindowDelete);


this.btnSend.Clicked += new EventHandler(SendMessage);
mainLayout.BorderWidth = 8;
controlLayout.BorderWidth = 8;
controlLayout.PackStart(msgLabel,false,true,0);
controlLayout.PackStart(txtMsg,true,true,0);
controlLayout.PackStart(btnSend,false,false,0);
mainLayout.PackStart(controlLayout,false,true,0);
mainLayout.PackStart(txtChat,true,true,0);
mainLayout.PackStart(new Label("Log "),false,true,0);
mainLayout.PackStart(txtLog,true,true,0);
this.Add(mainLayout);
this.ShowAll();
readThread = new Thread(RunServer);
readThread.Start();
}
protected void OnWindowDelete(object o, DeleteEventArgs args)
{
}

//Terminate all
System.Environment.Exit(System.Environment.ExitCode);
protected void SendMessage(object o,EventArgs args)
{
try
{
writer.Write(ChatMessage(txtMsg.Text));
txtChat.Buffer.Text += ChatMessage(txtMsg.Text);
txtMsg.Text = string.Empty;
}
catch (System.Net.Sockets.SocketException error)
{
LogMessage("Error: " + error.Message);
}
}
string ChatMessage(string msg)
{
return string.Format("Server ({0}): {1} {2}",
DateTime.Now.ToShortTimeString(),
msg,
Environment.NewLine);
}
void LogMessage(string msg)
{
txtLog.Buffer.Text += string.Format("{0} : {1}{2}",
DateTime.Now.ToShortTimeString(),
msg,
Environment.NewLine);
}
void RunServer()
{
int counter = 1;
try
{
//step 1: create endpoint

IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any,6000);


//Step 2: create TcpListener
listener = new System.Net.Sockets.TcpListener(ipEndPoint);
//Step 3: waits for connection request
listener.Start();
//Step 4: establish connection upon client request
while(true)
{
LogMessage("Waiting for connection...");
//step 5: accept an incoming connection
connection = listener.AcceptSocket();
//step 6: create the network stream object associated with
socket

//NOTE: Use the namespace to avoid conflicts with GTK.Socket


System.Net.Sockets.NetworkStream socketStream = new
System.Net.Sockets.NetworkStream(connection);
//step7: create the objects for transferring data across stream
using(writer = new BinaryWriter(socketStream))
{
using(BinaryReader reader = new BinaryReader(socketStream))
{
LogMessage(TcpFlags.SYN + " : " + counter);
//inform client that connection was ACK
writer.Write(ChatMessage(TcpFlags.ACK));
string theReply = "";
//read string data sent from client until receive the
FIN signal
do
{
try
{
//read the string sent to the server
theReply = reader.ReadString();
//display the message except the FIN
if(!theReply.Equals(TcpFlags.FIN))
txtChat.Buffer.Text += theReply;
else
{
LogMessage("Received " + TcpFlags.FIN);
LogMessage("Send " + TcpFlags.FIN + " to
Client");
writer.Write(TcpFlags.FIN);
}
}
catch (System.Exception)
{
break;
}
} while (theReply != TcpFlags.FIN
&& connection.Connected);
//Close connection
LogMessage("Close connection");
}
}
socketStream.Close();
connection.Close();
++counter;
}
}

catch (System.Exception ex)


{
LogMessage("Ex " + ex.ToString());
}
}
}

El cdigo fuente de la clase Program (Server).


using System;
using Gtk;
namespace Samples.GtkNetworking
{
class MainClass
{
public static void Main(string[] args)
{
Application.Init();
MainWindowServer win = new MainWindowServer();
win.Show();
Application.Run();
}
}
}

Pasos para la construccin de un cliente TCP GTK#


El proyecto del cliente TCP GTK# se compone de 2 clases:
1. La clase MainWindow.cs es la clase que construye la GUI del cliente, maneja los eventos para
recibir y enviar los mensajes al servidor, y mostrar las excepciones o mensajes que ocurran.
2. La clase Program.cs es la clase principal del cliente
Para construir el cliente TCP se requieren de los siguientes pasos, algunos son idnticos a los que se
escribieron para el servidor:
1) Crear un objeto IPEndpoint que asocia la direccin IP y el nmero de puerto del servidor,
generalmente estos datos son fijos ya que los servidores se configuran para tenerlos de manera esttica.
(en este ejemplo utilice una sola mquina como servidor y como cliente)
IPEndPoint localEndPoint =
new IPEndPoint(IPAddress.Loopback,6000);

2) Se crea un Socket de cliente y se conecta al puerto del servidor.


client.Connect(localEndPoint);

3) Se obtiene el flujo de comunicacin del Socket


output = client.GetStream();

4) Se crean los objetos lector y escritor para trabajar con el flujo de comunicacin.
using(writer = new BinaryWriter(output))
{
using(reader = new BinaryReader(output))
{
//the stream goes here
}
}

Finalmente cuando se termina la comunicacin con el servidor, se cierra el flujo de datos y la conexin
con el mtodo Close de cada objeto.
output.Close();
client.Close();

El cdigo fuente de la clase MainWindow (cliente)


using
using
using
using
using
using

System;
Gtk;
System.Net.Sockets;
System.Net;
System.IO;
System.Threading;

namespace Samples.GtkNetworking
{
public class MainWindow : Gtk.Window
{
VBox mainLayout = new VBox();
HBox controlLayout = new HBox(false, 2);
HBox connectedLayout = new HBox(false,2);
Entry txtMsg = new Entry();
Button btnSend = new Button(Stock.Ok);
Button btnDisconnect = new Button(Stock.Disconnect);
TextView txtChat = new TextView();
TextView txtLog = new TextView();
Label msgLabel = new Label("Message: ");
TcpClient client = new TcpClient();
NetworkStream output = null;
BinaryWriter writer = null;
BinaryReader reader = null;
string message = "";
Thread readThread = null;
public MainWindow() : base(WindowType.Toplevel)
{
this.Title = "GTK# Network client";
this.SetDefaultSize(343, 288);
this.DeleteEvent += new DeleteEventHandler(OnWindowDelete);
this.btnSend.Clicked += new EventHandler(SendMessage);
this.btnDisconnect.Clicked += new EventHandler(SendDisconnect);
mainLayout.BorderWidth = 8;

connectedLayout.BorderWidth = 8;
connectedLayout.PackStart(btnDisconnect,false,false,0);
controlLayout.BorderWidth = 8;
controlLayout.PackStart(msgLabel,false,true,0);
controlLayout.PackStart(txtMsg,true,true,0);
controlLayout.PackStart(btnSend,false,false,0);
mainLayout.PackStart(connectedLayout,false,true,0);
mainLayout.PackStart(txtChat,true,true,0);
mainLayout.PackStart(controlLayout,false,true,0);
mainLayout.PackStart(new Label("Log"),false,true,0);
mainLayout.PackStart(txtLog,true,true,0);
this.Add(mainLayout);
this.ShowAll();
readThread = new Thread(new ThreadStart(RunClient));
readThread.Start();
}
protected void OnWindowDelete(object o, DeleteEventArgs args)
{
System.Environment.Exit(System.Environment.ExitCode);
}
protected void SendDisconnect(object o,EventArgs args)
{
if(client.Connected)
{
try
{
//Send disconnected signal
writer.Write(TcpFlags.FIN);
}
catch(SocketException error)
{
LogMessage("Error " + error.Message);
}
}
else
LogMessage("You are not connected");
}
protected void SendMessage(object o,EventArgs args)
{
try
{
writer.Write(ChatMessage(txtMsg.Text));
txtChat.Buffer.Text += ChatMessage(txtMsg.Text);
txtMsg.Text = string.Empty;
}
catch (SocketException error)
{
LogMessage("Error " + error.Message);
}
}
String ChatMessage(string msg)
{
return string.Format("Client ({0}): {1} {2}",
DateTime.Now.ToLongTimeString(),

msg,
Environment.NewLine);
}
void LogMessage(string msg)
{
txtLog.Buffer.Text += string.Format("{0} : {1}{2}",
DateTime.Now.ToShortTimeString(),
msg,
Environment.NewLine);
}
protected void RunClient()
{
try
{
//step 1 create a local endpoint
IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Loopback,6000);
//step 2 create a socket client and connect
client.Connect(localEndPoint);
LogMessage(TcpFlags.SYN);
//step 3 get the network stream associated with tcpclient
output = client.GetStream();
//step 4 create the objects for writing and reading across the stream
using(writer = new BinaryWriter(output))
{
using(reader = new BinaryReader(output))
{
//Receive message until receive the FIN signal
do
{
try
{
message = reader.ReadString();
if(!message.Equals(TcpFlags.FIN))
txtChat.Buffer.Text += message;
else
LogMessage("Received " + TcpFlags.FIN + " from server");
}
catch (System.Exception error)
{
LogMessage("Error: " + error.Message);
}
} while (message != TcpFlags.FIN);
LogMessage("Closing connection...");
}
}
output.Close();
client.Close();
}
catch (System.Exception ex)
{
LogMessage(" Ex " + ex.Message);
}
}

}
}

El cdigo fuente de la clase Program (cliente)

using System;
using Gtk;
namespace Samples.GtkNetworking
{
class MainClass
{
public static void Main(string[] args)
{
Application.Init();
MainWindow win = new MainWindow();
win.Show();
Application.Run();
}
}
}

La clase TcpFlags
Ambos proyectos utilizan la clase TcpFlags, la cual pretende ilustrar bsicamente como son las
banderas TCP (aqu ms detalles de las TCP Flags) que utiliza la capa de transporte (layer 4) para
manejar la comunicacin entre dos mquinas.
El cdigo fuente de la clase TcpFlags
using System;
namespace Samples.GtkNetworking
{
public class TcpFlags
{
public const string FIN = "Connection FIN";
public const string ACK = "Connection ACK";
public const string SYN = "Connection SYN";
}
}

A continuacin unas imgenes del cliente y servidor comunicndose entre si.

Fig 5 Enviando un mensaje desde el cliente al servidor.

Fig 6 Recibiendo el mensaje del cliente.

Fig 7 Envindole un mensaje al cliente desde el servidor.

Fig 8 Desconectndose del servidor.

Fig 9 Recibiendo la seal de desconexion del Server.

Descarga el cdigo fuente de la clase TcpFlags.cs


Descarga la solucin del servidor TCP GTK#.
Descarga la solucin del cliente TCP GTK#.
Este documento est protegido bajo la licencia de documentacin libre Free Documentacion License del
Proyecto GNU, para consulta ver el sitio http://www.gnu.org/licenses/fdl.txt , toda persona que lo desee est
autorizada a usar, copiar y modificar este documento segn los puntos establecidos en la Licencia FDL

También podría gustarte