Socket異步通信——使用SocketAsyncEventArgs


  上一次的博文說錯了東西,幸好有園友指出。才把錯誤改正過來,順便也把利用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         }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM