上一篇中,我們編寫了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,則是隨機端口,把這個端口賦值給port port = ((IPEndPoint)socket.LocalEndPoint).Port; //服務啟動中設置為true IsStarted = true; //開始異步監聽 socket.BeginAccept(EndAccept, null); } } //異步監聽結束 private void EndAccept(IAsyncResult result) { //獲得客戶端Socket Socket 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復制過來。

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); //發送的數據不能為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 }); } } #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; else data.Add(key, value); } }
為構造函數添加以下代碼。
data = new Dictionary<string, object>(); //保存IP地址到字典 this["RemoteEndPoint"] = socket.RemoteEndPoint;
這樣,我們的TCPListenerClient就完成了。
接下來我們再把TCPClient修改以下,繼承SocketBase。

/// <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