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