Socket編程 (異步通訊) (Tcp,Udp) - Part2


  

Socket編程(異步通訊)(Tcp,Udp)

  上一章主要展示了Socket的Tcp\Udp兩種協議下的基本通訊方式,屬於同步通訊。至於一個服務器對應多個客戶端,或者對應多個請求,我們采用的是多線程的方式來解決此問題。然而本章節我們將有更好的方式去實現它:Socket在Tcp\Udp兩種協議下的異步通訊方式。

基於Tcp協議異步:

  BeginAccept方法和EndAccept方法

  包含在System.Net.Sockets命名空間下。異步Tcp使用BeginAccept方法開始接受新的客戶端連接請求,該方法中系統自動利用線程池創建需要的線程,並在操作完成時利用異步回調機制調用提供給它的方法,同時返回相應的狀態參數,然后方可利用EndAccept方法結束該連接請求.

  BeginRecive方法和EndRecive方法

  異步Tcp使用BeginRecive方法和開始接受客戶端發送的的消息,該方法如上同理,接受完畢后調用回調函數傳遞相應的狀態參數。利用EndRecive方法接受接受消息。

  至於BeginSend方法和EndSend方法、BeginConnect方法和EndConnect方法與上類似。

 

下面我們來看看如何在Tcp協議下進行客戶端與服務器端之間的通訊:

服務器端:  

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空間
using System.Net;
using System.Net.Sockets;
using System.Threading;
#endregion

namespace AsynServerConsole
{
    /// <summary>
    /// Tcp協議異步通訊類(服務器端)
    /// </summary>
    public class AsynTcpServer
    {
        #region Tcp協議異步監聽
        /// <summary>
        /// Tcp協議異步監聽
        /// </summary>
        public void StartListening()
        {
            //主機IP
            IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686);
            Socket tcpServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            tcpServer.Bind(serverIp);
            tcpServer.Listen(100);
            Console.WriteLine("異步開啟監聽...");
            AsynAccept(tcpServer);
        }
        #endregion

        #region 異步連接客戶端
        /// <summary>
        /// 異步連接客戶端
        /// </summary>
        /// <param name="tcpServer"></param>
        public void AsynAccept(Socket tcpServer)
        {
            tcpServer.BeginAccept(asyncResult =>
            {
                Socket tcpClient = tcpServer.EndAccept(asyncResult);
                Console.WriteLine("server<--<--{0}", tcpClient.RemoteEndPoint.ToString());
                AsynSend(tcpClient, "收到連接...");//發送消息
                AsynAccept(tcpServer);
                AsynRecive(tcpClient);
            }, null);
        }
        #endregion

        #region 異步接受客戶端消息
        /// <summary>
        /// 異步接受客戶端消息
        /// </summary>
        /// <param name="tcpClient"></param>
        public void AsynRecive(Socket tcpClient)
        {
            byte[] data = new byte[1024];
            try
            {
                tcpClient.BeginReceive(data, 0, data.Length, SocketFlags.None,
                asyncResult =>
                {
                    int length = tcpClient.EndReceive(asyncResult);
                    Console.WriteLine("server<--<--client:{0}", Encoding.UTF8.GetString(data));
                    AsynSend(tcpClient, "收到消息...");
                    AsynRecive(tcpClient);
                }, null);
            }
            catch (Exception ex)
            {
                Console.WriteLine("異常信息:", ex.Message);
            }
        }
        #endregion

        #region 異步發送消息
        /// <summary>
        /// 異步發送消息
        /// </summary>
        /// <param name="tcpClient">客戶端套接字</param>
        /// <param name="message">發送消息</param>
        public void AsynSend(Socket tcpClient, string message)
        {
            byte[] data = Encoding.UTF8.GetBytes(message);
            try
            {
                tcpClient.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
                {
                    //完成發送消息
                    int length = tcpClient.EndSend(asyncResult);
                    Console.WriteLine("server-->-->client:{0}", message);
                }, null);
            }
            catch (Exception ex)
            {
                Console.WriteLine("異常信息:{0}", ex.Message);
            }
        }
        #endregion
    }
}

 

客戶端:

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空間
using System.Net;
using System.Net.Sockets;
using System.Threading;
#endregion

