上一次的博文說錯了東西,幸好有園友指出。才把錯誤改正過來,順便也把利用SocketAsyncEventArgs進行Socket異步通信這方面的知識整理一下。
之前看了網上的代碼,每進行一次異步操作都new 一個SocketAsyncEventArgs對象,然后網友評論太浪費資源了,於是就誤以為用BeginXXX進行Socket異步通信會更優,幸好有園友指出我的誤區,再看了這篇文章《net3.5與.net2.0 Socket性能比較》之后才發現SocketAsyncEventArgs是.NET Framework 3.5才出現的,而IAsyncResult是.NET Framework 2.0出現的,單純從這點來看,新出的東西總會比舊的東西有優越之處吧。在用了之后明顯的感覺就是,SocketAsyncEventArgs可以重復利用,而IAsyncResult對象就每次BeginXXX調用一次,就會生成一個,同樣的進行多次的接收和發送之后,使用SocketAsyncEventArgs的一直都是使用那么一兩個對象;可IAsyncResult的就每Begin一次就創建一次,累計下來創建了對象的數量很明顯有差別。但是本人在使用SocketAsyncEventArgs時有部分思想還是停留在之前用BeginXXX方式時的。下面逐一列舉吧
同樣也是定義了一個數據結構
1 class ConnectInfo 2 { 3 public ArrayList tmpList { get; set; } 4 public SocketAsyncEventArgs SendArg { get; set; } 5 public SocketAsyncEventArgs ReceiveArg { get; set; } 6 public Socket ServerSocket{get;set;} 7 }
雖然SocketAsyncEventArgs可以重復利用,但我在整個程序里頭總共使用了三個,一個用於Accept的,這個在Complete事件綁定的方法里釋放資源了。另外兩個分別用於發送和接收。ArrayList是暫存客戶端發來的數據。
下面則是Main方法里的代碼
1 IPEndPoint serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); 2 Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 3 4 serverSocket.Bind(serverPoint); 5 serverSocket.Listen(10); 6 7 Console.WriteLine("waiting for a client"); 8 SocketAsyncEventArgs e=new SocketAsyncEventArgs(); 9 e.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed); 10 serverSocket.AcceptAsync(e); 11 12 Console.ReadLine();
個人感覺這部分與使用IAsyncReult的BeginXXX不同的就是不需要把客戶端的Socket對象傳到Accept的方法里。
接着是與Complete事件定義的方法定義,也就是Accept成功之后調用的方法
1 static void Accept_Completed(object sender, SocketAsyncEventArgs e) 2 { 3 Socket client = e.AcceptSocket; 4 Socket server = sender as Socket; 5 6 if (sender == null) return; 7 8 SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs(); 9 SocketAsyncEventArgs receciveArg = new SocketAsyncEventArgs(); 10 11 ConnectInfo info = new ConnectInfo(); 12 info.tmpList = new ArrayList(); 13 info.SendArg = sendArg; 14 info.ReceiveArg = receciveArg; 15 info.ServerSocket=server; 16 17 byte[] sendbuffers=Encoding.ASCII.GetBytes("hello world"); 18 sendArg.SetBuffer(sendbuffers, 0, sendbuffers.Length); 19 20 sendbuffers=new byte[1024]; 21 receciveArg.SetBuffer(sendbuffers, 0, 1024); 22 receciveArg.UserToken = info; 23 receciveArg.Completed += new EventHandler<SocketAsyncEventArgs>(Rececive_Completed); 24 25 client.SendAsync(sendArg); 26 client.ReceiveAsync(receciveArg); 27 28 e.Dispose(); 29 }
由於有sender這個參數,就可以獲取服務端的Socket對象,在這里把兩個SocektAsyncEventArgs對象構造出來, 在構造了一個ConnectInfo的對象記錄本次通信的一些信息。ConnectInfo這部分與BeginXXX的類似。但把ConnectInfo的對象傳過去給相應的Complete事件處理方法里面就有所不同了,SocketAsyncEventArgs是用UserToken屬性,把關聯的用戶或應用程序對象傳過去。設置Buffer就用SetBuffer方法。發送消息並沒有綁定Complete方法,感覺綁了也沒什么用。
最后上的是接受成功的代碼
1 static void Rececive_Completed(object sender, SocketAsyncEventArgs e) 2 { 3 ConnectInfo info = e.UserToken as ConnectInfo; 4 if (info == null) return; 5 Socket client = sender as Socket; 6 if (client == null) return; 7 8 if (e.SocketError== SocketError.Success) 9 { 10 int rec = e.BytesTransferred; 11 //收不到數據表明客戶端終止了通信 12 //關閉相關的socket和釋放資源 13 if (rec == 0) 14 { 15 client.Close(); 16 client.Dispose(); 17 18 info.ReceiveArg.Dispose(); 19 info.SendArg.Dispose(); 20 21 if (info.ServerSocket != null) 22 { 23 info.ServerSocket.Close(); 24 info.ServerSocket.Dispose(); 25 } 26 return; 27 } 28 29 byte[] datas=e.Buffer; 30 31 //如果數據還沒接收完的就把已接收的數據暫存 32 //新開辟一個足夠大的buffer來接收數據 33 if (client.Available > 0) 34 { 35 for (int i = 0; i < rec; i++) 36 info.tmpList.Add(datas[i]); 37 Array.Clear(datas, 0, datas.Length); 38 39 datas = new byte[client.Available]; 40 e.SetBuffer(datas, 0, datas.Length); 41 client.ReceiveAsync(e); 42 } 43 else 44 { 45 //檢查暫存數據的ArrayList中有沒有數據,有就和本次的數據合並 46 if (info.tmpList.Count > 0) 47 { 48 for (int i = 0; i < rec; i++) 49 info.tmpList.Add(datas[i]); 50 datas = info.tmpList.ToArray(typeof(byte)) as byte[]; 51 rec = datas.Length; 52 } 53 54 //對接收的完整數據進行簡單處理,回發給客戶端 55 string msg = Encoding.ASCII.GetString(datas).Trim('\0'); 56 if (msg.Length > 10) msg = msg.Substring(0, 10) + "..."; 57 msg = string.Format("rec={0}\r\nmessage={1}", rec, msg); 58 59 info.SendArg.SetBuffer(Encoding.ASCII.GetBytes(msg),0,msg.Length); 60 client.SendAsync(info.SendArg); 61 62 //如果buffer過大的,把它換成一個小的 63 info.tmpList.Clear(); 64 if (e.Buffer.Length > 1024) 65 { 66 datas = new byte[1024]; 67 e.SetBuffer(datas, 0, datas.Length); 68 } 69 70 //再次進行異步接收 71 client.ReceiveAsync(e); 72 } 73 } 74 }
這里也是考慮到接收數據量過大造成下次接收時粘包,也做類似上篇博文中的那樣處理。在接收過大的數據時需要分兩次讀取,用一個ArrayList暫時存放已經讀取的數據。在上一篇博文有位園友提了一下內存消耗的問題,於是這次我縮減了一下byte[]的使用量。這樣應該消耗不再大了吧!
盡管這次我做的感覺比上次的要好,但對於在行的人應該會挑得出不少毛病出來的。上面有什么說錯的請各位指出,有什么說漏的,請各位提點,多多指導。謝謝!

1 class ConnectInfo 2 { 3 public ArrayList tmpList { get; set; } 4 public SocketAsyncEventArgs SendArg { get; set; } 5 public SocketAsyncEventArgs ReceiveArg { get; set; } 6 public Socket ServerSocket{get;set;} 7 } 8 9 class EventSocektServer 10 { 11 static void Main() 12 { 13 IPEndPoint serverPoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8081); 14 Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 15 16 serverSocket.Bind(serverPoint); 17 serverSocket.Listen(10); 18 19 Console.WriteLine("waiting for a client"); 20 SocketAsyncEventArgs e=new SocketAsyncEventArgs(); 21 e.Completed += new EventHandler<SocketAsyncEventArgs>(Accept_Completed); 22 serverSocket.AcceptAsync(e); 23 24 Console.ReadLine(); 25 } 26 27 static void Accept_Completed(object sender, SocketAsyncEventArgs e) 28 { 29 Socket client = e.AcceptSocket; 30 Socket server = sender as Socket; 31 32 if (sender == null) return; 33 34 SocketAsyncEventArgs sendArg = new SocketAsyncEventArgs(); 35 SocketAsyncEventArgs receciveArg = new SocketAsyncEventArgs(); 36 37 ConnectInfo info = new ConnectInfo(); 38 info.tmpList = new ArrayList(); 39 info.SendArg = sendArg; 40 info.ReceiveArg = receciveArg; 41 info.ServerSocket=server; 42 43 byte[] sendbuffers=Encoding.ASCII.GetBytes("hello world"); 44 sendArg.SetBuffer(sendbuffers, 0, sendbuffers.Length); 45 46 sendbuffers=new byte[1024]; 47 receciveArg.SetBuffer(sendbuffers, 0, 1024); 48 receciveArg.UserToken = info; 49 receciveArg.Completed += new EventHandler<SocketAsyncEventArgs>(Rececive_Completed); 50 51 client.SendAsync(sendArg); 52 client.ReceiveAsync(receciveArg); 53 54 e.Dispose(); 55 } 56 57 static void Rececive_Completed(object sender, SocketAsyncEventArgs e) 58 { 59 ConnectInfo info = e.UserToken as ConnectInfo; 60 if (info == null) return; 61 Socket client = sender as Socket; 62 if (client == null) return; 63 64 if (e.SocketError == SocketError.Success) 65 { 66 int rec = e.BytesTransferred; 67 //收不到數據表明客戶端終止了通信 68 //關閉相關的socket和釋放資源 69 if (rec == 0) 70 { 71 client.Close(); 72 client.Dispose(); 73 74 info.ReceiveArg.Dispose(); 75 info.SendArg.Dispose(); 76 77 if (info.ServerSocket != null) 78 { 79 info.ServerSocket.Close(); 80 info.ServerSocket.Dispose(); 81 } 82 return; 83 } 84 85 byte[] datas = e.Buffer; 86 87 //如果數據還沒接收完的就把已接收的數據暫存 88 //新開辟一個足夠大的buffer來接收數據 89 if (client.Available > 0) 90 { 91 for (int i = 0; i < rec; i++) 92 info.tmpList.Add(datas[i]); 93 Array.Clear(datas, 0, datas.Length); 94 95 datas = new byte[client.Available]; 96 e.SetBuffer(datas, 0, datas.Length); 97 client.ReceiveAsync(e); 98 } 99 else 100 { 101 //檢查暫存數據的ArrayList中有沒有數據,有就和本次的數據合並 102 if (info.tmpList.Count > 0) 103 { 104 for (int i = 0; i < rec; i++) 105 info.tmpList.Add(datas[i]); 106 datas = info.tmpList.ToArray(typeof(byte)) as byte[]; 107 rec = datas.Length; 108 } 109 110 //對接收的完整數據進行簡單處理,回發給客戶端 111 string msg = Encoding.ASCII.GetString(datas).Trim('\0'); 112 if (msg.Length > 10) msg = msg.Substring(0, 10) + "..."; 113 msg = string.Format("rec={0}\r\nmessage={1}", rec, msg); 114 115 info.SendArg.SetBuffer(Encoding.ASCII.GetBytes(msg), 0, msg.Length); 116 client.SendAsync(info.SendArg); 117 118 //如果buffer過大的,把它換成一個小的 119 info.tmpList.Clear(); 120 if (e.Buffer.Length > 1024) 121 { 122 datas = new byte[1024]; 123 e.SetBuffer(datas, 0, datas.Length); 124 } 125 126 //再次進行異步接收 127 client.ReceiveAsync(e); 128 } 129 } 130 }