Broadcast mediante UDP
Enviar mensajes a toda una red utilizando la clase Socket

Fecha: 30/Abr/2005 (28/04/05)
Autor: Gustavo Alegre Hidalgo ([email protected])

 


Basándome en el artículo sobre Broadcast que publiqué para Visual Basic 6.0, decidí desarrollar uno igual para .NET. Esta versión del programa utiliza la clase Socket de System.Net.Sockets e hilos de ejecución. Programé este ejemplo utilizando la Beta 2 de Visual Basic .NET y C# 2005 (Whidbey).

Funcionamiento:

El programa consiste en un Socket que va a enviar un paquete de datos (texto) a la dirección Broadcast de toda la red local (System.Net.IPAddress.Broadcast) mediante un puerto determinado (en el ejemplo: 20145) y haciendo uso del protocolo UDP. Para recibir datos entrantes vamos a crear un hilo que se encargará de recepcionarlos mediante el método "ReceiveFrom" y un bucle que terminará al finalizar la aplicación. Una instancia del programa será capaz de enviar y recibir mensajes públicos desde cualquier computadora conectada a la red.

Creamos un nuevo proyecto y diseñaremos un formulario similar a la siguiente figura:

Modelo del formulario para la aplicación
Modelo del formulario para la aplicación


Nombres de los controles:

txtMensaje: Contiene el mensaje que se desea enviar.
cmdEnviar: Al hacer clic en este botón, el mensaje se envía a toda la red.
txtDatosRecibidos: Todos los mensajes enviados desde otras PCs se mostrarán en esta caja de texto. Debe ser multilínea y con la propiedad ReadOnly a True.

Código del formulario para Visual Basic .NET:
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading

Public Class frmMain

#Region "Variables"
    'Variable de objeto que contiene el socket
    Dim ElSocket As New Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp)

    'Variable que contiene al hilo encargado de recibir los datos
    Dim HiloRecibir As Thread

    'Variable que indica si el programa se está cerrando
    Dim Saliendo As Boolean = False

    'Variables temporales para almacenar los datos recibidos
    Dim DireccIP As String, ContenidoMensaje As String

#End Region

    Private Sub txtMensaje_TextChanged(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles txtMensaje.TextChanged
        'Cuando txtMensaje esté vacío, deshabilitar el botón de envío.
        cmdEnviar.Enabled = (txtMensaje.TextLength > 0)
    End Sub

    Private Sub cmdEnviar_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdEnviar.Click
        'Contiene la dirección de Broadcast y el puerto utilizado
        Dim DirecciónDestino As New IPEndPoint(IPAddress.Broadcast, 20145)
        'Buffer que guardará los datos hasta que se envíen
        Dim DatosBytes As Byte() = Encoding.Default.GetBytes(txtMensaje.Text)

        'Envía los datos
        ElSocket.SendTo(DatosBytes, DatosBytes.Length, SocketFlags.None, DirecciónDestino)

        txtMensaje.Clear() 'Limpia txtMensaje
        txtMensaje.Focus() 'Mueve el foco hacia txtMensaje
    End Sub

    Private Sub frmMain_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
        Saliendo = True 'Indica que se está saliendo del programa
        ElSocket.Close() 'Cierra el socket
        HiloRecibir.Abort() 'Termina el proceso del hilo
    End Sub

    Private Sub frmMain_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
        'Separamos el puerto 200145 para usarlo en nuestra aplicación
        ElSocket.Bind(New IPEndPoint(IPAddress.Any, 20145))
        'Habilitamos la opción Broadcast para el socket
        ElSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, True)

        HiloRecibir = New Thread(AddressOf RecibirDatos) 'Crea el hilo
        HiloRecibir.Start() 'Inicia el hilo
    End Sub

    Private Sub RecibirDatos()
        'Mientras el inidicador de salida no sea verdadero
        Do Until Saliendo

            'Variable para obtener la IP de la máquína remitente
            Dim LaIPRemota As New IPEndPoint(IPAddress.Any, 0)
            'Variable para almacenar la IP temporalmente
            Dim IPRecibida As EndPoint = CType(LaIPRemota, EndPoint)
            Dim RecibirBytes(255) As Byte 'Buffer
            Dim Datos As String = "" 'Texto a mostrar

            Try
                'Recibe los datos
                ElSocket.ReceiveFrom(RecibirBytes, RecibirBytes.Length, SocketFlags.None, IPRecibida)
                'Los convierte y lo guarda en la variable Datos
                Datos = Encoding.Default.GetString(RecibirBytes)
            Catch ex As SocketException
                If ex.ErrorCode = 10040 Then 'Datos muy largos
                    Datos &= "[truncado]" 'Añade la cadena "[truncado]" al texto recibido
                Else
                    'Muestra el mensaje de error
                    MsgBox("Error '" & ex.ErrorCode.ToString & "' " & ex.Message, MsgBoxStyle.Critical, "Error al recibir datos")
                End If
            End Try

            'Convierte el tipo EndPoint a IPEndPoint con sus respectivas variables
            LaIPRemota = CType(IPRecibida, IPEndPoint)
            'Guarda los datos en variables temporales
            DireccIP = LaIPRemota.Address.ToString
            ContenidoMensaje = Datos.ToString

            'Invoca al evento que mostrará los datos en txtDatosRecibidos
            txtDatosRecibidos.Invoke(New EventHandler(AddressOf ActualizarTextoMensaje))

        Loop
    End Sub

    Private Sub txtDatosRecibidos_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtDatosRecibidos.TextChanged
        'Mostrar siempre la última línea del TextBox.
        txtDatosRecibidos.SelectionStart = txtDatosRecibidos.TextLength
        txtDatosRecibidos.ScrollToCaret()
    End Sub

    Protected Sub ActualizarTextoMensaje(ByVal sender As Object, ByVal e As System.EventArgs)
        'Si txtDatosRecibidos está vacío:
        If txtDatosRecibidos.TextLength = 0 Then
            txtDatosRecibidos.Text = DireccIP & ">" & ContenidoMensaje
        Else
            'de lo contrario insertar primero un salto de línea y luego los datos.
            txtDatosRecibidos.Text &= vbCrLf & DireccIP & ">" & ContenidoMensaje
        End If
    End Sub