namespace AsynClientConsole
{
    /// <summary>
    /// Tcp協議異步通訊類(客戶端)
    /// </summary>
    public class AsynTcpClient
    {
        #region 異步連接
        /// <summary>
        /// Tcp協議異步連接服務器
        /// </summary>
        public void AsynConnect()
        {
            //主機IP
            IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686);
            Socket tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            tcpClient.BeginConnect(serverIp, asyncResult =>
            {
                tcpClient.EndConnect(asyncResult);
                Console.WriteLine("client-->-->{0}", serverIp.ToString());
                AsynSend(tcpClient, "我上線了...");
                AsynSend(tcpClient, "第一次發送消息...");
                AsynSend(tcpClient, "第二次發送消息...");
                AsynRecive(tcpClient);
            }, null);
        }
        #endregion

        #region 異步接受消息
        /// <summary>
        /// 異步連接客戶端回調函數
        /// </summary>
        /// <param name="tcpClient"></param>
        public void AsynRecive(Socket tcpClient)
        {
            byte[] data = new byte[1024];
            tcpClient.BeginReceive(data, 0, data.Length, SocketFlags.None, asyncResult =>
            {
                int length = tcpClient.EndReceive(asyncResult);
                Console.WriteLine("client<--<--server:{0}", Encoding.UTF8.GetString(data));
                AsynRecive(tcpClient);
            }, null);
        }
        #endregion

        #region 異步發送消息
        /// <summary>
        /// 異步發送消息
        /// </summary>
        /// <param name="tcpClient">客戶端套接字</param>
        /// <param name="message">發送消息</param>
        public void AsynSend(Socket tcpClient, string message)
        {
            byte[] data = Encoding.UTF8.GetBytes(message);
            tcpClient.BeginSend(data, 0, data.Length, SocketFlags.None, asyncResult =>
            {
                //完成發送消息
                int length = tcpClient.EndSend(asyncResult);
                Console.WriteLine("client-->-->server:{0}", message);
            }, null);
        }
        #endregion
    }
}

通訊效果如下圖:

服務器:

客戶端:

上面我們完成了基於Tcp協議下的Socket通訊,那么Udp協議下的通訊我們將以什么樣的形式來通訊呢?畢竟Udp協議下是無連接模式。

基於Udp協議的異步通訊:

  其實與Tcp協議具有的方法類似,但由於Udp協議是無連接模式,我們所用到方法就無BeginConnect和EndConnect方法。我們所要做的就是收發消息的處理。

  在Udp協議的異步通訊中,我們需要注意一下幾個編程點:

  1.在EndRecive方法中,由於無狀態返回模式,不能返回發送端的Remote,所以我們需要在該方法中獲取活動端的Remote,然后利用EndRecive方法結束接受該消息接受。

  2.客戶端由於無需Connect到服務器端,但是需要先向服務器端發送一個請求如Send一些消息。讓服務器端確定自己Remote,然后可利用Recive方法接收其他終端發送過來的消息。

下面將演示Udp協議下異步通訊:

服務器端:

 

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空間
using System.Net;
using System.Net.Sockets;
using System.Threading;
#endregion

namespace AsynServerConsole
{
    /// <summary>
    /// Udp協議異步通訊類(服務器端)
    /// </summary>
    public class AsynUdpServer
    {
        #region 容器對象
        /// <summary>
        /// 容器對象
        /// </summary>
        public class StateObject
        {
            //服務器端
            public Socket udpServer = null;
            //接受數據緩沖區
            public byte[] buffer = new byte[1024];
            //遠程終端
            public EndPoint remoteEP;
        }

        public StateObject state;
        #endregion

        #region 服務器綁定終端節點
        public void ServerBind()
        {
            //主機IP
            IPEndPoint serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686);
            Socket udpServer = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            udpServer.Bind(serverIp);
            Console.WriteLine("server ready...");
            IPEndPoint clientIp = new IPEndPoint(IPAddress.Any, 0);
            state = new StateObject();
            state.udpServer = udpServer;
            state.remoteEP = (EndPoint)clientIp;
            AsynRecive();
        }
        #endregion

        #region 異步接受消息
        public void AsynRecive()
        {
            state.udpServer.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref state.remoteEP,
                new AsyncCallback(ReciveCallback), null);
        }
        #endregion

        #region 異步接受消息回調函數
        public void ReciveCallback(IAsyncResult asyncResult)
        {
            if (asyncResult.IsCompleted)
            {
                //獲取發送端的終節點
                IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 0);
                EndPoint remoteEP = (EndPoint)ipep;
                state.udpServer.EndReceiveFrom(asyncResult, ref remoteEP);
                Console.WriteLine("server<--<--client:{0}", Encoding.UTF8.GetString(state.buffer));
                //向發送端通知:收到消息
                state.remoteEP = remoteEP;
                AsynSend("收到消息");
                //繼續接受消息
                AsynRecive();
            }
        }
        #endregion

        #region 異步發送消息
        public void AsynSend(string message)
        {
            Console.WriteLine("server-->-->client:{0}", message);
            byte[] buffer = Encoding.UTF8.GetBytes(message);
            state.udpServer.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, state.remoteEP,
                new AsyncCallback(SendCallback), null);
        }
        #endregion

        #region 異步發送消息回調函數
        public void SendCallback(IAsyncResult asyncResult)
        {
            //消息發送完畢
            if (asyncResult.IsCompleted)
            {
                state.udpServer.EndSendTo(asyncResult);
            }
        }
        #endregion
    }
}

 

