Buscar..


Introducción

Al usar sockets asíncronos, un servidor puede escuchar las conexiones entrantes y, mientras tanto, hacer alguna otra lógica, en contraste con el socket síncrono cuando están escuchando, bloquean el hilo principal y la aplicación deja de responder y se congelará hasta que el cliente se conecte.

Observaciones

Zócalo y red

¿Cómo acceder a un servidor fuera de mi propia red? Esta es una pregunta común y cuando se la pregunta se marca principalmente como tema.

Lado del servidor

En la red de su servidor necesita reenviar su enrutador a su servidor.

Por ejemplo, PC donde el servidor se está ejecutando en:

IP local = 192.168.1.115

El servidor está escuchando el puerto 1234.

Reenvíe las conexiones entrantes en el enrutador del Port 1234 al 192.168.1.115

Lado del cliente

Lo único que necesitas cambiar es la IP. No desea conectarse a su dirección de bucle de retorno sino a la IP pública de la red en la que se ejecuta su servidor. Esta IP la puedes obtener aquí .

 _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("10.10.10.10"), 1234));

Así que ahora creas una solicitud en este punto final: 10.10.10.10:1234 si hiciste que el puerto de propiedad reenvíe tu enrutador, tu servidor y el cliente se conectarán sin ningún problema.

Si desea conectarse a una IP local, no tendrá que portforwart, simplemente cambie la dirección de loopback a 192.168.1.178 o algo así.

Enviando datos:

Los datos se envían en una matriz de bytes. Debe empaquetar sus datos en una matriz de bytes y descomprimirlos en el otro lado.

Si está familiarizado con el socket, también puede intentar cifrar su matriz de bytes antes de enviar. Esto evitará que alguien robe tu paquete.

Ejemplo de Socket Asíncrono (Cliente / Servidor).

Ejemplo del lado del servidor

Crear escucha para el servidor

Comience con la creación de un servidor que maneje los clientes que se conecten y las solicitudes que se enviarán. Así que crea una clase de oyente que manejará esto.

class Listener
{
    public Socket ListenerSocket; //This is the socket that will listen to any incoming connections
    public short Port = 1234; // on this port we will listen

    public Listener()
    {
        ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }
 }

Primero necesitamos inicializar el socket de escucha donde podemos escuchar cualquier conexión. Vamos a usar un Socket Tcp, por eso usamos SocketType.Stream. También especificamos a qué puerto debe escuchar el servidor.

Entonces comenzamos a escuchar cualquier conexión entrante.

Los métodos de árbol que usamos aquí son:

  1. ListenerSocket.Bind ();

    Este método une el socket a un IPEndPoint . Esta clase contiene el host y la información del puerto local o remoto que necesita una aplicación para conectarse a un servicio en un host.

  2. ListenerSocket.Listen (10);

    El parámetro backlog especifica el número de conexiones entrantes que pueden ponerse en cola para su aceptación.

  3. ListenerSocket.BeginAccept ();

    El servidor comenzará a escuchar las conexiones entrantes y continuará con otra lógica. Cuando hay una conexión, el servidor vuelve a este método y ejecutará el método AcceptCallBack

    public void StartListening()
    {
        try
        {                
                MessageBox.Show($"Listening started port:{Port} protocol type: {ProtocolType.Tcp}");                    
                ListenerSocket.Bind(new IPEndPoint(IPAddress.Any, Port));
                ListenerSocket.Listen(10);
                ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);                
        }
        catch(Exception ex)
        {
            throw new Exception("listening error" + ex);
        }
    }

Así que cuando un cliente se conecta podemos aceptarlos por este método:

