非同期ソケット

非同期ソケットを使用することにより、サーバーは着信接続をリッスンし、その間に他のロジックを実行できます。同期ソケットとは対照的に、リッスンしているときにメインスレッドをブロックし、アプリケーションが応答しなくなり、クライアントが接続するまでフリーズします.

# 非同期ソケット (クライアント / サーバー) の例

サーバー側の例

サーバーのリスナーを作成

接続するクライアントと送信されるリクエストを処理するサーバーの作成から始めます。したがって、これを処理するリスナー クラスを作成してください。

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

最初に、あらゆる接続をリッスンできるリスナー ソケットを初期化する必要があります。 Tcp ソケットを使用するため、SocketType.Stream を使用します。また、サーバーがリッスンするスイッチ ポートを指定します

次に、着信接続のリッスンを開始します。

ここで使用するツリー メソッドは次のとおりです。

  • [ListenerSocket.Bind();](https://msdn.microsoft.com/en-us/library/system.net.sockets.socket.bind(v=vs.110).aspx)このメソッドはバインドします[IPEndPoint](https://msdn.microsoft.com/en-us/library/system.net.ipendpoint(v=vs.110).aspx) へのソケット。このクラスには、アプリケーションがホスト上のサービスに接続するために必要な、ホストおよびローカルまたはリモートのポート情報が含まれます。
  • [ListenerSocket.Listen(10);](https://msdn.microsoft.com/nl-nl/library/system.net.sockets.socket.listen(v=vs.110).aspx)バックログパラメータは、受け入れのためにキューに入れることができる着信接続の数を指定します。
  • [ListenerSocket.BeginAccept();](https://msdn.microsoft.com/en-us/library/5bb431f9(v=vs.110).aspx)サーバーは着信接続のリッスンを開始し、他のロジックで。接続があると、サーバーはこのメソッドに戻り、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);
            }
        }
    
    

    したがって、クライアントが接続すると、次の方法でクライアントを受け入れることができます:

    ここで使用する 3 つの方法:

  • [ListenerSocket.EndAccept()](https://msdn.microsoft.com/en-us/library/zdee4kd7(v=vs.110).aspx)`Listener.BeginAccept()`でコールバックを開始しましたコールバックを終了する必要があります。 `EndAccept()` メソッドは IAsyncResult パラメータを受け入れます。これは非同期メソッドの状態を保存します。この状態から、着信接続の発信元であるソケットを抽出できます。
  • `ClientController.AddClient()``EndAccept()` から取得したソケットを使用して、独自のメソッドでクライアントを作成します **(サーバーの例の下の ClientController のコード)**。
  • [ListenerSocket.BeginAccept()](https://msdn.microsoft.com/en-us/library/5bb431f9(v=vs.110).aspx)ソケットが終了したら、リッスンを再開する必要があります新しい接続を処理します。このコールバックをキャッチするメソッドを渡します。また、今後の接続でこのソケットを再利用できるように、Listener ソケットを int に渡します。
  • 
       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);
            }
        }
    
    

    これで Listening Socket ができましたが、次のコードが示すように、クライアントから送信されたデータをどのように受信するのでしょうか。

    クライアントごとにサーバー レシーバーを作成

    最初に、Socket をパラメーターとして受け取るコンストラクターを使用して受信クラスを作成します。

    
       public class ReceivePacket
        {
            private byte[] _buffer;
            private Socket _receiveSocket;
    
            public ReceivePacket(Socket receiveSocket)
            {
               _receiveSocket = receiveSocket;
            }
        }
    
    

    次のメソッドでは、最初にバッファーに 4 バイト (Int32) のサイズを指定するか、パッケージに含まれる部分 {長さ、実際のデータ} を指定します。したがって、最初の 4 バイトはデータの長さ用に予約し、残りは実際のデータ用です。

    次に、BeginReceive() メソッドを使用します。このメソッドは、接続されたクライアントからの受信を開始するために使用され、データを受信するときに 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()
        }
    
    

    これで、着信接続を受信して​​リッスンできるサーバーをセットアップしました。クライアントが接続すると、クライアントのリストに追加され、すべてのクライアントが独自の受信クラスを持ちます。サーバーにリッスンさせるには:

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

    この例で使用するいくつかのクラス

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

    クライアント側の例

    サーバーに接続しています

    まず、サーバーに接続するクラスを作成します。名前は次のとおりです:コネクタ:

    class Connector
    {
        private Socket _connectingSocket;
    }
    
    

    このクラスの次のメソッドは TryToConnect() です

    このメソッドにはいくつかの興味深い点があります:

  • ソケットを作成します。
  • 次に、ソケットが接続されるまでループします
  • ループごとにスレッドを 1 秒間保持するだけなので、サーバー XD に DOS を実行したくありません
  • [Connect()](https://msdn.microsoft.com/en-us/library/4xzx2d41(v=vs.110).aspx) を使用すると、サーバーへの接続が試行されます。失敗すると例外がスローされますが、プログラムはサーバーへの接続を維持します。これには [Connect CallBack](https://msdn.microsoft.com/en-us/library/ms145129(v=vs.110).aspx) メソッドを使用できますが、メソッドを呼び出すだけですソケットが接続されたとき。
  • クライアントがポート 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();
     }
    
    
    でローカル PC に接続しようとしていることに注意してください。
  • サーバーへのメッセージの送信

    これで、ほぼ完成した Socket アプリケーションが完成しました。 jet がない唯一のものは、サーバーにメッセージを送信するためのクラスです。

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

    最後に、接続用とメッセージ送信用の 2 つのボタンを作成します。

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

    この例で使用したクライアント クラス

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

    注意

    サーバーからの受信クラスは、クライアントからの受信クラスと同じです。

    結論

    これでサーバーとクライアントができました。この基本的な例を実行できます。たとえば、サーバーがファイルやその他の情報も受信できるようにします。または、クライアントにメッセージを送信します。サーバーでクライアントのリストを取得したので、何かを受け取ると、それがクライアントから来たことを知ることができます.

    最終結果:

    # コメント

    ソケットとネットワーク

    自分のネットワークの外部にあるサーバーにアクセスするにはどうすればよいですか?これはよくある質問であり、尋ねられると、ほとんどの場合トピックとしてフラグが立てられます。

    サーバー側

    サーバーのネットワーク上で、ルーターをサーバーにポート転送する必要があります。

    サーバーが実行されている PC の例:

    ローカル IP =192.168.1.115

    サーバーはポート 1234 をリッスンしています。

    Port 1234 で着信接続を転送する 192.168.1.115 へのルーター

    クライアント側

    変更する必要があるのは IP だけです。ループバック アドレスに接続するのではなく、サーバーが実行されているネットワークのパブリック IP に接続します。この IP はここで取得できます。

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

    このエンドポイントでリクエストを作成します:10.10.10.10:1234 ルーターのプロパティ ポート フォワードを行った場合、サーバーとクライアントは問題なく接続されます。

    ローカル IP に接続する場合は、ループバック アドレスを 192.168.1.178 に変更するだけで portforwart する必要はありません。 またはそのようなもの。

    データの送信:

    データはバイト配列で送信されます。データをバイト配列にパックし、反対側でアンパックする必要があります。

    ソケットに精通している場合は、送信する前にバイト配列を暗号化することもできます。これにより、誰かがあなたのパッケージを盗むのを防ぐことができます.