異步Socket服務器與客戶端


本文靈感來自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,講的是異步的Socket通信。

關於技術博客,我覺得永遠是老外的比較好~Andre Azevedo的這篇文章里,給出了一個很復雜的例子,內容涉及如下

  • Socket連接(Socket Connection)
  • Socket服務(Socket Service)
  • 連接主機(Connection Host)
  • 加密與壓縮(Encrypt與Compress)
  • 請求入隊(Enqueuing Requests)
  • 確保發送和接收(Ensure send and recieve)
  • 檢查消息頭(Check message header)
  • 檢查空閑連接(Checking idle connections)
  • 加密服務
  • SSL認證(SSL authentication)
  • 對稱認證(Symmetric authentication)
  • 連接創建者(Connection Creator)
  • Socket服務器與Socket偵聽者(SocketServer and SocketListener)
  • Socket服務器構造函數與方法(SocketServer constructor and methods)
  • Socket客戶端與Socket連接者(SocketClient and SocketConnector)
  • Socket客戶端構造函數與方法(SocketClient constructor and methods)
  • 應答演示項目(Echo Demo Project)
  • 主機(Hosts)
  • 服務(Services)
  • 結語(Conclusion)
  • 版本歷史(History)

本文僅實現一個相對簡單的異步Socket服務器與客戶端通信示例。

首先需要說明如下2個問題

1.同步、異步、多線程是什么關系?答:同步是等待返回,相當於阻塞式;異步是不等待返回,是非阻塞式,可以用多線程實現。

2.有些異步方法有兩種實現方式, 如BeginAccept()和AcceptAsync(), 這兩個方法有什么區別呢?答:  以 Begin 和 End 開頭的方法是以 APM(Asynchronous Programming Model)設計方法實現的異步操作, 以 Async 結尾的方法是利用稱為 EAP (Event-based Asynchronous Pattern) 的設計方法實現的異步操作。

界面簡單如下:

主要代碼如下:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace Chatting
{
    public abstract class SocketFunc
    {
        //不管是服務端還是客戶端, 建立連接后用這個Socket進行通信
        public Socket communicateSocket = null;

        //服務端和客戶端建立連接的方式稍有不同, 子類會重載
        public abstract void Access(string IP, System.Action AccessAciton);

        //發送消息的函數
        public void Send(string message)
        {
            if (communicateSocket.Connected == false)
            {
                throw new Exception("還沒有建立連接, 不能發送消息");
            }
            Byte[] msg = Encoding.UTF8.GetBytes(message);
            communicateSocket.BeginSend(msg,0, msg.Length, SocketFlags.None,
                ar => {
                
                }, null);
        }

        //接受消息的函數
        public void Receive(System.Action<string> ReceiveAction)
        {
            //如果消息超過1024個字節, 收到的消息會分為(總字節長度/1024 +1)條顯示
            Byte[] msg = new byte[1024];
            //異步的接受消息
            communicateSocket.BeginReceive(msg, 0, msg.Length, SocketFlags.None,
                ar => {
                    //對方斷開連接時, 這里拋出Socket Exception
                    //An existing connection was forcibly closed by the remote host 
                        communicateSocket.EndReceive(ar); 
                    ReceiveAction(Encoding.UTF8.GetString(msg).Trim('\0',' '));
                    Receive(ReceiveAction);
                }, null);
        }
    }


    public class ServerSocket:SocketFunc
    {
        //服務端重載Access函數
        public override void Access(string IP, System.Action AccessAciton)
        {
            Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            //本機預使用的IP和端口
            IPEndPoint serverIP = new IPEndPoint(IPAddress.Any, 9050);
            //綁定服務端設置的IP
            serverSocket.Bind(serverIP);
            //設置監聽個數
            serverSocket.Listen(1);
            //異步接收連接請求
            serverSocket.BeginAccept(ar =>
                {
                    base.communicateSocket = serverSocket.EndAccept(ar);
                    AccessAciton();
                }, null);
        }
    }

    public class ClientSocket:SocketFunc
    {
        //客戶端重載Access函數
        public override void Access(string IP, System.Action AccessAciton)
        {
            base.communicateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            base.communicateSocket.Bind(new IPEndPoint(IPAddress.Any, 9051));
            
            //服務器的IP和端口
            IPEndPoint serverIP;
            try
            {
                serverIP = new IPEndPoint(IPAddress.Parse(IP), 9050);
            }
            catch
            {
                throw new Exception(String.Format("{0}不是一個有效的IP地址!", IP));
            }
            
            //客戶端只用來向指定的服務器發送信息,不需要綁定本機的IP和端口,不需要監聽
            try
            {
                base.communicateSocket.BeginConnect(serverIP, ar =>
                {
                    AccessAciton();
                }, null);
            }
            catch
            {
                throw new Exception(string.Format("嘗試連接{0}不成功!", IP));
            }
        }
    }
}

