[C#]手把手教你打造Socket的TCP通讯连接(一)
本文章將講解基于TCP連接的Socket通訊,使用Socket異步功能,并且無粘包現象,通過事件驅動使用。
在編寫Socket代碼之前,我們得要定義一下Socket的基本功能。
作為一個TCP連接,不論是客戶端還是服務器端,它都得有以下接口:
public interface ISocket {/// <summary>/// 獲取是否已連接。/// </summary>bool IsConnected { get; }/// <summary>/// 發送數據。/// </summary>/// <param name="data">要發送的數據。</param>void Send(byte[] data);/// <summary>/// 異步發送數據。/// </summary>/// <param name="data">要發送的數據。</param>void SendAsync(byte[] data);/// <summary>/// 斷開連接。/// </summary>void Disconnect();/// <summary>/// 異步斷開連接。/// </summary>void DisconnectAsync(); /// <summary>/// 斷開完成時引發事件。/// </summary>event EventHandler<SocketEventArgs> DisconnectCompleted;/// <summary>/// 接收完成時引發事件。/// </summary>event EventHandler<SocketEventArgs> ReceiveCompleted;/// <summary>/// 發送完成時引發事件。/// </summary>event EventHandler<SocketEventArgs> SendCompleted; }用到的事件參數SocketEventArgs。
/// <summary> /// Socket事件參數 /// </summary> public class SocketEventArgs : EventArgs {/// <summary>/// 實例化Socket事件參數/// </summary>/// <param name="socket">相關Socket</param>/// <param name="operation">操作類型</param>public SocketEventArgs(ISocket socket, SocketAsyncOperation operation){if (socket == null)throw new ArgumentNullException("socket");Socket = socket;Operation = operation;}/// <summary>/// 獲取或設置事件相關數據。/// </summary>public byte[] Data { get; set; }/// <summary>/// 獲取數據長度。/// </summary>public int DataLength { get { return Data == null ? 0 : Data.Length; } }/// <summary>/// 獲取事件相關Socket/// </summary>public ISocket Socket { get; private set; }/// <summary>/// 獲取事件操作類型。/// </summary>public SocketAsyncOperation Operation { get; private set; } }因為作為客戶端只管收發,比較簡單,所以這里從客戶端開始做起。
定義類TCPClient繼承接口ISocket和IDisposable
/// <summary> /// TCP客戶端 /// </summary> public class TCPClient : ISocket, IDisposable {/// <summary>/// 獲取是否已連接。/// </summary>public bool IsConnected { get; }/// <summary>/// 發送數據。/// </summary>/// <param name="data">要發送的數據。</param>public void Send(byte[] data){}/// <summary>/// 異步發送數據。/// </summary>/// <param name="data">要發送的數據。</param>public void SendAsync(byte[] data){}/// <summary>/// 斷開連接。/// </summary>public void Disconnect(){}/// <summary>/// 異步斷開連接。/// </summary>public void DisconnectAsync(){} /// <summary>/// 斷開完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> DisconnectCompleted;/// <summary>/// 接收完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> ReceiveCompleted;/// <summary>/// 發送完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> SendCompleted;/// <summary>/// 釋放資源。/// </summary>public void Dispose(){} }并在此之上,增加以下方法
/// <summary>/// 連接至服務器。/// </summary>/// <param name="endpoint">服務器終結點。</param>public void Connect(IPEndPoint endpoint){}/// <summary>/// 異步連接至服務器。/// </summary>/// <param name="endpoint"></param>public void ConnectAsync(IPEndPoint endpoint){}下面我們開始編寫構造函數,實例化一個Socket并保存到私有變量里。
把IsConnected指向Socket.Connected。
private Socket Socket; private Stream Stream; /// <summary>/// 實例化TCP客戶端。/// </summary>public TCPClient(){Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);}/// <summary>/// 獲取是否已連接。/// </summary>public bool IsConnected { get { return Socket.Connected; } }因為接下來我們開始編寫Socket的異步功能,所以在此之前,我們要做一個狀態類,用來保存異步狀態。
internal class SocketAsyncState {/// <summary>/// 是否完成。/// </summary>public bool Completed { get; set; }/// <summary>/// 數據/// </summary>public byte[] Data { get; set; } /// <summary>/// 是否異步/// </summary>public bool IsAsync { get; set; } }下面我們開始編寫TCP連接功能。
/// <summary>/// 連接至服務器。/// </summary>/// <param name="endpoint">服務器終結點。</param>public void Connect(IPEndPoint endpoint){//判斷是否已連接if (IsConnected)throw new InvalidOperationException("已連接至服務器。");if (endpoint == null)throw new ArgumentNullException("endpoint");//鎖定自己,避免多線程同時操作lock (this){SocketAsyncState state = new SocketAsyncState();//Socket異步連接 Socket.BeginConnect(endpoint, EndConnect, state).AsyncWaitHandle.WaitOne();//等待異步全部處理完成while (!state.Completed) { }}}/// <summary>/// 異步連接至服務器。/// </summary>/// <param name="endpoint"></param>public void ConnectAsync(IPEndPoint endpoint){//判斷是否已連接if (IsConnected)throw new InvalidOperationException("已連接至服務器。");if (endpoint == null)throw new ArgumentNullException("endpoint");//鎖定自己,避免多線程同時操作lock (this){SocketAsyncState state = new SocketAsyncState();//設置狀態為異步state.IsAsync = true;//Socket異步連接 Socket.BeginConnect(endpoint, EndConnect, state);}}private void EndConnect(IAsyncResult result){SocketAsyncState state = (SocketAsyncState)result.AsyncState;try{Socket.EndConnect(result);}catch{//出現異常,連接失敗。state.Completed = true;//判斷是否為異步,異步則引發事件if (state.IsAsync && ConnectCompleted != null)ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));return;}//連接成功。//創建Socket網絡流Stream = new NetworkStream(Socket);//連接完成state.Completed = true;if (state.IsAsync && ConnectCompleted != null){ConnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Connect));}//開始接收數據 Handler.BeginReceive(Stream, EndReceive, state);}??? /// <summary>
??? /// 連接完成時引發事件。
??? /// </summary>
??? public event EventHandler<SocketEventArgs> ConnectCompleted;
以上為連接服務器的代碼,EndConnect中最后的Handler為一個處理IO收發的類,這留到后面再說。
接下來我們開始做斷開服務器的方法。
/// <summary>/// 斷開與服務器的連接。/// </summary>public void Disconnect(){//判斷是否已連接if (!IsConnected)throw new InvalidOperationException("未連接至服務器。");lock (this){//Socket異步斷開并等待完成Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();}}/// <summary>/// 異步斷開與服務器的連接。/// </summary>public void DisconnectAsync(){//判斷是否已連接if (!IsConnected)throw new InvalidOperationException("未連接至服務器。");lock (this){//Socket異步斷開Socket.BeginDisconnect(true, EndDisconnect, false);}}private void EndDisconnect(IAsyncResult result){try{Socket.EndDisconnect(result);}catch{}//是否同步bool sync = (bool)result.AsyncState;if (!sync && DisconnectCompleted!=null){DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));}}//這是一個給收發異常準備的斷開引發事件方法private void Disconnected(bool raiseEvent){if (raiseEvent && DisconnectCompleted != null)DisconnectCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Disconnect));}至此,我們已經完成了客戶端的連接于斷開功能。
現在我們開始寫客戶端的發送接收功能。
對于Socket的發送與接收,在大量數據吞吐的時候,容易造成粘包問題,要解決這個問題,我們先定義一個ISocketHandler接口。
該接口定義了Socket的發送與接收。
public interface ISocketHandler {/// <summary>/// 開始接收/// </summary>/// <param name="stream">Socket網絡流</param>/// <param name="callback">回調函數</param>/// <param name="state">自定義狀態</param>/// <returns>異步結果</returns>IAsyncResult BeginReceive(Stream stream, AsyncCallback callback, object state);/// <summary>/// 結束接收/// </summary>/// <param name="asyncResult">異步結果</param>/// <returns>接收到的數據</returns>byte[] EndReceive(IAsyncResult asyncResult);/// <summary>/// 開始發送/// </summary>/// <param name="data">要發送的數據</param>/// <param name="offset">數據偏移</param>/// <param name="count">發送長度</param>/// <param name="stream">Socket網絡流</param>/// <param name="callback">回調函數</param>/// <param name="state">自定義狀態</param>/// <returns>異步結果</returns>IAsyncResult BeginSend(byte[] data, int offset, int count, Stream stream, AsyncCallback callback, object state);/// <summary>/// 結束發送/// </summary>/// <param name="asyncResult">異步結果</param>/// <returns>發送是否成功</returns>bool EndSend(IAsyncResult asyncResult); }在TCPClient中添加一個屬性。
/// <summary>/// Socket處理程序/// </summary>public ISocketHandler Handler { get; set; }這個ISocketHandler在上面的EndConnect里有使用到BeginReceive()。
而使用BeginReceive的回調函數則是這個。
private void EndReceive(IAsyncResult result){SocketAsyncState state = (SocketAsyncState)result.AsyncState;//接收到的數據byte[] data = Handler.EndReceive(result);//如果數據長度為0,則斷開Socket連接if (data.Length == 0){Disconnected(true);return;}//再次開始接收數據 Handler.BeginReceive(Stream, EndReceive, state);//引發接收完成事件if (ReceiveCompleted != null)ReceiveCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Receive) { Data = data });}有了這個回調函數,我們的客戶端就能持續的接收數據。
現在剩下發送數據的功能要完成。
/// <summary>/// 發送數據。/// </summary>/// <param name="data">要發送的數據。</param>public void Send(byte[] data){//是否已連接if (!IsConnected)throw new SocketException(10057);//發送的數據不能為nullif (data == null)throw new ArgumentNullException("data");//發送的數據長度不能為0if (data.Length == 0)throw new ArgumentException("data的長度不能為0");//設置異步狀態SocketAsyncState state = new SocketAsyncState();state.IsAsync = false;state.Data = data;try{//開始發送數據Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state).AsyncWaitHandle.WaitOne();}catch{//出現異常則斷開Socket連接Disconnected(true);}}/// <summary>/// 異步發送數據。/// </summary>/// <param name="data">要發送的數據。</param>public void SendAsync(byte[] data){//是否已連接if (!IsConnected)throw new SocketException(10057);//發送的數據不能為nullif (data == null)throw new ArgumentNullException("data");//發送的數據長度不能為0if (data.Length == 0)throw new ArgumentException("data的長度不能為0");//設置異步狀態SocketAsyncState state = new SocketAsyncState();state.IsAsync = true;state.Data = data;try{//開始發送數據并等待完成Handler.BeginSend(data, 0, data.Length, Stream, EndSend, state);}catch{//出現異常則斷開Socket連接Disconnected(true);}}private void EndSend(IAsyncResult result){SocketAsyncState state = (SocketAsyncState)result.AsyncState;//是否完成state.Completed = Handler.EndSend(result);//沒有完成則斷開Socket連接if (!state.Completed)Disconnected(true);//引發發送結束事件if (state.IsAsync && SendCompleted != null){SendCompleted(this, new SocketEventArgs(this, SocketAsyncOperation.Send) { Data = state.Data });}}至此,客戶端的發送接收也完成了。
我們再寫一下釋放資源的方法。
/// <summary>/// 釋放資源/// </summary>public void Dispose(){lock (this){if (IsConnected)Socket.Disconnect(false);Socket.Close();}}整個客戶端編寫完成。
下一篇將講解ISocketHandler的實現方法,用ISocketHandler來完成Socket的IO工作。
你可以在ISocketHandler中定義你自己的Socket通訊協議。
?
原文地址:http://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html
轉載于:https://www.cnblogs.com/Kation/archive/2013/03/06/2946761.html
總結
以上是生活随笔為你收集整理的[C#]手把手教你打造Socket的TCP通讯连接(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux 管道和重定向
- 下一篇: 带你攀顶云端高级认证,有这回事?