サーチ…


前書き

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

備考

ソケットとネットワーク

私自身のネットワークの外にあるサーバーにアクセスするには?これは一般的な質問であり、質問されたときに主にトピックとしてフラグが立てられます。

サーバ側

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

サーバーが実行されている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:123410.10.10.10:1234プロパティポートをルータに転送した場合、サーバとクライアントは問題なく接続されます。

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

データの送信:

データはバイト配列で送信されます。データをバイト配列にパックし、もう片方に展開する必要があります。

ソケットに慣れている場合は、送信する前にバイト配列を暗号化することもできます。これは、誰もあなたのパッケージを盗むのを防ぎます。

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

サーバーサイドの例

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

接続するクライアントを処理するサーバーを作成し、送信する要求を開始します。したがって、これを処理するListenerクラスを作成します。

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

まず、リスナーソケットを初期化して、接続を待機できるようにする必要があります。私たちはSocketType.Streamを使う理由であるTcpソケットを使うつもりです。また、我々はサーバーが聴くべき魔法のポートを指定します

次に、着信接続を待機します。

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

  1. ListenerSocket.Bind();

    このメソッドは、ソケットをIPEndPointにバインドします。このクラスには、アプリケーションがホスト上のサービスに接続するために必要なホストおよびローカルまたはリモートのポート情報が含まれます。

  2. ListenerSocket.Listen(10);

    backlogパラメータは、受け入れのためにキューに入れることができる着信接続の数を指定します。

  3. ListenerSocket.BeginAccept();

    サーバーは着信接続のリスンを開始し、他のロジックに進みます。接続があると、サーバーはこのメソッドに戻り、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つの方法を使用しています:

  1. ListenerSocket.EndAccept()

    Listener.BeginAccept()コールバックを開始しました。コールバックを終了する必要があります。 The EndAccept()メソッドはIAsyncResultパラメータを受け取ります。これは非同期メソッドの状態を格納します。この状態から、着信接続が発生したソケットを抽出できます。

  2. ClientController.AddClient()

    EndAccept()から取得したソケットを使用して、独自のメソッド(サーバーの例の下のClientControllerコード EndAccept()を持つクライアントを作成します

  3. ListenerSocket.BeginAccept()

    新しい接続の処理でソケットが終了したら、再びリスニングを開始する必要があります。このコールバックをキャッチするメソッドを渡します。また、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);
        }
    }

今度はListen Socketがありますが、クライアントが次のコードが表示しているデータをどのように受け取るのでしょうか。

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

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

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

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

次の方法では、最初にバッファに4バイト(Int32)のサイズを与えるか、またはパッケージに部分に{Lenght、実際のデータ}を含めることから始めます。最初の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()
    }

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

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

クライアントサイドの例

サーバーにつなげる

まず最初に、サーバーに接続するクラスを作成したいとします。名前を付けます:Connector:

class Connector
{
    private Socket _connectingSocket;
}

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

この方法は、いくつかの興味深いものがあります:

  1. ソケットを作成します。

  2. 次に、ソケットが接続されるまでループします

  3. すべてのループそれはちょうど私たちがDOSにサーバーXDしたくないスレッドを1秒間保持している

  4. Connect()を使用すると、サーバーに接続しようとします。失敗した場合は例外がスローされますが、プログラムはサーバーに接続したままになります。このためにConnect CallBackメソッドを使用することはできますが、Socketが接続されているときにメソッドを呼び出すために移動します。

  5. クライアントがポート1234のローカルPCに接続しようとしていることに注目してください。

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

サーバーへのメッセージの送信

これでほぼ完成またはソケットアプリケーションが完成しました。私たちがジェット機を持っていないのは、サーバにメッセージを送信するためのクラスだけです。

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

通知

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

結論

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

最終結果: ここに画像の説明を入力



Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow