封装Socket.BeginReceive/EndReceive以支持Timeout
Socket
.NET中的Socket类提供了网络通信常用的方法,分别提供了同步和异步两个版本,其中异步的实现是基于APM异步模式实现,即BeginXXX/EndXXX的方式。异步方法由于其非阻塞的特性,在需考虑程序性能和伸缩性的情况下,一般会选择使用异步方法。但使用过Socket提供的异步方法的同学,应该都会注意到了Socket的异步方法是无法设置Timeout的。以Receive操作为例,Socket提供了一个ReceiveTimeout属性,但该属性设置的是同步版本的Socket.Receive()方法的Timeout值,该设置对异步的Socket.BeginReceive()无效:如果对方没有返回任何消息,则BeginReceive操作将无法完成,其中提供的回调函数也将不会调用。如下示例代码所示:
private static void TestSocketBeginReceive()
{
Socket socket = new Socket(AddressFamily.InterNetwork,
SocketType.Dgram, ProtocolType.Udp);
byte[] content = Encoding.ASCII.GetBytes("Hello world"); IPAddress ip = Dns.Resolve("www.google.com").AddressList[0];
IPEndPoint receiver = new IPEndPoint(ip, 80); socket.BeginSendTo(content, 0, content.Length, SocketFlags.None,
receiver, SendToCb, socket);
Console.WriteLine("Sent bytes: " + content.Length);
} private static void SendToCb(IAsyncResult ar)
{
var socket = ar.AsyncState as Socket;
socket.EndSendTo(ar);
byte[] buffer = new byte[1024]; IAsyncResult receiveAr = socket.BeginReceive(buffer, 0, buffer.Length,
SocketFlags.None, null, null);
int received = socket.EndReceive(receiveAr);
Console.WriteLine("Received bytes: " + received);
}
由于接收方不会返回任何消息,Socket.BeginReceive将永远不会完成,SentToCb方法中的socket.EndReceive()调用将永远阻塞,应用程序也无法得知操作的状态。
支持Timeout
在个别的应用场景下,我们希望既能使用Socket的异步通信方法,保证程序的性能,同时又希望能指定Timeout值,当操作没有在指定的时间内完成时,应用程序能得到通知,以进行下一步的操作,如retry等。以下介绍的就是一种支持Timeout的Socket异步Receive操作的实现,方式如下:
- 基于APM异步模式封装Socket.BeginReceive/EndReceive方法。
- 使用ThreadPool提供的RegisterWaitForSingleObject()方法注册一个WaitOrTimerCallback,如果指定时间内操作未完成,则结束操作,并设置状态为Timeout。
- 将上述封装实现为Socket的扩展方法方便调用。
以下代码简化了所有的参数检查和异常处理,实际使用中需添加相关逻辑。
AsyncResultWithTimeout
首先看一下IAsyncResult接口的实现:
public class AsyncResultWithTimeout : IAsyncResult
{
private ManualResetEvent m_waitHandle = new ManualResetEvent(false);
public AsyncResultWithTimeout(AsyncCallback cb, object state)
{
this.AsyncState = state;
this.Callback = cb;
} #region IAsyncResult public object AsyncState { get; private set; }
public WaitHandle AsyncWaitHandle { get { return m_waitHandle; } }
public bool CompletedSynchronously { get { return false; } }
public bool IsCompleted { get; private set; } #endregion public AsyncCallback Callback { get; private set; }
public int ReceivedCount { get; private set; }
public bool TimedOut { get; private set; }
public void SetResult(int count)
{
this.IsCompleted = true;
this.ReceivedCount = count;
this.m_waitHandle.Set(); if (Callback != null) Callback(this);
} public void SetTimeout()
{
this.TimedOut = true;
this.IsCompleted = true;
this.m_waitHandle.Set();
}
}
AsyncResultWithTimeOut类中包含了IAsyncResult接口中4个属性的实现、用户传入的AsyncCallback委托、接收到的字节数ReceivedCount以及两个额外的方法:
- SetResult(): 用于正常接收到消息时设置结果,标记操作完成以及执行回调。
- SetTimeout():当超时时,标记操作完成以及设置超时状态。
StateInfo
StateInfo类用于保存相关的状态信息,该对象会作为Socket.BeginReceive()的最后一个参数传入。当接收到消息时,接收到的字节数会保存到AsyncResult属性中,并设置操作完成。当超时时,WatchTimeOut方法会将AsyncResult设置为TimeOut状态,并通过RegisteredWaitHandle属性取消注册的WaitOrTimerCallback.
public class StateInfo
{
public StateInfo(AsyncResultWithTimeout result, Socket socket)
{
this.AsycResult = result;
this.Socket = socket;
} public Socket Socket { get; private set; }
public AsyncResultWithTimeout AsycResult { get; private set; }
public RegisteredWaitHandle RegisteredWaitHandle { get; set; }
}
封装Socket.BeginReceive
与Socket.BeginReceive方法相比,BeginReceive2添加了一个参数timeout,可以设置该操作的超时时间,单位为毫秒。BeginReceive2中调用Socket.BeginReceive()方法,其中指定的ReceiveCb回调将在正常接收到消息后将结果保存在stateInfo对象的AsyncResult属性中,该属性中的值就是BeginReceive2()方法返回的IAsyncResult。BeginReceive2调用Socket.BeginReceive后,在ThreadPool中注册了一个WaitOrTimerCallback委托。ThreadPool将在Receive操作完成或者Timeout时调用该委托。
public static class SocketExtension
{ public static int EndReceive2(IAsyncResult ar)
{
var result = ar as AsyncResultWithTimeout;
result.AsyncWaitHandle.WaitOne(); return result.ReceivedCount;
} public static AsyncResultWithTimeout BeginReceive2
(
this Socket socket,
int timeout,
byte[] buffer,
int offset,
int size,
SocketFlags flags,
AsyncCallback callback,
object state
)
{
var result = new AsyncResultWithTimeout(callback, state); var stateInfo = new StateInfo(result, socket); socket.BeginReceive(buffer, offset, size, flags, ReceiveCb, state); var registeredWaitHandle =
ThreadPool.RegisterWaitForSingleObject(
result.AsyncWaitHandle,
WatchTimeOut,
stateInfo, // 作为state传递给WatchTimeOut
timeout,
true); // stateInfo中保存RegisteredWaitHandle,以方便在úWatchTimeOut
// 中unregister.
stateInfo.RegisteredWaitHandle = registeredWaitHandle; return result;
} private static void WatchTimeOut(object state, bool timeout)
{
var stateInfo = state as StateInfo;
// 设置的timeout前,操作未完成,则设置为操作Timeout
if (timeout)
{
stateInfo.AsycResult.SetTimeout();
} // 取消之前注册的WaitOrTimerCallback
stateInfo.RegisteredWaitHandle.Unregister(
stateInfo.AsycResult.AsyncWaitHandle);
} private static void ReceiveCb(IAsyncResult result)
{
var state = result.AsyncState as StateInfo;
var asyncResultWithTimeOut = state.AsycResult;
var count = state.Socket.EndReceive(result);
state.AsycResult.SetResult(count);
}
}
试一下
以下代码演示了如何使用BeginReceive2:
private static void TestSocketBeginReceive2()
{
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
byte[] content = Encoding.ASCII.GetBytes("Hello world"); IPAddress ip = Dns.Resolve("www.google.com").AddressList[0];
IPEndPoint receiver = new IPEndPoint(ip, 80); socket.BeginSendTo(content, 0, content.Length, SocketFlags.None, receiver, SendToCb2, socket);
Console.WriteLine("Sent bytes: " + content.Length);
} private static void SendToCb2(IAsyncResult ar)
{
var socket = ar.AsyncState as Socket;
socket.EndSendTo(ar);
byte[] buffer = new byte[1024]; AsyncResultWithTimeout receiveAr = socket.BeginReceive2(2000, buffer, 0, buffer.Length, SocketFlags.None, null, null);
receiveAr.AsyncWaitHandle.WaitOne();
if (receiveAr.TimedOut)
{
Console.WriteLine("Operation timed out.");
}
else
{
int received = socket.EndReceive(ar);
Console.WriteLine("Received bytes: " + received);
}
}
输出结果如下:

上述实现是针对BeginReceive的封装,还可以以相同的方式将Send/Receive封装以支持Timeout, 或者更进一步支持retry操作。
附示例代码:下载
出处:http://www.cnblogs.com/dytes/archive/2012/08/13/SocketAsyncOpWithTimeout.html
封装Socket.BeginReceive/EndReceive以支持Timeout的更多相关文章
- 封装Socket.BeginReceive/EndReceive支持Timeout简介
.NET中的Socket类提供了网络通信常用的方法,分别提供了同步和异步两个版本,其中异步的实现是基于APM异步模式实现,即BeginXXX/EndXXX的方式.异步方法由于其非阻塞的特性,在需考虑程 ...
- [转载]socket下server端支持多客户端并发访问简单实现
/*Author: wainiwann *Source: 博客园 http://www.cnblogs.com/wainiwann *Remarks: 转载请说明出处!!! */ 感觉很不错,可以学 ...
- C Socket初探 - 加入多线程支持,限制最大接入客户端个数
C Socket初探 - 加入多线程支持,限制最大接入客户端个数 先上一些多线程需要使用的函数定义: DWORD WINAPI ProcessClientRequests(LPVOID lpParam ...
- Socket.IO介绍:支持WebSocket、用于WEB端的即时通讯的框架
一.基本介绍 WebSocket是HTML5的一种新通信协议,它实现了浏览器与服务器之间的双向通讯.而Socket.IO是一个完全由JavaScript实现.基于Node.js.支持WebSocket ...
- EF架构~真正被封装的排序方法,支持多列排序
回到目录 对于linq to sql 和linq to entity来说,当你把获取数据的方法封装了之后,总觉得还缺点什么,想了之后,应该是排序,但看了微软的orchard项目之后,觉得它的排序封装的 ...
- 第六十二篇、AFN3.0封装网络请求框架,支持缓存
1.网络请求 第一种实现方式: 功能:GET POST 请求 缓存逻辑: 1.是否要刷新本地缓存,不需要就直接发起无缓存的网络请求,否则直接读取本地数据 2.需要刷新本地缓存,先读取本地数据,有就返回 ...
- 基于事件驱动的前端通信框架(封装socket.io)
socket.io的使用可以很轻松的实现websockets,兼容所有浏览器,提供实时的用户体验,并且为程序员提供客户端与服务端一致的编程体验.但是在使用socket.io的过程中,由于业务需求需要同 ...
- [ActionScript 3.0] AS3.0 简单封装Socket的通信
Socket服务器 package com.controls.socket { import com.models.events.AppEvent; import com.models.events. ...
- 夺命雷公狗—angularjs—15—内置封装好的计时器$interval和$timeout
这里其实和js源生的效果是一样的,但是源生的在angularjs里面不能直接正常执行代码如下所示: <!DOCTYPE html> <html lang="en" ...
随机推荐
- C语言细节注意
前段时间用C语言写了个小的程序,也算是复习了下好久没有用的C语言.也是有好多的坑了,哈哈. 1.C语言的结构体 结构体的命名最好能够做到规范.因为不同的 编译环境下,不是很规范的命名有时候会导致莫名其 ...
- Shiro缓存使用Redis、Ehcache、自带的MpCache实现的三种方式实例
第一种:使用Redis做缓存,将数据存储到redis数据库中 第一步:在项目里面引入redis,配置文件如下: 配置文件:spring_shiro_redis.xml <?xml version ...
- 【bzoj5427】最长上升子序列(贪心+LIS)
题目传送门:https://www.lydsy.com/JudgeOnline/problem.php?id=5427 因为noip,博客咕了好久,这几天集中填一下坑. 这题我们可以假设往不确定的空位 ...
- springboot处理session生命周期
在使用springboot开发过程中发现用户登陆后60s后session就自动失效了,需要重新登陆,明明 application.yml 文件里已经配置了 server.session.timeou ...
- Ubuntu 16 安装redis客户端
https://snapcraft.io/redis-desktop-manager sudo snap install redis-desktop-manager 很好用! 支持模糊过滤,两边加星号 ...
- <<Hadoop基础教程》之初识Hadoop【转】
Hadoop一直是我想学习的技术,正巧最近项目组要做电子商城,我就开始研究Hadoop,虽然最后鉴定Hadoop不适用我们的项目,但是我会继续研究下去,技多不压身. <Hadoop基础教程> ...
- Selenium学习笔记(1) - 自动化测试体系与原理
技术体系 基于代码的测试(Code-Based Testing) 基于协议的测试(Protocol-Based Testing) 基于界面的测试(GUI-Based Testing) 工作原理 基于代 ...
- Ajax-06 Ajax数据交换格式
1.数据交换格式 服务端返回的数据,在本质上都是字符串,只是原生Ajax 或jQuery Ajax将这些字符串转换为容易理解的各种常用格式. a. Text 文本字符串 b. XML JavaScr ...
- 【Hive】窗口函数
我们都知道在sql中有一类函数叫做聚合函数,例如sum().avg().max()等等, 这类函数可以将多行数据按照规则聚集为一行,一般来讲聚集后的行数是要少于聚集前的行数的. 但是有时我们想要既显示 ...
- MVC 框架中的缓存
在程序中加入缓存的目的很多是为了提高程序的性能,提高数据的查找效率,在MVC框架中也引入了非常多的缓存,比如Controller的匹配查找,Controller,ControllerDescripto ...