客戶端:

 

 

using System;
using System.Collections.Generic;
using System.Text;
#region 命名空間
using System.Net;
using System.Net.Sockets;
using System.Threading;
#endregion

namespace AsynClientConsole
{
    /// <summary>
    /// Udp協議異步通訊類(客戶端)
    /// </summary>
    public class AsynUdpClient
    {
        #region 容器對象
        /// <summary>
        /// 容器對象
        /// </summary>
        public class StateObject
        {
            //客戶端套接字
            public Socket udpClient = null;
            //接收信息緩沖區
            public byte[] buffer = new byte[1024];
            //服務器端終節點
            public IPEndPoint serverIp;
            //遠程終端節點
            public EndPoint remoteEP;
        }

        public StateObject state;
        #endregion

        #region 客戶端初始化
        public void InitClient()
        {
            state = new StateObject();
            state.udpClient = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
            state.serverIp = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8686);
            state.remoteEP = (EndPoint)(new IPEndPoint(IPAddress.Any, 0));
            //此處注意:
            //  由於當前是客戶端,所以沒有綁定終節點
            //  不可直接接收消息,必須先向其他終端發送信息告知本機終節點
            AsynSend("第1次發送消息");
            AsynSend("第2次發送消息");
            AsynRecive();
        }
        #endregion

        #region 異步接收來自其他終端發送的消息
        public void AsynRecive()
        {
            state.udpClient.BeginReceiveFrom(state.buffer, 0, state.buffer.Length, SocketFlags.None, ref state.remoteEP,
                new AsyncCallback(ReciveCallback), null);
        }
        #endregion

        #region 異步接收來自其他終端發送的消息回調函數
        public void ReciveCallback(IAsyncResult asyncResult)
        {
            //信息接收完成
            if (asyncResult.IsCompleted)
            {
                state.udpClient.EndReceiveFrom(asyncResult, ref state.remoteEP);
                Console.WriteLine("client<--<--{0}:{1}", state.remoteEP.ToString(), Encoding.UTF8.GetString(state.buffer));
                AsynRecive();
            }
        }
        #endregion

        #region 異步發送消息
        public void AsynSend(string message)
        {
            Console.WriteLine("client-->-->{0}:{1}", state.serverIp.ToString(), message);
            byte[] buffer = Encoding.UTF8.GetBytes(message);
            state.udpClient.BeginSendTo(buffer, 0, buffer.Length, SocketFlags.None, state.serverIp,
                new AsyncCallback(SendCallback), null);
        }
        #endregion

        #region 異步發送消息回調函數
        public void SendCallback(IAsyncResult asyncResult)
        {
            //消息發送完成
            if (asyncResult.IsCompleted)
            {
                state.udpClient.EndSendTo(asyncResult);
            }
        }
        #endregion
    }
}

 

通訊效果如下圖:

 

服務器:

 

客戶端:

 

總結:基於異步模式的通訊無須采用多線程來服務多個客戶端以及多個請求,這樣的通訊模式效率更高。

  同步上面Tcp效果展示圖,我們發現客戶端分幾次連續發送的消息被服務器端一次接收了,讀成了一條數據,而這就是Socket通訊基於Tcp協議下發生的粘包問題,下面一種我們將着重對Tcp協議的通訊信息封包,拆包以解決上面問題。

  同樣Udp協議通訊下屬於無連接模式通訊,客戶端只管將消息發送出去,或者由於網絡原因,而造成的丟包問題,下一章也將采用一定的方式解決。

最后附上源碼:Socket-Part2.zip

作者:曾慶雷
出處:http://www.cnblogs.com/zengqinglei
本頁版權歸作者和博客園所有,歡迎轉載,但未經作者同意必須保留此段聲明, 且在文章頁面明顯位置給出原文鏈接,否則保留追究法律責任的權利


免責聲明!

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



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