Socket

.NET中的Socket类提供了网络通信常用的方法,分别提供了同步和异步两个版本,其中异步的实现是基于APM异步模式实现,即BeginXXX/EndXXX的方式。异步方法由于其非阻塞的特性,在需考虑程序性能和伸缩性的情况下,一般会选择使用异步方法。但使用过Socket提供的异步方法的同学,应该都会注意到了Socket的异步方法是无法设置Timeout的。以Receive操作为例,Socket提供了一个ReceiveTimeout属性,但该属性设置的是同步版本的Socket.Receive()方法的Timeout值,该设置对异步的Socket.BeginReceive()无效:如果对方没有返回任何消息,则BeginReceive操作将无法完成,其中提供的回调函数也将不会调用。如下示例代码所示:

  1. private static void TestSocketBeginReceive()
  2. {
  3. Socket socket = new Socket(AddressFamily.InterNetwork,
  4. SocketType.Dgram, ProtocolType.Udp);
  5. byte[] content = Encoding.ASCII.GetBytes("Hello world");
  6.  
  7. IPAddress ip = Dns.Resolve("www.google.com").AddressList[0];
  8. IPEndPoint receiver = new IPEndPoint(ip, 80);
  9.  
  10. socket.BeginSendTo(content, 0, content.Length, SocketFlags.None,
  11. receiver, SendToCb, socket);
  12. Console.WriteLine("Sent bytes: " + content.Length);
  13. }
  14.  
  15. private static void SendToCb(IAsyncResult ar)
  16. {
  17. var socket = ar.AsyncState as Socket;
  18. socket.EndSendTo(ar);
  19. byte[] buffer = new byte[1024];
  20.  
  21. IAsyncResult receiveAr = socket.BeginReceive(buffer, 0, buffer.Length,
  22. SocketFlags.None, null, null);
  23. int received = socket.EndReceive(receiveAr);
  24. Console.WriteLine("Received bytes: " + received);
  25. }

由于接收方不会返回任何消息,Socket.BeginReceive将永远不会完成,SentToCb方法中的socket.EndReceive()调用将永远阻塞,应用程序也无法得知操作的状态。

支持Timeout

在个别的应用场景下,我们希望既能使用Socket的异步通信方法,保证程序的性能,同时又希望能指定Timeout值,当操作没有在指定的时间内完成时,应用程序能得到通知,以进行下一步的操作,如retry等。以下介绍的就是一种支持Timeout的Socket异步Receive操作的实现,方式如下:

  1. 基于APM异步模式封装Socket.BeginReceive/EndReceive方法。
  2. 使用ThreadPool提供的RegisterWaitForSingleObject()方法注册一个WaitOrTimerCallback,如果指定时间内操作未完成,则结束操作,并设置状态为Timeout。
  3. 将上述封装实现为Socket的扩展方法方便调用。

以下代码简化了所有的参数检查和异常处理,实际使用中需添加相关逻辑。

AsyncResultWithTimeout

首先看一下IAsyncResult接口的实现:

  1. public class AsyncResultWithTimeout : IAsyncResult
  2. {
  3. private ManualResetEvent m_waitHandle = new ManualResetEvent(false);
  4. public AsyncResultWithTimeout(AsyncCallback cb, object state)
  5. {
  6. this.AsyncState = state;
  7. this.Callback = cb;
  8. }
  9.  
  10. #region IAsyncResult
  11.  
  12. public object AsyncState { get; private set; }
  13. public WaitHandle AsyncWaitHandle { get { return m_waitHandle; } }
  14. public bool CompletedSynchronously { get { return false; } }
  15. public bool IsCompleted { get; private set; }
  16.  
  17. #endregion
  18.  
  19. public AsyncCallback Callback { get; private set; }
  20. public int ReceivedCount { get; private set; }
  21. public bool TimedOut { get; private set; }
  22. public void SetResult(int count)
  23. {
  24. this.IsCompleted = true;
  25. this.ReceivedCount = count;
  26. this.m_waitHandle.Set();
  27.  
  28. if (Callback != null) Callback(this);
  29. }
  30.  
  31. public void SetTimeout()
  32. {
  33. this.TimedOut = true;
  34. this.IsCompleted = true;
  35. this.m_waitHandle.Set();
  36. }
  37. }

AsyncResultWithTimeOut类中包含了IAsyncResult接口中4个属性的实现、用户传入的AsyncCallback委托、接收到的字节数ReceivedCount以及两个额外的方法:

  1. SetResult(): 用于正常接收到消息时设置结果,标记操作完成以及执行回调。
  2. SetTimeout():当超时时,标记操作完成以及设置超时状态。

StateInfo

StateInfo类用于保存相关的状态信息,该对象会作为Socket.BeginReceive()的最后一个参数传入。当接收到消息时,接收到的字节数会保存到AsyncResult属性中,并设置操作完成。当超时时,WatchTimeOut方法会将AsyncResult设置为TimeOut状态,并通过RegisteredWaitHandle属性取消注册的WaitOrTimerCallback.

  1. public class StateInfo
  2. {
  3. public StateInfo(AsyncResultWithTimeout result, Socket socket)
  4. {
  5. this.AsycResult = result;
  6. this.Socket = socket;
  7. }
  8.  
  9. public Socket Socket { get; private set; }
  10. public AsyncResultWithTimeout AsycResult { get; private set; }
  11. public RegisteredWaitHandle RegisteredWaitHandle { get; set; }
  12. }

封装Socket.BeginReceive

与Socket.BeginReceive方法相比,BeginReceive2添加了一个参数timeout,可以设置该操作的超时时间,单位为毫秒。BeginReceive2中调用Socket.BeginReceive()方法,其中指定的ReceiveCb回调将在正常接收到消息后将结果保存在stateInfo对象的AsyncResult属性中,该属性中的值就是BeginReceive2()方法返回的IAsyncResult。BeginReceive2调用Socket.BeginReceive后,在ThreadPool中注册了一个WaitOrTimerCallback委托。ThreadPool将在Receive操作完成或者Timeout时调用该委托。

  1. public static class SocketExtension
  2. {
  3.  
  4. public static int EndReceive2(IAsyncResult ar)
  5. {
  6. var result = ar as AsyncResultWithTimeout;
  7. result.AsyncWaitHandle.WaitOne();
  8.  
  9. return result.ReceivedCount;
  10. }
  11.  
  12. public static AsyncResultWithTimeout BeginReceive2
  13. (
  14. this Socket socket,
  15. int timeout,
  16. byte[] buffer,
  17. int offset,
  18. int size,
  19. SocketFlags flags,
  20. AsyncCallback callback,
  21. object state
  22. )
  23. {
  24. var result = new AsyncResultWithTimeout(callback, state);
  25.  
  26. var stateInfo = new StateInfo(result, socket);
  27.  
  28. socket.BeginReceive(buffer, offset, size, flags, ReceiveCb, state);
  29.  
  30. var registeredWaitHandle =
  31. ThreadPool.RegisterWaitForSingleObject(
  32. result.AsyncWaitHandle,
  33. WatchTimeOut,
  34. stateInfo, // 作为state传递给WatchTimeOut
  35. timeout,
  36. true);
  37.  
  38. // stateInfo中保存RegisteredWaitHandle,以方便在úWatchTimeOut
  39. // 中unregister.
  40. stateInfo.RegisteredWaitHandle = registeredWaitHandle;
  41.  
  42. return result;
  43. }
  44.  
  45. private static void WatchTimeOut(object state, bool timeout)
  46. {
  47. var stateInfo = state as StateInfo;
  48. // 设置的timeout前,操作未完成,则设置为操作Timeout
  49. if (timeout)
  50. {
  51. stateInfo.AsycResult.SetTimeout();
  52. }
  53.  
  54. // 取消之前注册的WaitOrTimerCallback
  55. stateInfo.RegisteredWaitHandle.Unregister(
  56. stateInfo.AsycResult.AsyncWaitHandle);
  57. }
  58.  
  59. private static void ReceiveCb(IAsyncResult result)
  60. {
  61. var state = result.AsyncState as StateInfo;
  62. var asyncResultWithTimeOut = state.AsycResult;
  63. var count = state.Socket.EndReceive(result);
  64. state.AsycResult.SetResult(count);
  65. }
  66. }

试一下

以下代码演示了如何使用BeginReceive2:

  1. private static void TestSocketBeginReceive2()
  2. {
  3. Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
  4. byte[] content = Encoding.ASCII.GetBytes("Hello world");
  5.  
  6. IPAddress ip = Dns.Resolve("www.google.com").AddressList[0];
  7. IPEndPoint receiver = new IPEndPoint(ip, 80);
  8.  
  9. socket.BeginSendTo(content, 0, content.Length, SocketFlags.None, receiver, SendToCb2, socket);
  10. Console.WriteLine("Sent bytes: " + content.Length);
  11. }
  12.  
  13. private static void SendToCb2(IAsyncResult ar)
  14. {
  15. var socket = ar.AsyncState as Socket;
  16. socket.EndSendTo(ar);
  17. byte[] buffer = new byte[1024];
  18.  
  19. AsyncResultWithTimeout receiveAr = socket.BeginReceive2(2000, buffer, 0, buffer.Length, SocketFlags.None, null, null);
  20. receiveAr.AsyncWaitHandle.WaitOne();
  21. if (receiveAr.TimedOut)
  22. {
  23. Console.WriteLine("Operation timed out.");
  24. }
  25. else
  26. {
  27. int received = socket.EndReceive(ar);
  28. Console.WriteLine("Received bytes: " + received);
  29. }
  30. }

输出结果如下:

上述实现是针对BeginReceive的封装,还可以以相同的方式将Send/Receive封装以支持Timeout, 或者更进一步支持retry操作。