Tres métodos que utilizamos aquí son:

  1. ListenerSocket.EndAccept ()

    Comenzamos la devolución de llamada con Listener.BeginAccept() fin ahora que tenemos que terminar esa llamada. The EndAccept() método The EndAccept() acepta un parámetro IAsyncResult, esto almacenará el estado del método asíncrono. Desde este estado podemos extraer el socket del que provenía la conexión entrante.

  2. ClientController.AddClient()

    Con el socket que obtuvimos de EndAccept() creamos un Cliente con un método propio (código ClientController debajo del ejemplo del servidor) .

  3. ListenerSocket.BeginAccept ()

    Necesitamos comenzar a escuchar nuevamente cuando el socket haya terminado con el manejo de la nueva conexión. Pase en el método que capturará esta devolución de llamada. Y también pase int en el socket del oyente para que podamos reutilizarlo para las próximas conexiones.

    public void AcceptCallback(IAsyncResult ar)
    {
        try
        {
            Console.WriteLine($"Accept CallBack port:{Port} protocol type: {ProtocolType.Tcp}");
            Socket acceptedSocket = ListenerSocket.EndAccept(ar);               
            ClientController.AddClient(acceptedSocket);

            ListenerSocket.BeginAccept(AcceptCallback, ListenerSocket);
        }
        catch (Exception ex)
        {
            throw new Exception("Base Accept error"+ ex);
        }
    }

Ahora tenemos un zócalo de escucha, pero ¿cómo recibimos los datos enviados por el cliente que es lo que muestra el siguiente código?

Crear un receptor de servidor para cada cliente

Primero, cree una clase de recepción con un constructor que tome un Socket como parámetro:

    public class ReceivePacket
    {
        private byte[] _buffer;
        private Socket _receiveSocket;

        public ReceivePacket(Socket receiveSocket)
        {
           _receiveSocket = receiveSocket;
        }
    }

En el siguiente método, primero comenzamos con darle al búfer un tamaño de 4 bytes (Int32) o el paquete contiene las partes {longitud, datos reales}. Así que los primeros 4 bytes nos reservamos para la longitud de los datos el resto para los datos reales.

A continuación utilizamos el método BeginReceive () . Este método se utiliza para comenzar a recibir de los clientes conectados y cuando reciba datos ejecutará la función ReceiveCallback .

    public void StartReceiving()
    {
        try
        {
            _buffer = new byte[4];
            _receiveSocket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, null);
        }
        catch {}
    }

    private void ReceiveCallback(IAsyncResult AR)
    {
        try
        {
            // if bytes are less than 1 takes place when a client disconnect from the server.
            // So we run the Disconnect function on the current client
            if (_receiveSocket.EndReceive(AR) > 1)
            {
                // Convert the first 4 bytes (int 32) that we received and convert it to an Int32 (this is the size for the coming data).
                _buffer = new byte[BitConverter.ToInt32(_buffer, 0)];  
                // Next receive this data into the buffer with size that we did receive before
                _receiveSocket.Receive(_buffer, _buffer.Length, SocketFlags.None); 
                // When we received everything its onto you to convert it into the data that you've send.
                // For example string, int etc... in this example I only use the implementation for sending and receiving a string.

                // Convert the bytes to string and output it in a message box
                string data = Encoding.Default.GetString(_buffer);
                MessageBox.Show(data);
                // Now we have to start all over again with waiting for a data to come from the socket.
                StartReceiving();
            }
            else
            {
                Disconnect();
            }
        }
        catch
        {
            // if exeption is throw check if socket is connected because than you can startreive again else Dissconect
            if (!_receiveSocket.Connected)
            {
                Disconnect();
            }
            else
            {
                StartReceiving();
            }
        }
    }

    private void Disconnect()
    {
        // Close connection
        _receiveSocket.Disconnect(true);
        // Next line only apply for the server side receive
        ClientController.RemoveClient(_clientId);
        // Next line only apply on the Client Side receive
        Here you want to run the method TryToConnect()
    }

Así que hemos configurado un servidor que puede recibir y escuchar las conexiones entrantes. Cuando los clientes se conectan, se agregará a una lista de clientes y cada cliente tendrá su propia clase de recepción. Para hacer que el servidor escuche:

Listener listener = new Listener();
listener.StartListening();

Algunas clases que uso en este ejemplo

    class Client
    {
        public Socket _socket { get; set; }
        public ReceivePacket Receive { get; set; }
        public int Id { get; set; }

        public Client(Socket socket, int id)
        {
            Receive = new ReceivePacket(socket, id);
            Receive.StartReceiving();
            _socket = socket;
            Id = id;
        }
    }

     static class ClientController
     {
          public static List<Client> Clients = new List<Client>();

          public static void AddClient(Socket socket)
          {
              Clients.Add(new Client(socket,Clients.Count));
          }

          public static void RemoveClient(int id)
          {
              Clients.RemoveAt(Clients.FindIndex(x => x.Id == id));
          }
      }

Ejemplo del lado del cliente

Conectando al servidor

En primer lugar, queremos crear una clase que se conecte con el nombre del servidor que le damos: Connector:

class Connector
{
    private Socket _connectingSocket;
}

El siguiente método para esta clase es TryToConnect ()

Este método goth algunas cosas interesantes:

  1. Crea el zócalo;

  2. A continuación hago un bucle hasta que el zócalo está conectado.

  3. En cada bucle, solo mantiene el subproceso durante 1 segundo, no queremos DOS del servidor XD

  4. Con Connect () intentará conectarse al servidor. Si falla, lanzará una excepción, pero el programa mantendrá el programa conectado al servidor. Puedes usar un método Connect CallBack para esto, pero solo iré por llamar a un método cuando el Socket esté conectado.

  5. Observe que el Cliente ahora está intentando conectarse a su PC local en el puerto 1234.

     public void TryToConnect()
     {
         _connectingSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
         
          while (!_connectingSocket.Connected)
          {
              Thread.Sleep(1000);
    
              try
              {
                  _connectingSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 1234));
              }
              catch { }
          }
          SetupForReceiveing();
         }
     }
    
     private void SetupForReceiveing()
     {
        // View Client Class bottom of Client Example
         Client.SetClient(_connectingSocket);
         Client.StartReceiving();
     }
    

Enviando un mensaje al servidor.

Así que ahora tenemos una aplicación casi final o Socket. Lo único que no tenemos Jet es una clase para enviar un mensaje al servidor.

public class SendPacket
{
    private Socket _sendSocked;

    public SendPacket(Socket sendSocket)
    {
        _sendSocked = sendSocket;
    }

    public void Send(string data)
    {
        try
        {         
            /* what hapends here:
                 1. Create a list of bytes
                 2. Add the length of the string to the list.
                    So if this message arrives at the server we can easily read the length of the coming message.
                 3. Add the message(string) bytes
            */
  
            var fullPacket = new List<byte>();
            fullPacket.AddRange(BitConverter.GetBytes(data.Length));
            fullPacket.AddRange(Encoding.Default.GetBytes(data));

            /* Send the message to the server we are currently connected to.
            Or package stucture is {length of data 4 bytes (int32), actual data}*/
            _sendSocked.Send(fullPacket.ToArray());
        }
        catch (Exception ex)
        {
            throw new Exception();
        }
    }

Finalmente, active dos botones uno para conectar y el otro para enviar un mensaje:

    private void ConnectClick(object sender, EventArgs e)
    {
        Connector tpp = new Connector();
        tpp.TryToConnect();
    }

    private void SendClick(object sender, EventArgs e)
    {
        Client.SendString("Test data from client");
    }

La clase cliente que utilicé en este ejemplo.

    public static void SetClient(Socket socket)
    {
        Id = 1;
        Socket = socket;
        Receive = new ReceivePacket(socket, Id);
        SendPacket = new SendPacket(socket);
    }

darse cuenta

La clase de recepción del servidor es la misma que la clase de recepción del cliente.

Conclusión

Ahora tienes un servidor y un cliente. Puedes trabajar este ejemplo básico. Por ejemplo, haga que el servidor también pueda recibir archivos u otros datos. O enviar un mensaje al cliente. En el servidor tienes una lista de clientes, de modo que cuando recibas algo con el cliente sabrás de dónde provienen.

Resultado final: introduzca la descripción de la imagen aquí



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow