[C#]手把手教你打造Socket的TCP通讯连接(三)
上一篇中,我們編寫了SocketHandler處理Socket的IO。
現在我們只剩下服務器端了。
服務器端包含兩個類,一個TCPListener,一個TCPListenerClient。
TCPListener只管Start與Stop還有Accept。
TCPListenerClient是連接到服務器的客戶端,相當于TCPClient在TCPListener上的體現。
現在我們開始編寫TCPListener。
/// <summary> /// TCP監聽端 /// </summary> public class TCPListener : IEnumerable<TCPListenerClient> {private Socket socket;private HashSet<TCPListenerClient> clients;/// <summary>/// 實例化TCP監聽者。/// </summary>public TCPListener(){clients = new HashSet<TCPListenerClient>();IsStarted = false;Handler = new SocketHandler();}public ISocketHandler Handler { get; set; }private int port;/// <summary>/// 監聽端口。/// </summary>public int Port{get { return port; }set{if (value < 0 || value > 65535)throw new ArgumentOutOfRangeException(value + "不是有效端口。");port = value;}}/// <summary>/// 服務啟動中/// </summary>public bool IsStarted { get; private set; }/// <summary>/// 開始服務。/// </summary>public void Start(){}/// <summary>/// 停止服務。/// </summary>public void Stop(){}/// <summary>/// 接收完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> ReceiveCompleted;/// <summary>/// 接受客戶完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> AcceptCompleted;/// <summary>/// 客戶斷開完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> DisconnectCompleted;/// <summary>/// 發送完成時引發事件。/// </summary>public event EventHandler<SocketEventArgs> SendCompleted;/// <summary>/// 獲取客戶端泛型。/// </summary>/// <returns></returns>public IEnumerator<TCPListenerClient> GetEnumerator(){return clients.GetEnumerator();}/// <summary>/// 獲取客戶端泛型。/// </summary>/// <returns></returns> System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator(){return clients.GetEnumerator();} /// <summary>/// 釋放資源。/// </summary>/// <returns></returns> public void Dispose(){} }TCPListener繼承IEnumerable<TCPListenerClient>與IDisposable
clients保存所有已連接的客戶端。
編寫Start方法。
/// <summary>/// 開始服務。/// </summary>public void Start(){lock (this){if (IsStarted)throw new InvalidOperationException("已經開始服務。");socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//綁定端口//可以引發端口被占用異常socket.Bind(new IPEndPoint(IPAddress.Any, port));//監聽隊列socket.Listen(512);//如果端口是0,則是隨機端口,把這個端口賦值給portport = ((IPEndPoint)socket.LocalEndPoint).Port;//服務啟動中設置為trueIsStarted = true;//開始異步監聽socket.BeginAccept(EndAccept, null);}}//異步監聽結束private void EndAccept(IAsyncResult result){//獲得客戶端SocketSocket clientSocket = socket.EndAccept(result);//實例化客戶端類TCPListenerClient client = new TCPListenerClient(this, clientSocket);//增加事件鉤子client.SendCompleted += client_SendCompleted;client.ReceiveCompleted += client_ReceiveCompleted;client.DisconnectCompleted += client_DisconnectCompleted;socket.BeginAccept(EndAccept, null);//增加客戶端lock (clients)clients.Add(client);//客戶端連接事件if (AcceptCompleted != null)AcceptCompleted(this, new SocketEventArgs(client, SocketAsyncOperation.Accept));}//客戶端斷開連接private void client_DisconnectCompleted(object sender, SocketEventArgs e){//移除客戶端lock (clients)clients.Remove((TCPListenerClient)e.Socket);e.Socket.DisconnectCompleted -= client_DisconnectCompleted;e.Socket.ReceiveCompleted -= client_ReceiveCompleted;e.Socket.SendCompleted -= client_SendCompleted;if (DisconnectCompleted != null)DisconnectCompleted(this, e);}//收到客戶端發送的數據private void client_ReceiveCompleted(object sender, SocketEventArgs e){if (ReceiveCompleted != null)ReceiveCompleted(this, e);}//向客戶端發送數據完成private void client_SendCompleted(object sender, SocketEventArgs e){if (SendCompleted != null)SendCompleted(this, e);}編寫Stop與Dispose方法。
/// <summary>/// 停止服務。/// </summary>public void Stop(){lock (this){if (!IsStarted)throw new InvalidOperationException("沒有開始服務。");foreach (TCPListenerClient client in clients){client.Disconnect();client.DisconnectCompleted -= client_DisconnectCompleted;client.ReceiveCompleted -= client_ReceiveCompleted;client.SendCompleted -= client_SendCompleted;}socket.Close();socket = null;IsStarted = false;}}/// <summary>/// 釋放資源/// </summary>public void Dispose(){if (socket == null)return;Stop();}輪到TCPListenerClient了,TCPListenerClient其實和TCPClient差不多,也是要繼承ISocket和IDisposable。
既然重復代碼做么多,要不要合并起來呢?答案是肯定的。
做一個SocketBase類,繼承ISocket和IDisposable。
大部分代碼直接從TCPClient復制過來。
View Code public class SocketBase : ISocket, IDisposable {protected Socket Socket { get; private set; }protected Stream Stream { get; set; }/// <summary>/// 實例化TCP客戶端。/// </summary>public SocketBase(Socket socket, ISocketHandler socketHandler){if (socket == null)throw new ArgumentNullException("socket");if (socketHandler == null)throw new ArgumentNullException("socketHandler");Socket = socket;Handler = socketHandler;}/// <summary>/// Socket處理程序/// </summary>public ISocketHandler Handler { get; set; }/// <summary>/// 獲取是否已連接。/// </summary>public bool IsConnected { get { return Socket.Connected; } }#region 斷開連接/// <summary>/// 斷開與服務器的連接。/// </summary>public void Disconnect(){//判斷是否已連接if (!IsConnected)throw new SocketException(10057);lock (this){//Socket異步斷開并等待完成Socket.BeginDisconnect(true, EndDisconnect, true).AsyncWaitHandle.WaitOne();}}/// <summary>/// 異步斷開與服務器的連接。/// </summary>public void DisconnectAsync(){//判斷是否已連接if (!IsConnected)throw new SocketException(10057);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));}#endregion#region 發送數據/// <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 });}}#endregion#region 接收數據protected 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 });}#endregion#region 事件///// <summary>///// 斷開完成時引發事件。///// </summary>public event EventHandler<SocketEventArgs> DisconnectCompleted;///// <summary>///// 接收完成時引發事件。///// </summary>public event EventHandler<SocketEventArgs> ReceiveCompleted;///// <summary>///// 發送完成時引發事件。///// </summary>public event EventHandler<SocketEventArgs> SendCompleted;#endregion/// <summary>/// 釋放資源/// </summary>public void Dispose(){lock (this){if (IsConnected)Socket.Disconnect(false);Socket.Close();}} }然后我們再寫TCPListenerClient,繼承SocketBase。
public class TCPListenerClient : SocketBase {internal TCPListenerClient(TCPListener listener, Socket socket):base(socket,listener.Handler){??????? data = new Dictionary<string, object>();this["RemoteEndPoint"] = socket.RemoteEndPoint;//創建Socket網絡流Stream = new NetworkStream(socket); //設置服務器Listener = listener; //開始異步接收數據SocketAsyncState state = new SocketAsyncState();Handler.BeginReceive(Stream, EndReceive, state);}public TCPListener Listener { get; private set; } }
我們還可以給TCPListenerClient加上點東西,比如類似Session的東西。
private Dictionary<string, object> data;public object this[string key]{get{key = key.ToLower();if (data.ContainsKey(key))return data[key];return null;}set{key = key.ToLower();if (value == null){if (data.ContainsKey(key))data.Remove(key);return;}if (data.ContainsKey(key))data[key] = value;elsedata.Add(key, value);}}為構造函數添加以下代碼。
data = new Dictionary<string, object>();//保存IP地址到字典this["RemoteEndPoint"] = socket.RemoteEndPoint;這樣,我們的TCPListenerClient就完成了。
接下來我們再把TCPClient修改以下,繼承SocketBase。
View Code /// <summary> /// TCP客戶端 /// </summary> public class TCPClient : SocketBase {/// <summary>/// 實例化TCP客戶端。/// </summary>public TCPClient(): base(new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp), new SocketHandler()){}public bool IsUseAuthenticate { get; set; }/// <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);if (IsUseAuthenticate){NegotiateStream negotiate = new NegotiateStream(Stream);negotiate.AuthenticateAsClient();while (!negotiate.IsMutuallyAuthenticated){Thread.Sleep(10);}}//連接完成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; }所有工作,全部完成。
這個Socket還有很多功能可以增加、改造。
比如你自己寫一個Handler內置加密解密,或者壓縮與解壓縮。
還可以再改寫一下Stream,可以弄成NegotiateStream驗證等等。
下一篇我們總結一下所有工作。
?
原文地址:http://www.cnblogs.com/Kation/archive/2013/03/07/2947278.html
轉載于:https://www.cnblogs.com/Kation/archive/2013/03/07/2947278.html
總結
以上是生活随笔為你收集整理的[C#]手把手教你打造Socket的TCP通讯连接(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于Hadoop的云盘系统客户端技术选型
- 下一篇: SSH框架