Szukaj…


Wprowadzenie

Korzystając z gniazd asynchronicznych, serwer może nasłuchiwać połączeń przychodzących i wykonywać inne operacje logiczne w przeciwieństwie do gniazd synchronicznych, gdy nasłuchują, blokują główny wątek, a aplikacja przestaje reagować i zawiesza się do momentu połączenia klienta.

Uwagi

Gniazdo i sieć

Jak uzyskać dostęp do serwera poza moją siecią? Jest to częste pytanie i kiedy jest zadawane, jest w większości oznaczane jako temat.

Po stronie serwera

W sieci serwera musisz przekierować router na serwer.

Na przykład komputer, na którym działa serwer:

lokalny adres IP = 192.168.1.115

Serwer nasłuchuje na porcie 1234.

Przekieruj połączenia przychodzące na routerze Port 1234 na 192.168.1.115

Strona klienta

Jedyne, co musisz zmienić, to adres IP. Nie chcesz łączyć się z adresem zwrotnym, ale z publicznym adresem IP z sieci, w której działa serwer. Ten adres IP można uzyskać tutaj .

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

Teraz tworzysz żądanie w tym punkcie końcowym: 10.10.10.10:1234 jeśli wykonałeś właściwość port przekieruj router, serwer i klient połączą się bez problemu.

Jeśli chcesz połączyć się z lokalnym adresem IP, nie będziesz musiał portforwart tylko zmienić adres sprzężenia zwrotnego na 192.168.1.178 lub coś w tym rodzaju.

Wysyłanie danych:

Dane są wysyłane w tablicy bajtów. Musisz spakować dane do tablicy bajtów i rozpakować je po drugiej stronie.

Jeśli znasz gniazdo, możesz spróbować zaszyfrować tablicę bajtów przed wysłaniem. Zapobiegnie to kradzieży paczki.

Przykład gniazda asynchronicznego (klient / serwer).

Przykład po stronie serwera

Utwórz Listener dla serwera

Rozpocznij od utworzenia serwera, który będzie obsługiwał klientów, którzy się łączą, i żądań, które zostaną wysłane. Utwórz więc klasę nasłuchiwania, która to obsłuży.

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

Najpierw musimy zainicjować gniazdo Listener, w którym możemy nasłuchiwać wszelkich połączeń. Użyjemy gniazda Tcp, dlatego używamy SocketType.Stream. Podajemy również port, którego serwer powinien nasłuchiwać

Następnie zaczynamy nasłuchiwać połączeń przychodzących.

Stosowane tutaj metody drzewa to:

  1. ListenerSocket.Bind ();

    Ta metoda wiąże gniazdo z IPEndPoint . Ta klasa zawiera informacje o hoście oraz porcie lokalnym lub zdalnym potrzebnym aplikacji do połączenia się z usługą na hoście.

  2. ListenerSocket.Listen (10);

    Parametr zaległości określa liczbę połączeń przychodzących, które mogą być ustawione w kolejce do akceptacji.

  3. ListenerSocket.BeginAccept ();

    Serwer zacznie nasłuchiwać połączeń przychodzących i przejdzie do innej logiki. W przypadku połączenia serwer przełącza się z powrotem na tę metodę i uruchomi metodę 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);
        }
    }

Kiedy klient się łączy, możemy je zaakceptować za pomocą tej metody:

Stosujemy tutaj trzy metody:

  1. ListenerSocket.EndAccept ()

    Wywołanie zwrotne rozpoczęliśmy od Listener.BeginAccept() teraz musimy zakończyć to wywołanie zwrotne. The EndAccept() akceptuje parametr IAsyncResult, spowoduje to zapisanie stanu metody asynchronicznej. Z tego stanu możemy wyodrębnić gniazdo, z którego przychodziło połączenie przychodzące.

  2. ClientController.AddClient()

    Za pomocą gniazda, które otrzymaliśmy z EndAccept() , tworzymy klienta za pomocą własnej metody (kod ClientController poniżej przykład serwera) .

  3. ListenerSocket.BeginAccept ()

    Musimy zacząć nasłuchiwać ponownie, gdy gniazdo zakończy obsługę nowego połączenia. Podaj metodę, która złapie to wywołanie zwrotne. Przekaż także gniazdo nasłuchiwania, abyśmy mogli ponownie użyć tego gniazda dla nadchodzących połączeń.

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

Teraz mamy gniazdo nasłuchiwania, ale w jaki sposób otrzymujemy dane wysyłane przez klienta, co pokazuje następny kod.

Utwórz Serwer Odbiornika dla każdego klienta

Najpierw utwórz klasę odbiorczą z konstruktorem, który przyjmuje parametr Socket jako parametr:

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

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

W kolejnej metodzie zaczynamy od nadania buforowi rozmiaru 4 bajtów (Int32) lub zawartości pakietu dla części {długość, rzeczywiste dane}. Tak więc pierwsze 4 bajty rezerwujemy na długość danych, a resztę na rzeczywiste dane.

Następnie używamy metody BeginReceive () . Ta metoda służy do rozpoczynania odbierania od połączonych klientów, a kiedy otrzyma dane, uruchomi funkcję 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()
    }

Dlatego skonfigurowaliśmy serwer, który może odbierać i nasłuchiwać połączeń przychodzących. Kiedy klienci się połączą, zostanie dodany do listy klientów, a każdy klient ma swoją własną klasę odbioru. Aby serwer nasłuchiwał:

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

Niektóre klasy używam w tym przykładzie

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

Przykład po stronie klienta

Łączenie z serwerem

Przede wszystkim chcemy stworzyć klasę, która łączy się z nazwą serwera, którą podajemy: Łącznik:

class Connector
{
    private Socket _connectingSocket;
}

Następną metodą dla tej klasy jest TryToConnect ()

Ta metoda ma kilka interesujących rzeczy:

  1. Utwórz gniazdo;

  2. Następnie wykonuję pętlę, aż gniazdo zostanie podłączone

  3. W każdej pętli utrzymuje wątek przez 1 sekundę, nie chcemy DOS serwera XD

  4. Za pomocą Connect () spróbuje połączyć się z serwerem. Jeśli się nie powiedzie, wyrzuci wyjątek, ale wile utrzyma program łączący się z serwerem. Możesz użyć do tego metody Connect CallBack , ale po prostu wybiorę metodę, gdy Socket jest podłączony.

  5. Zwróć uwagę, że klient próbuje teraz połączyć się z lokalnym komputerem na porcie 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();
     }
    

Wysyłanie wiadomości na serwer

Więc teraz mamy prawie ukończoną aplikację Socket. Jedyne, czego nie mamy odrzutowiec, to klasa do wysyłania wiadomości na serwer.

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

Wreszcie skrzynka zawiera dwa przyciski, jeden do połączenia, a drugi do wysłania wiadomości:

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

Klasa klienta, której użyłem w tym przykładzie

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

Ogłoszenie

Klasa odbioru z serwera jest taka sama, jak klasa odbioru od klienta.

Wniosek

Masz teraz serwer i klienta. Możesz wypracować ten podstawowy przykład. Na przykład upewnij się, że serwer może również odbierać pliki lub inne pliki. Lub wyślij wiadomość do klienta. Na serwerze masz listę klientów, więc kiedy otrzymasz coś, co będziesz wiedział od klienta, pochodzi.

Ostateczny wynik: wprowadź opis zdjęcia tutaj



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow