Ricerca…


introduzione

Utilizzando socket asincroni, un server può ascoltare le connessioni in entrata e fare qualche altra logica nel frattempo in contrasto con il socket sincrono quando sono in ascolto bloccano il thread principale e l'applicazione sta diventando non rispondente e si bloccherà fino a quando un client non si connetterà.

Osservazioni

Presa e rete

Come accedere a un server al di fuori della mia rete? Questa è una domanda comune e quando viene posta la domanda è per lo più segnalata come argomento.

Lato server

Sulla rete del tuo server devi portare il tuo router in avanti al tuo server.

Per esempio PC su cui è in esecuzione il server:

IP locale = 192.168.1.115

Il server sta ascoltando la porta 1234.

Inoltra connessioni in entrata sul router Port 1234 a 192.168.1.115

Dalla parte del cliente

L'unica cosa che devi cambiare è l'IP. Non vuoi collegarti al tuo indirizzo di loopback ma all'IP pubblico dalla rete su cui è in esecuzione il tuo server. Questo IP puoi arrivare qui .

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

Quindi ora crei una richiesta su questo endpoint: 10.10.10.10:1234 se hai fatto la proprietà port forwarding del router il tuo server e client si connetteranno senza alcun problema.

Se vuoi connetterti ad un IP locale non dovrai portforwart cambiare semplicemente l'indirizzo di loopback a 192.168.1.178 o qualcosa del genere.

Invio di dati:

I dati vengono inviati nell'array di byte. Devi mettere i tuoi dati in un array di byte e decomprimerlo dall'altro lato.

Se hai familiarità con il socket puoi anche provare a crittografare il tuo array di byte prima di inviarlo. Ciò impedirà a chiunque di rubare il pacchetto.

Esempio di socket asincrono (client / server).

Esempio lato server

Crea listener per server

Inizia con la creazione di un server che gestirà i client che si connettono e le richieste che verranno inviate. Quindi crea una classe listener che gestirà questo.

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);
    }
 }

Per prima cosa dobbiamo inizializzare il socket Listener dove possiamo ascoltare tutte le connessioni. Useremo un socket Tcp per questo usiamo SocketType.Stream. Inoltre, specifichiamo la porta strega che il server dovrebbe ascoltare

Quindi iniziamo ad ascoltare le connessioni in arrivo.

I metodi dell'albero che usiamo qui sono:

  1. ListenerSocket.Bind ();

    Questo metodo associa il socket a un IPEndPoint . Questa classe contiene le informazioni sull'host e sulla porta locale o remota necessarie a un'applicazione per connettersi a un servizio su un host.

  2. ListenerSocket.Listen (10);

    Il parametro backlog specifica il numero di connessioni in ingresso che possono essere accodate per l'accettazione.

  3. ListenerSocket.BeginAccept ();

    Il server inizierà ad ascoltare le connessioni in entrata e continuerà con altra logica. Quando c'è una connessione il server torna a questo metodo ed eseguirà il metodo 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);
        }
    }

Quindi, quando un client si connette, possiamo accettarli con questo metodo:

I tre metodi che utilizziamo qui sono:

  1. ListenerSocket.EndAccept ()

    Abbiamo iniziato la funzione di callback con Listener.BeginAccept() ora dobbiamo terminare la richiamata. The EndAccept() metodo The EndAccept() accetta un parametro IAsyncResult, questo memorizzerà lo stato del metodo asincrono, Da questo stato possiamo estrarre il socket da cui proveniva la connessione in ingresso.

  2. ClientController.AddClient()

    Con il socket ottenuto da EndAccept() creiamo un Client con un metodo personalizzato (codice ClientController sotto l'esempio del server) .

  3. ListenerSocket.BeginAccept ()

    Abbiamo bisogno di ricominciare ad ascoltare quando il socket ha finito di gestire la nuova connessione. Passa nel metodo che catturerà questa richiamata. E anche passare il socket Listener in modo da poter riutilizzare questo socket per le prossime connessioni.

    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);
        }
    }

Ora abbiamo un socket di ascolto ma come riceviamo i dati inviati dal client che è ciò che mostra il prossimo codice.

Crea ricevitore del server per ogni cliente

Prima di creare una classe di ricezione con un costruttore che accetta un Socket come parametro:

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

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

Nel prossimo metodo iniziamo con il dare al buffer una dimensione di 4 byte (Int32) o il pacchetto contiene parti {lunghezza, dati reali}. Quindi i primi 4 byte riserviamo per la lunghezza dei dati il ​​resto per i dati effettivi.

Successivamente usiamo il metodo BeginReceive () . Questo metodo viene utilizzato per iniziare a ricevere dai client connessi e quando riceverà i dati eseguirà la funzione 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()
    }

Quindi abbiamo configurato un server in grado di ricevere e ascoltare le connessioni in entrata. Quando un client lo connette, verrà aggiunto a un elenco di client e ogni client avrà la sua classe di ricezione. Per fare in modo che il server ascolti:

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

Alcune classi che uso in questo esempio

    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));
          }
      }

Esempio lato client

Connessione al server

Prima di tutto vogliamo creare una classe che collega al server il nome che gli diamo è: Connettore:

class Connector
{
    private Socket _connectingSocket;
}

Il prossimo metodo per questa classe è TryToConnect ()

Questo metodo fa alcune cose interminabili:

  1. Crea il socket;

  2. Prossimo ciclo I fino al collegamento della presa

  3. Ogni ciclo è solo tenendo premuto il thread per 1 secondo, non vogliamo DOS sul server XD

  4. Con Connect () proverà a connettersi al server. Se fallisce, genererà un'eccezione ma il wile manterrà il programma connesso al server. È possibile utilizzare un metodo Connect CallBack per questo, ma andrò a chiamare un metodo quando lo Socket è connesso.

  5. Si noti che il client sta tentando di connettersi al PC locale sulla porta 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();
     }
    

Invio di un messaggio al server

Quindi ora abbiamo quasi una finitura o l'applicazione Socket. L'unica cosa che non abbiamo jet è una classe per inviare un messaggio al server.

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();
        }
    }

Finaly cassa due pulsanti uno per la connessione e l'altro per l'invio di un messaggio:

    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 classe client che ho usato in questo esempio

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

Avviso

La classe di ricezione dal server è la stessa della classe di ricezione del client.

Conclusione

Ora hai un server e un client. Puoi lavorare su questo esempio di base. Ad esempio, assicurati che il server possa anche ricevere file o altri elementi. O inviare un messaggio al cliente. Nel server hai una lista di clienti così quando ricevi qualcosa che saprai dal cliente da cui proviene.

Risultato finale: inserisci la descrizione dell'immagine qui



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow