非同步套接字(Client Server)示例

伺服器端示例

為伺服器建立監聽器

開始建立將處理連線的客戶端和將要傳送的請求的伺服器。因此,建立一個將處理此問題的偵聽器類。

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

首先,我們需要初始化 Listener 套接字,我們可以在其中監聽任何連線。我們將使用 Tcp Socket,這就是我們使用 SocketType.Stream 的原因。我們還指定伺服器應該監聽的女巫埠

然後我們開始偵聽任何傳入的連線。

我們在這裡使用的樹方法是:

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

因此,當客戶端連線時,我們可以通過以下方法接受它們:

這裡使用的三種方法是:

  1. ListenerSocket.EndAccept()

    我們開始使用 Listener.BeginAccept() 結束回撥,現在我們必須結束回撥。The EndAccept() 方法接受一個 I​​AsyncResult 引數,這將儲存非同步方法的狀態。從這個狀態,我們可以提取傳入連線來自的套接字。

  2. ClientController.AddClient()

    使用我們從 EndAccept() 獲得的套接字,我們使用自己的方法建立一個客戶端 (在伺服器示例下面的程式碼 ClientController)

  3. ListenerSocket.BeginAccept()

    當套接字完成處理新連線時,我們需要再次開始監聽。傳遞將捕獲此回撥的方法。並且還傳遞偵聽器套接字 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)或包含給部分{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()
    }

所以我們設定了一個可以接收和偵聽傳入連線的伺服器。當客戶端連線時,它將被新增到客戶端列表中,並且每個客戶端都有自己的接收類。要讓伺服器監聽:

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. 建立套接字;

  2. 接下來我迴圈直到套接字連線

  3. 每個迴圈只是持有執行緒 1 秒我們不想 DOS 伺服器 XD

  4. 使用 Connect() ,它將嘗試連線到伺服器。如果失敗,它將丟擲異常,但是 wile 將使程式保持連線到伺服器。你可以使用 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();
     }
    

將訊息傳送到伺服器

所以現在我們有一個幾乎完成或 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();
        }
    }

Finaly crate 兩個按鈕一個用於連線,另一個用於傳送訊息:

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

注意

伺服器的 Receive Class 與客戶端的 receive 類相同。

結論

你現在有一個伺服器和一個客戶端。你可以用這個基本的例子。例如,使伺服器也可以接收檔案或其他鈴聲。或者向客戶傳送訊息。在伺服器中,你獲得了一個客戶列表,所以當你收到來自客戶端的內容時,它會來自。

最後結果: StackOverflow 文件