.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简介的更多相关文章

  1. 封装Socket.BeginReceive/EndReceive以支持Timeout

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

  2. java.io.IOException: read failed, socket might closed or timeout, read ret: -1

    近期项目中连接蓝牙之后接收蓝牙设备发出的指令功能,在连接设备之后,创建RfcommSocket连接时候报java.io.IOException: read failed, socket might c ...

  3. C# socket编程实践——支持广播的简单socket服务器

    在上篇博客简单理解socket写完之后我就希望写出一个websocket的服务器了,但是一路困难重重,还是从基础开始吧,先搞定C# socket编程基本知识,写一个支持广播的简单server/clie ...

  4. java封装FFmpeg命令,支持原生ffmpeg全部命令,实现FFmpeg多进程处理与多线程输出控制(开启、关闭、查询),rtsp/rtmp推流、拉流

    前言: 之前已经对FFmpeg命令进行了封装http://blog.csdn.net/eguid_1/article/details/51787646,但是当时没有考虑到扩展性,所以总体设计不是太好, ...

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

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

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

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

  7. Azure支持docker简介以及使用指南

    Docker 是一个开源的项目,主要的特点是能将应用程序包装在一个 LXC (Linux Container) 容器中,当这些应用被包装进容器后,部署.迁移都变得更为简单.与传统的虚拟化技术相比,虚拟 ...

  8. java socket编程(一)简介

    #Java TCP Ip编程 其实下面几张图片就可以解释简单解释tcp-ip协议的大体流程了. ###计算机网络,分组报文和协议 网络是一组通过通信信道相互连接的机器组成. 组与组之间通过路由器连接 ...

  9. 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 ...

随机推荐

  1. 2014年企业改善IT风险管理的5个办法

    进入新的一年,企业面对数据泄密事故.日益复杂的攻击和对其控制的持续监管,现在是时候重新审视其风险管理战略了.虽然每个企业都是独特的,风险管理专家认为有些风险管理办法值得企业关注.下面我们列出了5个风险 ...

  2. 读书笔记 -part1

    自从毕业以后到现在~看的书是越来越少了 の其实好像貌似从来没有认认真真的看书  除非工作遇到难于解决的问题迫不得已才去翻书看 有些问题也是莫名其妙的就这样解决了  于是乎被人美名其曰“高人”或&quo ...

  3. C++Primer charpter1.

    一.输入输出流 endl:会刷新buffer.刷新之后你才能看到.不手动用endl的话,就只能依靠系统自动刷.程序崩溃的话,你看到的调试信息可能是错误的. >>:   两个连续的符号 ci ...

  4. java之多线程的理解

    线程的属性 (1)线程的状态     线程在它的生命周期中可能处于以下几种状态之一: New(新生):线程对象刚刚被创建出来: Runnable(可运行):在线程对象上调用start方法后,相应线程便 ...

  5. 深度围观block:第三集

    深度围观block:第三集 发布于:2013-07-12 10:09阅读数:7804 本文是深度围观block的第三篇文章,也是最后一篇.希望读者阅读了之后,对block有更加深入的理解,同时也希望之 ...

  6. 转:窗口启用/禁用功能函数EnableWindow的使用

    在非MFC环境中如何使控件或者窗口禁用呢?起初是想通过发送消息来实现,但找来找去都木有找到控件禁用的消息(也是是博主木有找到的缘故),所以只能另辟蹊径,使用 EnableWindow这个函数, 该函数 ...

  7. 用PYTHON实现将电脑里的所有文件按大小排序,便于清理

    嘿嘿,慢慢找到写代码的感觉了. 这个小程序涉及的东东还是很多的,数据结构的设计,错误的处理,快速字典排序,文件数值调整.... import os,os.path import glob SUFFIX ...

  8. 导出excel的三种方式

    第一种是Response输出,这种方式输出的文件不符合标准的excel格式,在打开的时候会有提示,而且不好控制内容.第一种是Response输出,这种方式输出的文件不符合标准的excel格式,在打开的 ...

  9. Altium Designer多图纸原理图设计方法探讨

    1 图纸结构 包括层次式图纸的连接关系是纵向的,也就是某一层次的图纸只能和相邻的上级或下级有关系,另一种即扁平式图纸的连接关系是横向的,任何两张图纸之间都可以建立信号连接. 2 网络连接方式 Alti ...

  10. Smarty for foreach 使用

    {for} {for}{forelse}用于创建一个简单的循环. 下面的几种方式都是支持的: {for $var=$start to $end}步长1的简单循环. {for $var=$start t ...