五一在家看了一會兒<重構手冊>, 想拿以前寫的代碼嘗試著改進改進, 想起去年暑假寫的區域網路聊天小工具, 現在看自己那時寫的代碼已經不堪入目, 最不可思議的當屬用了"多線程"處理網路請求, 現在覺得應該使用非同步方法呼叫.
主要設計
簡要說明
左邊部分表示的是用戶端的過程, 右邊部分表示的是服務端的過程. 用戶端相比服務端在建立串連之前步驟稍微少一些, 成功建立串連後用戶端和服務端都有一個CommunicateSocket負責與對方通訊, 如發送訊息, 接收訊息, 傳送檔案, 接收檔案等.
服務端, 聲明ServerSocket, 綁定(Bind)一個IP並指定這個IP的通訊連接埠, 比如是127.0.0.1:9050, ServerSocket可以監聽來自多個IP發送的串連請求, 監聽(Listen)方法的參數可以設定允許的最多串連請求個數. 然後調用非同步接受請求的方法(BeginAccept), 如果接受到某個用戶端發來串連請求, 這時定義一個新的CommunicateSocket專門負責與這個用戶端通訊. 然後可以通過CommunicateSocket.BeginSend()方法給用戶端發送資料, CommunicateSocket.BeginReceive()可以接收用戶端發來的資料.
用戶端, 有一個CommunicateSocket, 並綁定一個IP以及一個未被佔用的連接埠, 定義IPEndPoint serverIP表示服務端Socket的IP和連接埠, 這樣才可以進行連接埠對連接埠之間的通訊, 接下來就可以嘗試CommunicateSocket.BeginConnect(serverIP), 串連成功之後就可以發送和接收資料了, CommunicateSocket.BeginSend(), CommunicateSocket.BeginReceive().
有些非同步方法呼叫有兩種實現方式, 如BeginAccept()和AcceptAsync(), 這兩個方法有什麼區別呢? 以 Begin 和 End 開頭的方法是以 APM(Asynchronous Programming Model)設計方法實現的非同步作業, 以 Async 結尾的方法是利用稱為 EAP (Event-based Asynchronous Pattern) 的設計方法實現的非同步作業.
代碼部分1. SocketFunc類
SocketFunc是一個抽象類別, 服務端和用戶端只有建立串連的方法不同, 其它都相同, 所以把相同的部分放到這個類中.
public abstract class SocketFunc{ //不管是服務端還是用戶端, 建立串連後用這個Socket進行通訊 public Socket communicateSocket = null; //服務端和用戶端建立串連的方式稍有不同, 子類會重載 public abstract void Access(string IP, System.Action AccessAciton); //發送訊息的函數 public void Send(string message) { if (communicateSocket.Connected == false) { throw new Exception("還沒有建立串連, 不能發送訊息"); } Byte[] msg = Encoding.UTF8.GetBytes(message); communicateSocket.BeginSend(msg,0, msg.Length, SocketFlags.None, ar => { }, null); } //接受訊息的函數 public void Receive(System.Action<string> ReceiveAction) { //如果訊息超過1024個位元組, 收到的訊息會分為(總位元組長度/1024 +1)條顯示 Byte[] msg = new byte[1024]; //非同步接受訊息 communicateSocket.BeginReceive(msg, 0, msg.Length, SocketFlags.None, ar => { //對方中斷連線時, 這裡拋出Socket Exception //An existing connection was forcibly closed by the remote host communicateSocket.EndReceive(ar); ReceiveAction(Encoding.UTF8.GetString(msg).Trim('\0',' ')); Receive(ReceiveAction); }, null); }}2. ServerSocket:SocketFunc類
繼承自SocketFunc類, 類中重載了Access方法.
public class ServerSocket:SocketFunc{ //服務端重載Access函數 public override void Access(string IP, System.Action AccessAciton) { Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); //本機預使用的IP和連接埠 IPEndPoint serverIP = new IPEndPoint(IPAddress.Any, 9050); //綁定服務端設定的IP serverSocket.Bind(serverIP); //設定監聽個數 serverSocket.Listen(1); //非同步接收串連請求 serverSocket.BeginAccept(ar => { base.communicateSocket = serverSocket.EndAccept(ar); AccessAciton(); }, null); }}3. ClientSocket:SocketFunc類
繼承自SocketFunc類, 類中重載了Access方法.
public class ClientSocket:SocketFunc{ //用戶端重載Access函數 public override void Access(string IP, System.Action AccessAciton) { base.communicateSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); base.communicateSocket.Bind(new IPEndPoint(IPAddress.Any, 9051)); //伺服器的IP和連接埠 IPEndPoint serverIP; try { serverIP = new IPEndPoint(IPAddress.Parse(IP), 9050); } catch { throw new Exception(String.Format("{0}不是一個有效IP地址!", IP)); } //用戶端只用來向指定的伺服器發送資訊,不需要綁定原生IP和連接埠,不需要監聽 try { base.communicateSocket.BeginConnect(serverIP, ar => { AccessAciton(); }, null); } catch { throw new Exception(string.Format("嘗試串連{0}不成功!", IP)); } }}程式
源碼下載