相關的事件處理程序,如下:

View Code
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Net.Sockets;

namespace Chatting
{
    public partial class MainForm : Form
    {

        public MainForm()
        {
            InitializeComponent();
        }

        SocketFunc socket;
        System.Action<string> ReceiveAction;
        System.Action AccessAction;

        private void MainForm_Load(object sender, EventArgs e)
        {
            //異步建立連接回調
            AccessAction = () =>
            {
                this.Invoke((MethodInvoker)delegate()
                {
                    lblFriendIP.Visible = false;
                    txtIP.Visible = false;
                    btnConnect.Visible = false;
                    btnWaitAccess.Visible = false;

                    String friendIP = socket.communicateSocket.RemoteEndPoint.ToString();
                    lblState.Text = String.Format("連接成功. 對方IP:{0}", friendIP);

                    try
                    {
                        socket.Receive(ReceiveAction);
                    }
                    catch (Exception exp)
                    {
                        MessageBox.Show(exp.Message, "錯誤");
                    }
                });

            };
            //異步接收消息回調
            ReceiveAction = msg =>
            {
                txtGetMsg.Invoke((MethodInvoker)delegate()
                {
                    UpdateGetMsgTextBox(false, msg, Color.Red);
                });
            };
        }

        private void btnWaitAccess_Click(object sender, EventArgs e)
        {
            this.socket = new ServerSocket();
            try
            {
                this.socket.Access("", this.AccessAction);
            }
            catch (Exception ecp)
            {
                MessageBox.Show(ecp.Message, "錯誤");
            }

            lblState.Text = "等待對方連接...";
        }

        private void btnConnect_Click(object sender, EventArgs e)
        {
            this.socket = new ClientSocket();
            try
            {
                this.socket.Access(txtIP.Text, this.AccessAction);
            }
            catch (Exception ecp)
            {
                MessageBox.Show(ecp.Message, "錯誤");
            }
            lblState.Text = "正在連接對方...";
        }

        private void btnSendMsg_Click(object sender, EventArgs e)
        {
            string message = txtSendMsg.Text.Trim();
            if (string.IsNullOrEmpty(message))
            {
                MessageBox.Show("消息內容不能為空!", "錯誤");
                txtSendMsg.Focus();
                return;
            }

            try
            {
                socket.Send(message);
            }
            catch(Exception ecp)
            {
                MessageBox.Show(ecp.Message, "錯誤");
                return;
            }

            UpdateGetMsgTextBox(true, message, Color.Blue);
            txtSendMsg.Text = "";
        }

        private void UpdateGetMsgTextBox(bool sendMsg, string message, Color color)
        {
            string appendText;
            if (sendMsg == true)
            {
                appendText = "Client:           " + System.DateTime.Now.ToString()
                    + Environment.NewLine
                    + message + Environment.NewLine;
            }
            else
            {
                appendText = "Server:           " + System.DateTime.Now.ToString()
                    + Environment.NewLine
                    + message + Environment.NewLine;
            }
            int txtGetMsgLength = txtGetMsg.Text.Length;
            txtGetMsg.AppendText(appendText);
            txtGetMsg.Select(txtGetMsgLength, appendText.Length - Environment.NewLine.Length*2 -message.Length);
            txtGetMsg.SelectionColor = color;

            txtGetMsg.ScrollToCaret();
        }

        private void txtSendMsg_Click(object sender, EventArgs e)
        {
            SetStyle(ControlStyles.SupportsTransparentBackColor, true);
            BackColor = Color.FromArgb(50, 40, 60, 82);
        }
    }
}

 

效果,如下:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM