1. 程式人生 > >Socket程式設計(非同步通訊)(Tcp,Udp)

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協議通訊下屬於無連線模式通訊,客戶端只管將訊息傳送出去,或者由於網路原因,而造成的丟包問題,下一章也將採用一定的方式解決。

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