附示例代码:下载

出处:http://www.cnblogs.com/dytes/archive/2012/08/13/SocketAsyncOpWithTimeout.html

封装Socket.BeginReceive/EndReceive以支持Timeout的更多相关文章

  1. 封装Socket.BeginReceive/EndReceive支持Timeout简介

    .NET中的Socket类提供了网络通信常用的方法,分别提供了同步和异步两个版本,其中异步的实现是基于APM异步模式实现,即BeginXXX/EndXXX的方式.异步方法由于其非阻塞的特性,在需考虑程 ...

  2. [转载]socket下server端支持多客户端并发访问简单实现

    /*Author: wainiwann *Source: 博客园 http://www.cnblogs.com/wainiwann *Remarks:  转载请说明出处!!! */ 感觉很不错,可以学 ...

  3. C Socket初探 - 加入多线程支持,限制最大接入客户端个数

    C Socket初探 - 加入多线程支持,限制最大接入客户端个数 先上一些多线程需要使用的函数定义: DWORD WINAPI ProcessClientRequests(LPVOID lpParam ...

  4. Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架

    一.基本介绍 WebSocket是HTML5的一种新通信协议,它实现了浏览器与服务器之间的双向通讯.而Socket.IO是一个完全由JavaScript实现.基于Node.js.支持WebSocket ...

  5. EF架构~真正被封装的排序方法,支持多列排序

    回到目录 对于linq to sql 和linq to entity来说,当你把获取数据的方法封装了之后,总觉得还缺点什么,想了之后,应该是排序,但看了微软的orchard项目之后,觉得它的排序封装的 ...

  6. 第六十二篇、AFN3.0封装网络请求框架,支持缓存

    1.网络请求 第一种实现方式: 功能:GET POST 请求 缓存逻辑: 1.是否要刷新本地缓存,不需要就直接发起无缓存的网络请求,否则直接读取本地数据 2.需要刷新本地缓存,先读取本地数据,有就返回 ...

  7. 基于事件驱动的前端通信框架(封装socket.io)

    socket.io的使用可以很轻松的实现websockets,兼容所有浏览器,提供实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验.但是在使用socket.io的过程中,由于业务需求需要同 ...

  8. [ActionScript 3.0] AS3.0 简单封装Socket的通信

    Socket服务器 package com.controls.socket { import com.models.events.AppEvent; import com.models.events. ...

  9. 夺命雷公狗—angularjs—15—内置封装好的计时器$interval和$timeout

    这里其实和js源生的效果是一样的,但是源生的在angularjs里面不能直接正常执行代码如下所示: <!DOCTYPE html> <html lang="en" ...

随机推荐

  1. 2018-2019-2 20165114《网络对抗技术》Exp5 MSF基础应用

    Exp5 MSF基础应用 目录 一.实验目标 二.基础问题回答 三.实验过程记录 3.1主动攻击实践 ms08_067+generic/shell reverse_tcp ms17_010_etern ...

  2. 伸展树基础(Splay)

    3224: Tyvj 1728 普通平衡树 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 3948  Solved: 1627 [Submit][St ...

  3. iOS日常学习 - iOS10上关于NSPhotoLibraryUsageDescription等问题

    最近升级了Xcode8.0,真是很多坑啊,填完一个来另外一个,今天又遇到了一个,用Xcode8.0上传项目时被驳回说是info.plist里面没有设置NSPhotoLibraryUsageDescri ...

  4. Spring Cloud2.0之Oauth2环境搭建(授权码模式和密码授权模式)

    oauth2 server 微服务授权中心,    github源码  https://github.com/spring-cloud/spring-cloud-security 对微服务接口做一些权 ...

  5. LeetCode——Add Strings

    LeetCode--Add Strings Question Given two non-negative integers num1 and num2 represented as string, ...

  6. ASCII_02_扩展

    1.来自“http://www.360doc.com/content/10/1007/22/3775569_59187136.shtml” 2. 3. 4. 5.

  7. java-四则运算二

    1.实验目的:是否有乘除法,括号,多项式运算. 2.思路:利用简单的循环switch语句进行循环输出随机数 3.程序源代码: package jiajianchengchu; import java. ...

  8. 【LABVIEW到C#】3》String的操作之Match Pattern Funtion.vi

    C#实现如下 using System; using System.Collections.Generic; using System.Text; using System.Text.RegularE ...

  9. echarts的散点图

    目前页面中有一个故障数据,做成散点图的效果,打算用echarts 来实现,国内的图表类其实选择挺多的,个人觉得echarts是比较好用的,来看看它有什么优点,一时中文的,联合百度地图,很多都对国内很友 ...

  10. 内存保护机制及绕过方案——通过覆盖虚函数表绕过/GS机制

    1    GS内存保护机制 1.1    GS工作原理 栈中的守护天使--GS,亦称作Stack Canary / Cookie,从VS2003起开始启用(也就说,GS机制是由编译器决定的,跟操作系统 ...