使用.NET访问Internet(4)

使用异步客户端套接字

异步客户端套接字在等待网络操作完成时不挂起应用程序。相反,它使用标准 .NET 框架异步编程模型在一个线程上处理网络连接,而应用程序继续在原始线程上运行。异步套接字适用于大量使用网络或不能等待网络操作完成才能继续的应用程序。

Socket 类遵循异步方法的 .NET 框架命名模式;例如,同步 Receive 方法对应异步 BeginReceive 和 EndReceive 方法。

异步操作要求回调方法返回操作结果。如果应用程序不需要知道结果,则不需要任何回调方法。本节中的代码示例阐释如何使用某个方法开始与网络设备的连接并使用回调方法结束连接,如何使用某个方法开始发送数据并使用回调方法完成发送,以及如何使用某个方法开始接收数据并使用回调方法结束接收数据。

异步套接字使用多个系统线程池中的线程处理网络连接。一个线程负责初始化数据的发送或接收;其他线程完成与网络设备的连接并发送或接收数据。在下列示例中, System.Threading.ManualResetEvent 类的实例用于挂起主线程的执行并在执行可以继续时发出信号。

在下面的示例中,为了将异步套接字连接到网络设备, Connect 方法初始化 Socket 实例,然后调用 BeginConnect 方法,传递表示网络设备的远程终结点、连接回调方法以及状态对象(即客户端 Socket 实例,用于在异步调用之间传递状态信息)。该示例实现 Connect 方法以将指定的 Socket 实例连接到指定的终结点。它假定存在一个名为 connectDone 的全局 ** ManualResetEvent ** 。

 [C#]


public static void Connect(EndPoint remoteEP, Socket client) {


  client.BeginConnect(remoteEP, 


    new AsyncCallback(ConnectCallback), client );


 


  connectDone.WaitOne();


}

连接回调方法 ConnectCallback 实现 AsyncCallback 委托。它在远程设备可用时连接到远程设备,然后通过设置 ManualResetEvent connectDone 向应用程序线程发出连接完成的信号。下面的代码实现 ConnectCallback 方法。

 [C#]


private static void ConnectCallback(IAsyncResult ar) {


  try {


    // Retrieve the socket from the state object.


    Socket client = (Socket) ar.AsyncState;


 


    // Complete the connection.


    client.EndConnect(ar);


 


    Console.WriteLine("Socket connected to {0}",


      client.RemoteEndPoint.ToString());


 


    // Signal that the connection has been made.


    connectDone.Set();


  } catch (Exception e) {


    Console.WriteLine(e.ToString());


  }


}

Send 示例方法以 ASCII 格式对指定的字符串数据进行编码,并将其异步发送到指定的套接字所表示的网络设备。下面的示例实现 Send 方法。

 [C#]


private static void Send(Socket client, String data) {


  // Convert the string data to byte data using ASCII encoding.


  byte[] byteData = Encoding.ASCII.GetBytes(data);


 


  // Begin sending the data to the remote device.


  client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None,


    new AsyncCallback(SendCallback), client);


}

发送回调方法 SendCallback 实现 AsyncCallback 委托。它在网络设备准备接收时发送数据。下面的示例显示 SendCallback 方法的实现。它假定存在一个名为 sendDone 的全局 ManualResetEvent 实例。

 [C#]


private static void SendCallback(IAsyncResult ar) {


  try {


    // Retrieve the socket from the state object.


    Socket client = (Socket) ar.AsyncState;


 


    // Complete sending the data to the remote device.


    int bytesSent = client.EndSend(ar);


    Console.WriteLine("Sent {0} bytes to server.", bytesSent);


 


    // Signal that all bytes have been sent.


    sendDone.Set();


  } catch (Exception e) {


    Console.WriteLine(e.ToString());


  }


}

从客户端套接字读取数据需要一个在异步调用之间传递值的状态对象。下面的类是用于从客户端套接字接收数据的示例状态对象。它包含以下各项的字段:客户端套接字,用于接收数据的缓冲区,以及用于保存传入数据字符串的 StringBuilder。将这些字段放在该状态对象中,使这些字段的值在多个调用之间得以保留,以便从客户端套接字读取数据。

 [C#]


public class StateObject {


  public Socket workSocket = null;              // Client socket.


  public const int BufferSize = 256;            // Size of receive buffer.


  public byte[] buffer = new byte[BufferSize];  // Receive buffer.


  public StringBuilder sb = new StringBuilder();// Received data string.


}

Receive 方法示例设置状态对象,然后调用 BeginReceive 方法从客户端套接字异步读取数据。下面的示例实现 Receive 方法。

 [C#]


private static void Receive(Socket client) {


  try {


    // Create the state object.


    StateObject state = new StateObject();


    state.workSocket = client;


 


    // Begin receiving the data from the remote device.


    client.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,


      new AsyncCallback(ReceiveCallback), state);


  } catch (Exception e) {


    Console.WriteLine(e.ToString());


  }


}

接收回调方法 ReceiveCallback 实现 AsyncCallback 委托。它接收来自网络设备的数据并生成消息字符串。它将来自网络的一个或多个数据字节读入数据缓冲区,然后再次调用 BeginReceive 方法,直到客户端发送的数据完成为止。从客户端读取所有数据后, ReceiveCallback 通过设置 ManualResetEvent sendDone 向应用程序线程发出数据完成的信号。

下面的示例代码实现 ReceiveCallback 方法。它假定存在一个名为 response 的全局字符串(该字符串保存接收的字符串)和一个名为 receiveDone 的全局 ManualResetEvent 实例。服务器必须正常关闭客户端套接字以结束网络会话。

 [C#]


private static void ReceiveCallback( IAsyncResult ar ) {


  try {


    // Retrieve the state object and the client socket 


    // from the async state object.


    StateObject state = (StateObject) ar.AsyncState;


    Socket client = state.workSocket;


     // Read data from the remote device.


    int bytesRead = client.EndReceive(ar);


     if (bytesRead > 0) {


      // There might be more data, so store the data received so far.


      state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));


       //  Get the rest of the data.


      client.BeginReceive(state.buffer,0,StateObject.BufferSize,0,


        new AsyncCallback(ReceiveCallback), state);


    } else {


      // All the data has arrived; put it in response.


      if (state.sb.Length > 1) {


        response = state.sb.ToString();


      }


      // Signal that all bytes have been received.


      receiveDone.Set();


    }


  } catch (Exception e) {


    Console.WriteLine(e.ToString());


  }


}

用套接字进行侦听

侦听器或服务器套接字在网络上打开一个端口,然后等待客户端连接到该端口。尽管存在其他网络地址族和协议,但本示例说明如何为 TCP/IP 网络创建远程服务。

TCP/IP 服务的唯一地址是这样定义的:将主机的 IP 地址与服务的端口号组合,以便为服务创建终结点。Dns 类提供的方法返回有关本地网络设备支持的网络地址的信息。当本地网络设备有多个网络地址时,或者当本地系统支持多个网络设备时, Dns 类返回有关所有网络地址的信息,应用程序必须为服务选择正确的地址。Internet 分配号码机构 (Internet Assigned Numbers Authority, IANA) 定义公共服务的端口号(有关更多信息,请访问 http://www.iana.org/assignments/port-numbers)。其他服务可以具有 1,024 到 65,535 范围内的注册端口号。

下面的代码示例通过将 ** Dns ** 为主机返回的第一个 IP 地址与从注册端口号范围内选择的端口号组合,为服务器创建 IPEndPoint。

 [C#]


IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());


IPAddress ipAddress = ipHostInfo.AddressList[0];


IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 11000);

确定本地终结点后,必须使用 Bind 方法将 Socket 与该终结点关联,并使用 Listen 方法设置 Socket 在该终结点上侦听。如果特定的地址和端口组合已经被使用,则 Bind 将引发异常。下面的示例阐释如何将 SocketIPEndPoint 关联。

 [C#]


listener.Bind(localEndPoint);


listener.Listen(100);

** Listen ** 方法带单个参数,该参数指定在向连接客户端返回服务器忙错误之前允许的 Socket 挂起连接数。在本示例中,在向第 101 个客户端返回服务器忙响应之前,最多可以在连接队列中放置 100 个客户端。

使用同步服务器套接字

同步服务器套接字挂起应用程序的执行,直到套接字上接收到连接请求。同步服务器套接字不适用于在操作中大量使用网络的应用程序,但它们可能适用于简单的网络应用程序。

使用 Bind 和 Listen 方法设置 Socket 以在终结点上侦听之后, Socket 就可以随时使用 Accept 方法接受传入的连接请求了。应用程序被挂起,直到调用 Accept 方法时接收到连接请求。

接收到连接请求时, ** Accept ** 返回一个与连接客户端关联的新 Socket 实例。下面的示例读取客户端数据,在控制台上显示该数据,然后将该数据回显到客户端。 Socket 不指定任何消息协议,因此字符串“

  1<eof>”标记消息数据的结尾。它假定名为  ` listener  ` 的 ** Socket  ** 实例已初始化并绑定到终结点。 
  2    
  3    
  4     [C#]
  5    
  6    
  7    Console.WriteLine("Waiting for a connection...");
  8    
  9    
 10    Socket handler = listener.Accept();
 11    
 12    
 13    String data = null;
 14    
 15    
 16     
 17    
 18    
 19    while (true) {
 20    
 21    
 22      bytes = new byte[1024];
 23    
 24    
 25      int bytesRec = handler.Receive(bytes);
 26    
 27    
 28      data += Encoding.ASCII.GetString(bytes,0,bytesRec);
 29    
 30    
 31      if (data.IndexOf("<eof>") &gt; -1) {
 32    
 33    
 34        break;
 35    
 36    
 37      }
 38    
 39    
 40    }
 41    
 42    
 43     
 44    
 45    
 46    Console.WriteLine( "Text received : {0}", data);
 47    
 48    
 49     
 50    
 51    
 52    byte[] msg = Encoding.ASCII.GetBytes(data);
 53    
 54    
 55    handler.Send(msg);
 56    
 57    
 58    handler.Shutdown(SocketShutdown.Both);
 59    
 60    
 61    handler.Close();
 62
 63###  使用异步服务器套接字 
 64
 65异步服务器套接字使用  .NET 框架异步编程模型处理网络服务请求。Socket 类遵循标准 .NET 异步命名模式;例如,同步 Accept 方法对应异步 BeginAccept 和 EndAccept 方法。 
 66
 67异步服务器套接字需要一个开始接受网络连接请求的方法,一个处理连接请求并开始接收网络数据的回调方法以及一个结束接收数据的回调方法。本节将进一步讨论所有这些方法。 
 68
 69在下面的示例中,为开始接受网络连接请求,方法  ` StartListening  ` 初始化 **Socket** ,然后使用 **BeginAccept** 方法开始接受新连接。当套接字上接收到新连接请求时调用接受回调方法。它负责获取将处理连接的 **Socket** 实例,并将 **Socket** 提交给将处理请求的线程。接受回调方法实现 AsyncCallback 委托;它返回 void,并带一个 IAsyncResult 类型的参数。下面的示例是接受回调方法的外壳程序。 
 70    
 71    
 72     [C#]
 73    
 74    
 75    void acceptCallback( IAsyncResult ar) {
 76    
 77    
 78      //Add the callback code here.
 79    
 80    
 81    }
 82
 83** BeginAccept  ** 方法带两个参数:指向接受回调方法的 **AsyncCallback** 委托和一个用于将状态信息传递给回调方法的对象。在下面的示例中,侦听 **Socket** 通过 **状态** 参数传递给回调方法。本示例创建一个 **AsyncCallback** 委托并开始接受网络连接。 
 84    
 85    
 86     [C#]
 87    
 88    
 89    listener.BeginAccept(
 90    
 91    
 92      new AsyncCallback(SocketListener.acceptCallback), 
 93    
 94    
 95      listener);
 96
 97异步套接字使用系统线程池中的线程处理传入的连接。一个线程负责接受连接,另一线程用于处理每个传入的连接,还有一个线程负责接收连接数据。这些线程可以是同一个线程,具体取决于线程池所分配的线程。在下面的示例中,  System.Threading.ManualResetEvent 类挂起主线程的执行并在执行可以继续时发出信号。 
 98
 99下面的示例显示在本地计算机上创建异步  TCP/IP 套接字并开始接受连接的异步方法。它假定以下内容:存在一个名为  ` allDone  ` 的全局 **ManualResetEvent** 实例,该方法是名为  ` SocketListener  ` 类的成员,以及定义了一个名为  ` acceptCallback  ` 的回调方法。 
100    
101    
102     [C#]
103    
104    
105    public void StartListening() {
106    
107    
108      IPHostEntry ipHostInfo = new Dns.Resolve(Dns.GetHostName());
109    
110    
111      IPEndPoint localEP = new IPEndPoint(ipHostInfo.AddressList[0],11000);
112    
113    
114     
115    
116    
117      Console.WriteLine("Local address and port : {0}",localEP.ToString());
118    
119    
120     
121    
122    
123      Socket listener = new Socket( localEP.Address.AddressFamily,
124    
125    
126        SocketType.Stream, ProtocolType.Tcp );
127    
128    
129     
130    
131    
132      try {
133    
134    
135        listener.Bind(localEP);
136    
137    
138        s.Listen(10);
139    
140    
141     
142    
143    
144        while (true) {
145    
146    
147          allDone.Reset();
148    
149    
150     
151    
152    
153          Console.WriteLine("Waiting for a connection...");
154    
155    
156          listener.BeginAccept(
157    
158    
159            new AsyncCallback(SocketListener.acceptCallback), 
160    
161    
162            listener );
163    
164    
165     
166    
167    
168          allDone.WaitOne();
169    
170    
171        }
172    
173    
174      } catch (Exception e) {
175    
176    
177        Console.WriteLine(e.ToString());
178    
179    
180      }
181    
182    
183     
184    
185    
186      Console.WriteLine( "Closing the listener...");
187    
188    
189    }
190
191接受回调方法(即前例中的  ` acceptCallback  ` )负责向主应用程序发出信号,让它继续执行处理、建立与客户端的连接并开始异步读取客户端数据。下面的示例是  ` acceptCallback  ` 方法实现的第一部分。该方法的此节向主应用程序线程发出信号,让它继续处理并建立与客户端的连接。它假定存在一个名为  ` allDone  ` 的全局 **ManualResetEvent** 实例。 
192    
193    
194     [C#]
195    
196    
197    public void acceptCallback(IAsyncResult ac) {
198    
199    
200      allDone.Set();
201    
202    
203     
204    
205    
206      Socket listener = (Socket) ar.AsyncState;
207    
208    
209      Socket handler = listener.EndAccept(ar);
210    
211    
212     
213    
214    
215      // Additional code to read data goes here.  
216    
217    
218    }
219
220从客户端套接字读取数据需要一个在异步调用之间传递值的状态对象。下面的示例实现一个用于从远程客户端接收字符串的状态对象。它包含以下各项的字段:客户端套接字,用于接收数据的数据缓冲区,以及用于创建客户端发送的数据字符串的  StringBuilder。将这些字段放在该状态对象中,使这些字段的值在多个调用之间得以保留,以便从客户端套接字读取数据。 
221    
222    
223     [C#]
224    
225    
226    public class StateObject {
227    
228    
229      public Socket workSocket = null;
230    
231    
232      public const int BufferSize = 1024;
233    
234    
235      public byte[] buffer = new byte[BufferSize];
236    
237    
238      public StringBuilder sb = new StringBuilder();
239    
240    
241    }
242
243开始从客户端套接字接收数据的  ` acceptCallback  ` 方法的此节首先初始化  ` StateObject  ` 类的一个实例,然后调用 BeginReceive 方法以开始从客户端套接字异步读取数据。 
244
245下面的示例显示了完整的  ` acceptCallback  ` 方法。它假定以下内容:存在一个名为  ` allDone  ` 的 **ManualResetEvent** 实例,定义了  ` StateObject  ` 类,以及在名为  ` SocketListener  ` 的类中定义了  ` readCallback  ` 方法。 
246    
247    
248     [C#]
249    
250    
251      public static void acceptCallback(IAsyncResult ar) {
252    
253    
254        // Get the socket that handles the client request.
255    
256    
257        Socket listener = (Socket) ar.AsyncState;
258    
259    
260        Socket handler = listener.EndAccept(ar);
261    
262    
263     
264    
265    
266        // Signal the main thread to continue.
267    
268    
269        allDone.Set();
270    
271    
272     
273    
274    
275        // Create the state object.
276    
277    
278        StateObject state = new StateObject();
279    
280    
281        state.workSocket = handler;
282    
283    
284        handler.BeginReceive( state.buffer, 0, StateObject.BufferSize, 0,
285    
286    
287          new AsyncCallback(AsynchronousSocketListener.readCallback), state);
288    
289    
290      }
291
292需要为异步套接字服务器实现的  final 方法是返回客户端发送的数据的读取回调方法。与接受回调方法一样,读取回调方法也是一个 **AsyncCallback** 委托。该方法将来自客户端套接字的一个或多个字节读入数据缓冲区,然后再次调用 **BeginReceive** 方法,直到客户端发送的数据完成为止。从客户端读取整个消息后,在控制台上显示字符串,并关闭处理与客户端的连接的服务器套接字。 
293
294下面的示例实现  ` readCallback  ` 方法。它假定定义了  ` StateObject  ` 类。 
295    
296    
297     [C#]
298    
299    
300    public void readCallback(IAsyncResult ar) {
301    
302    
303      StateObject state = (StateObject) ar.AsyncState;
304    
305    
306      Socket handler = state.WorkSocket;
307    
308    
309     
310    
311    
312      // Read data from the client socket.
313    
314    
315      int read = handler.EndReceive(ar);
316    
317    
318     
319    
320    
321      // Data was read from the client socket.
322    
323    
324      if (read &gt; 0) {
325    
326    
327        state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,read));
328    
329    
330        handler.BeginReceive(state.buffer,0,StateObject.BufferSize, 0,
331    
332    
333          new AsyncCallback(readCallback), state);
334    
335    
336      } else {
337    
338    
339        if (state.sb.Length &gt; 1) {
340    
341    
342          // All the data has been read from the client;
343    
344    
345          // display it on the console.
346    
347    
348          string content = state.sb.ToString();
349    
350    
351          Console.WriteLine("Read {0} bytes from socket.\n Data : {1}",
352    
353    
354            content.Length, content);
355    
356    
357        }
358    
359    
360        handler.Close();
361    
362    
363      }
364    
365    
366    }
367
368###  同步客户端套接字示例 
369
370下面的示例程序创建一个连接到服务器的客户端。该客户端是用同步套接字生成的,因此挂起客户端应用程序的执行,直到服务器返回响应为止。该应用程序将字符串发送到服务器,然后在控制台显示该服务器返回的字符串。 
371    
372    
373     [C#]
374    
375    
376    using System;
377    
378    
379    using System.Net;
380    
381    
382    using System.Net.Sockets;
383    
384    
385    using System.Text;
386    
387    
388     
389    
390    
391    public class SynchronousSocketClient {
392    
393    
394     
395    
396    
397      public static void StartClient() {
398    
399    
400        // Data buffer for incoming data.
401    
402    
403        byte[] bytes = new byte[1024];
404    
405    
406     
407    
408    
409        // Connect to a remote device.
410    
411    
412        try {
413    
414    
415          // Establish the remote endpoint for the socket.
416    
417    
418          //    The name of the
419    
420    
421          //   remote device is "host.contoso.com".
422    
423    
424          IPHostEntry ipHostInfo = Dns.Resolve("host.contoso.com");
425    
426    
427          IPAddress ipAddress = ipHostInfo.AddressList[0];
428    
429    
430          IPEndPoint remoteEP = new IPEndPoint(ipAddress,11000);
431    
432    
433     
434    
435    
436          // Create a TCP/IP  socket.
437    
438    
439          Socket sender = new Socket(AddressFamily.InterNetwork, 
440    
441    
442            SocketType.Stream, ProtocolType.Tcp );
443    
444    
445     
446    
447    
448          // Connect the socket to the remote endpoint. Catch any errors.
449    
450    
451          try {
452    
453    
454            sender.Connect(remoteEP);
455    
456    
457     
458    
459    
460            Console.WriteLine("Socket connected to {0}",
461    
462    
463              sender.RemoteEndPoint.ToString());
464    
465    
466     
467    
468    
469    &lt;FONT fa</eof></eof>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus