本文章將講解基於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);
//發送的數據不能為null
if (data == null)
throw new ArgumentNullException("data");
//發送的數據長度不能為0
if (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);
//發送的數據不能為null
if (data == null)
throw new ArgumentNullException("data");
//發送的數據長度不能為0
if (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

