本文靈感來自Andre Azevedo 在CodeProject上面的一片文章,An Asynchronous Socket Server and Client,講的是異步的Socket通信。
關於技術博客,我覺得永遠是老外的比較好~Andre Azevedo的這篇文章里,給出了一個很復雜的例子,內容涉及如下
- Socket連接(Socket Connection)
- Socket服務(Socket Service)
- 連接主機(Connection Host)
- 加密與壓縮(Encrypt與Compress)
- 請求入隊(Enqueuing Requests)
- 確保發送和接收(Ensure send and recieve)
- 檢查消息頭(Check message header)
- 檢查空閑連接(Checking idle connections)
- 加密服務
- SSL認證(SSL authentication)
- 對稱認證(Symmetric authentication)
- 連接創建者(Connection Creator)
- Socket服務器與Socket偵聽者(SocketServer and SocketListener)
- Socket服務器構造函數與方法(SocketServer constructor and methods)
- Socket客戶端與Socket連接者(SocketClient and SocketConnector)
- Socket客戶端構造函數與方法(SocketClient constructor and methods)
- 應答演示項目(Echo Demo Project)
- 主機(Hosts)
- 服務(Services)
- 結語(Conclusion)
- 版本歷史(History)
本文僅實現一個相對簡單的異步Socket服務器與客戶端通信示例。
首先需要說明如下2個問題
1.同步、異步、多線程是什么關系?答:同步是等待返回,相當於阻塞式;異步是不等待返回,是非阻塞式,可以用多線程實現。
2.有些異步方法有兩種實現方式, 如BeginAccept()和AcceptAsync(), 這兩個方法有什么區別呢?答: 以 Begin 和 End 開頭的方法是以 APM(Asynchronous Programming Model)設計方法實現的異步操作, 以 Async 結尾的方法是利用稱為 EAP (Event-based Asynchronous Pattern) 的設計方法實現的異步操作。
界面簡單如下:
主要代碼如下:
using System; using System.Net; using System.Net.Sockets; using System.Text; namespace Chatting { 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); } } 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); } } 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)); } } } }
相關的事件處理程序,如下:

using System; using System.Drawing; using System.Windows.Forms; using System.Net.Sockets; namespace Chatting { public partial class MainForm : Form { public MainForm() { InitializeComponent(); } SocketFunc socket; System.Action<string> ReceiveAction; System.Action AccessAction; private void MainForm_Load(object sender, EventArgs e) { //異步建立連接回調 AccessAction = () => { this.Invoke((MethodInvoker)delegate() { lblFriendIP.Visible = false; txtIP.Visible = false; btnConnect.Visible = false; btnWaitAccess.Visible = false; String friendIP = socket.communicateSocket.RemoteEndPoint.ToString(); lblState.Text = String.Format("連接成功. 對方IP:{0}", friendIP); try { socket.Receive(ReceiveAction); } catch (Exception exp) { MessageBox.Show(exp.Message, "錯誤"); } }); }; //異步接收消息回調 ReceiveAction = msg => { txtGetMsg.Invoke((MethodInvoker)delegate() { UpdateGetMsgTextBox(false, msg, Color.Red); }); }; } private void btnWaitAccess_Click(object sender, EventArgs e) { this.socket = new ServerSocket(); try { this.socket.Access("", this.AccessAction); } catch (Exception ecp) { MessageBox.Show(ecp.Message, "錯誤"); } lblState.Text = "等待對方連接..."; } private void btnConnect_Click(object sender, EventArgs e) { this.socket = new ClientSocket(); try { this.socket.Access(txtIP.Text, this.AccessAction); } catch (Exception ecp) { MessageBox.Show(ecp.Message, "錯誤"); } lblState.Text = "正在連接對方..."; } private void btnSendMsg_Click(object sender, EventArgs e) { string message = txtSendMsg.Text.Trim(); if (string.IsNullOrEmpty(message)) { MessageBox.Show("消息內容不能為空!", "錯誤"); txtSendMsg.Focus(); return; } try { socket.Send(message); } catch(Exception ecp) { MessageBox.Show(ecp.Message, "錯誤"); return; } UpdateGetMsgTextBox(true, message, Color.Blue); txtSendMsg.Text = ""; } private void UpdateGetMsgTextBox(bool sendMsg, string message, Color color) { string appendText; if (sendMsg == true) { appendText = "Client: " + System.DateTime.Now.ToString() + Environment.NewLine + message + Environment.NewLine; } else { appendText = "Server: " + System.DateTime.Now.ToString() + Environment.NewLine + message + Environment.NewLine; } int txtGetMsgLength = txtGetMsg.Text.Length; txtGetMsg.AppendText(appendText); txtGetMsg.Select(txtGetMsgLength, appendText.Length - Environment.NewLine.Length*2 -message.Length); txtGetMsg.SelectionColor = color; txtGetMsg.ScrollToCaret(); } private void txtSendMsg_Click(object sender, EventArgs e) { SetStyle(ControlStyles.SupportsTransparentBackColor, true); BackColor = Color.FromArgb(50, 40, 60, 82); } } }
效果,如下: