封装Socket.BeginReceive/EndReceive支持Timeout简介
.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操作的实现,方式如下:
1.基于APM异步模式封装Socket.BeginReceive/EndReceive方法。
2.使用ThreadPool提供的RegisterWaitForSingleObject()方法注册一个WaitOrTimerCallback,如果指定时间内操作未完成,则结束操作,并设置状态为Timeout。
3.将上述封装实现为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以及两个额外的方法:
1.SetResult(): 用于正常接收到消息时设置结果,标记操作完成以及执行回调。
2.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操作。
附示例代码:files.cnblogs.com/dytes/SocketAsyncOpWithTimeOut.zip
本文转自:http://www.csharpwin.com/csharpspace/13263r8436.shtml
封装Socket.BeginReceive/EndReceive支持Timeout简介的更多相关文章
- 封装Socket.BeginReceive/EndReceive以支持Timeout
Socket .NET中的Socket类提供了网络通信常用的方法,分别提供了同步和异步两个版本,其中异步的实现是基于APM异步模式实现,即BeginXXX/EndXXX的方式.异步方法由于其非阻塞的特 ...
- java.io.IOException: read failed, socket might closed or timeout, read ret: -1
近期项目中连接蓝牙之后接收蓝牙设备发出的指令功能,在连接设备之后,创建RfcommSocket连接时候报java.io.IOException: read failed, socket might c ...
- C# socket编程实践——支持广播的简单socket服务器
在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/clie ...
- java封装FFmpeg命令,支持原生ffmpeg全部命令,实现FFmpeg多进程处理与多线程输出控制(开启、关闭、查询),rtsp/rtmp推流、拉流
前言: 之前已经对FFmpeg命令进行了封装http://blog.csdn.net/eguid_1/article/details/51787646,但是当时没有考虑到扩展性,所以总体设计不是太好, ...
- 基于事件驱动的前端通信框架(封装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. ...
- Azure支持docker简介以及使用指南
Docker 是一个开源的项目,主要的特点是能将应用程序包装在一个 LXC (Linux Container) 容器中,当这些应用被包装进容器后,部署.迁移都变得更为简单.与传统的虚拟化技术相比,虚拟 ...
- java socket编程(一)简介
#Java TCP Ip编程 其实下面几张图片就可以解释简单解释tcp-ip协议的大体流程了. ###计算机网络,分组报文和协议 网络是一组通过通信信道相互连接的机器组成. 组与组之间通过路由器连接 ...
- PHP——封装Curl请求方法支持POST | DELETE | GET | PUT 等
前言 Curl: https://www.php.net/manual/en/book.curl.php curl_setopt: https://www.php.net/manual/en/fun ...
随机推荐
- 在MyEclipse中设置Source folders和output folder
在一个项目中可能会有多个资源文件,它们共同编译输出到输出文件.那么除了默认的src以外,如何把其他文件设置成资源文件(Source folders)呢?
- uva 10154 贪心+dp
题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...
- div边框阴影的实现【转载】
box-shadow:阴影水平偏移值(可取正负值): 阴影垂直偏移值(可取正负值):阴影模糊值:阴影颜色: Firefox支持Box Shadow(阴影):-moz-box-shadow:2px 2p ...
- nginx-301重定向开始
日常工作中使用301重定向的情况很多:如网页目录结构变动,网页重命名.网页的扩展名改变.网站域名改变.SEO优化.等等,301重定向可以很方便的使页面实现跳转. 参考 URL重写模块 一.首先更改配置 ...
- 【结构型】Facade模式
外观模式主要意图是为子系统提供一个统一的接口,从而使用用户对子系统的直接依赖中,解耦合出来.Facade主要是通过为子系统统一封装个入口一样,原先用户对子系统的接口.类等都是直接访问,现在要通过Fac ...
- Dom4j修改xml文档
1. 写出内容到xml文档 XMLWriter writer = new XMLWriter(OutputStream, OutputForamt) wirter.write(Document); 2 ...
- HTML5最佳实践
首先先给大家推荐个不错的 前端 网站:http://www.tystudio.net HTML5正迅速称为web前端开发技术标准,作为一名前段开发人员,了解并正确的使用HTML5制作网站变得越来越重要 ...
- 转:pthread_create()
http://blog.csdn.net/youbang321/article/details/7815707 原型:int pthread_create((pthread_t *thread, ...
- Vector3.Dot 与Vector3.Cross
Vector3.Dot(点积) : 点积的计算方式为: a·b=|a|·|b|cos<a,b>; 其中<a,b>和<b,a> 夹角不分顺序; 物理学中点积用来计算 ...
- Android进阶2之PopupWindow弹窗(有点悬浮窗的感觉)
PopupWindow是一个可以用来显示一个任意的视图的弹出窗口,他需要完全依赖layout布局. 它没什么界面,在弹出的窗口中完全显示布局中的控件. 上面两个美女头就是弹窗PopupWindow显示 ...