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         }

 

posted @ 2013-04-16 12:44  猴健居士  阅读(10877)  评论(15编辑  收藏  举报