End Class

Código del formulario para Visual C#:

using System;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace Broadcast_utilizando_UDP
{
    public partial class frmMain : Form
    {
        #region "Variables"
            //Variable de objeto que contiene el socket
            Socket ElSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            
            //Variable que contiene al hilo encargado de recibir los datos
            Thread HiloRecibir;
            
            //Variable que indica si el programa se está cerrando
            bool Saliendo = false;
        
            //Variables temporales para almacenar los datos recibidos
            string DireccIP; string ContenidoMensaje;
        #endregion

        public frmMain()
        {
            InitializeComponent();
        }

        private void txtMensaje_TextChanged(object sender, EventArgs e)
        {
            //Cuando txtMensaje esté vacío, deshabilitar el botón de envío.
            cmdEnviar.Enabled = (txtMensaje.TextLength > 0);
        }

        private void cmdEnviar_Click(object sender, EventArgs e)
        {
            //Contiene la dirección de Broadcast y el puerto utilizado
            IPEndPoint DirecciónDestino = new IPEndPoint(IPAddress.Broadcast, 20145);
            //Buffer que guardará los datos hasta que se envíen
            byte[] DatosBytes = Encoding.Default.GetBytes(txtMensaje.Text);

            //Envía los datos
            ElSocket.SendTo(DatosBytes, DatosBytes.Length, SocketFlags.None, DirecciónDestino);

            txtMensaje.Clear(); //Limpia txtMensaje
            txtMensaje.Focus(); //Mueve el foco hacia txtMensaje
        }

        private void frmMain_FormClosing(object sender, FormClosingEventArgs e)
        {
            Saliendo = true; //Indica que se está saliendo del programa
            ElSocket.Close(); //Cierra el socket
            HiloRecibir.Abort(); //Termina el proceso del hilo
        }

        private void frmMain_Load(object sender, EventArgs e)
        {
            //Separamos el puerto 200145 para usarlo en nuestra aplicación
            ElSocket.Bind(new IPEndPoint(IPAddress.Any, 20145));
            //Habilitamos la opción Broadcast para el socket
            ElSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
            HiloRecibir = new Thread(RecibirDatos); //Crea el hilo
            HiloRecibir.Start(); //Inicia el hilo
        }

        private void RecibirDatos()
        {
            //Mientras el inidicador de salida no sea verdadero
            while(!Saliendo){
                //Variable para obtener la IP de la máquína remitente
                IPEndPoint LaIPRemota = new IPEndPoint(IPAddress.Any, 0);
                //Variable para almacenar la IP temporalmente
                EndPoint IPRecibida = (EndPoint)LaIPRemota;
                byte[] RecibirBytes = new byte[256]; //Buffer
                string Datos = ""; //Texto a mostrar

                try{
                    //Recibe los datos
                    ElSocket.ReceiveFrom(RecibirBytes, RecibirBytes.Length, SocketFlags.None, ref IPRecibida);
                    //Los convierte y lo guarda en la variable Datos
                    Datos = Encoding.Default.GetString(RecibirBytes);
                }catch (SocketException ex){
                    if(ex.ErrorCode == 10040){ //Datos muy largos
                        Datos += "[truncado]"; //Añade la cadena "[truncado]" al texto recibido
                    }else{
                        //Muestra el mensaje de error
                        MessageBox.Show("Error '" + ex.ErrorCode + "' " + ex.Message,"Error al recibir los datos", MessageBoxButtons.OK,MessageBoxIcon.Error);
                    }
                }

                //Convierte el tipo EndPoint a IPEndPoint con sus respectivas variables
                LaIPRemota = (IPEndPoint)IPRecibida;
                //Guarda los datos en variables temporales
                DireccIP = LaIPRemota.Address.ToString();
                ContenidoMensaje = Datos.ToString();

                //Invoca al evento que mostrará los datos en txtDatosRecibidos
                txtDatosRecibidos.Invoke(new EventHandler(ActualizarTextoMensaje));
            }
        }

        private void txtDatosRecibidos_TextChanged(object sender, EventArgs e)
        {
            //Mostrar siempre la última línea del TextBox.
            txtDatosRecibidos.SelectionStart = txtDatosRecibidos.TextLength;
            txtDatosRecibidos.ScrollToCaret();
        }

        protected void ActualizarTextoMensaje(object sender, EventArgs e)
        {
            //Si txtDatosRecibidos está vacío:
            if(txtDatosRecibidos.TextLength == 0){
                txtDatosRecibidos.Text = DireccIP + ">" + ContenidoMensaje;
            }else{
                //de lo contrario insertar primero un salto de línea y luego los datos.
                txtDatosRecibidos.Text += "\r\n" + DireccIP + ">" + ContenidoMensaje;
            }
        }

    }
}

Espacios de nombres usados en el código de este artículo:

System.Net
System.Net.Sockets
System.Text
System.Threading


Fichero con el código de ejemplo para VB .NET: galegre_BroadcastUDPvbnet.zip - Tamaño 18.4 KB

Fichero con el código de ejemplo para Visual C#: galegre_BroadcastUDPcs.zip - Tamaño 14.8 KB


ir al índice