使用F#中的原始套接字編寫異步Ping以使用盡可能少的線程啓用並行請求。不使用「System.Net.NetworkInformation.Ping」,因爲它似乎爲每個請求分配一個線程。我也對使用F#異步工作流感興趣。如何檢測使用異步Socket.BeginReceive時的超時?
下面的同步版本正確超時目標主機不存在/響應,但異步版本掛起。兩者都在主機確實響應時工作。不知道這是一個.NET的問題,或一個F#之一...
任何想法?
(注:該過程必須以管理員身份運行,讓原始套接字的訪問)
這將引發超時:
let result = Ping.Ping (IPAddress.Parse("192.168.33.22"), 1000)
然而,這種掛起:
let result = Ping.AsyncPing (IPAddress.Parse("192.168.33.22"), 1000)
|> Async.RunSynchronously
下面的代碼...
module Ping
open System
open System.Net
open System.Net.Sockets
open System.Threading
//---- ICMP Packet Classes
type IcmpMessage (t : byte) =
let mutable m_type = t
let mutable m_code = 0uy
let mutable m_checksum = 0us
member this.Type
with get() = m_type
member this.Code
with get() = m_code
member this.Checksum = m_checksum
abstract Bytes : byte array
default this.Bytes
with get() =
[|
m_type
m_code
byte(m_checksum)
byte(m_checksum >>> 8)
|]
member this.GetChecksum() =
let mutable sum = 0ul
let bytes = this.Bytes
let mutable i = 0
// Sum up uint16s
while i < bytes.Length - 1 do
sum <- sum + uint32(BitConverter.ToUInt16(bytes, i))
i <- i + 2
// Add in last byte, if an odd size buffer
if i <> bytes.Length then
sum <- sum + uint32(bytes.[i])
// Shuffle the bits
sum <- (sum >>> 16) + (sum &&& 0xFFFFul)
sum <- sum + (sum >>> 16)
sum <- ~~~sum
uint16(sum)
member this.UpdateChecksum() =
m_checksum <- this.GetChecksum()
type InformationMessage (t : byte) =
inherit IcmpMessage(t)
let mutable m_identifier = 0us
let mutable m_sequenceNumber = 0us
member this.Identifier = m_identifier
member this.SequenceNumber = m_sequenceNumber
override this.Bytes
with get() =
Array.append (base.Bytes)
[|
byte(m_identifier)
byte(m_identifier >>> 8)
byte(m_sequenceNumber)
byte(m_sequenceNumber >>> 8)
|]
type EchoMessage() =
inherit InformationMessage(8uy)
let mutable m_data = Array.create 32 32uy
do base.UpdateChecksum()
member this.Data
with get() = m_data
and set(d) = m_data <- d
this.UpdateChecksum()
override this.Bytes
with get() =
Array.append (base.Bytes)
(this.Data)
//---- Synchronous Ping
let Ping (host : IPAddress, timeout : int) =
let mutable ep = new IPEndPoint(host, 0)
let socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, timeout)
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeout)
let packet = EchoMessage()
let mutable buffer = packet.Bytes
try
if socket.SendTo(buffer, ep) <= 0 then
raise (SocketException())
buffer <- Array.create (buffer.Length + 20) 0uy
let mutable epr = ep :> EndPoint
if socket.ReceiveFrom(buffer, &epr) <= 0 then
raise (SocketException())
finally
socket.Close()
buffer
//---- Entensions to the F# Async class to allow up to 5 paramters (not just 3)
type Async with
static member FromBeginEnd(arg1,arg2,arg3,arg4,beginAction,endAction,?cancelAction): Async<'T> =
Async.FromBeginEnd((fun (iar,state) -> beginAction(arg1,arg2,arg3,arg4,iar,state)), endAction, ?cancelAction=cancelAction)
static member FromBeginEnd(arg1,arg2,arg3,arg4,arg5,beginAction,endAction,?cancelAction): Async<'T> =
Async.FromBeginEnd((fun (iar,state) -> beginAction(arg1,arg2,arg3,arg4,arg5,iar,state)), endAction, ?cancelAction=cancelAction)
//---- Extensions to the Socket class to provide async SendTo and ReceiveFrom
type System.Net.Sockets.Socket with
member this.AsyncSendTo(buffer, offset, size, socketFlags, remoteEP) =
Async.FromBeginEnd(buffer, offset, size, socketFlags, remoteEP,
this.BeginSendTo,
this.EndSendTo)
member this.AsyncReceiveFrom(buffer, offset, size, socketFlags, remoteEP) =
Async.FromBeginEnd(buffer, offset, size, socketFlags, remoteEP,
this.BeginReceiveFrom,
(fun asyncResult -> this.EndReceiveFrom(asyncResult, remoteEP)))
//---- Asynchronous Ping
let AsyncPing (host : IPAddress, timeout : int) =
async {
let ep = IPEndPoint(host, 0)
use socket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp)
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, timeout)
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, timeout)
let packet = EchoMessage()
let outbuffer = packet.Bytes
try
let! result = socket.AsyncSendTo(outbuffer, 0, outbuffer.Length, SocketFlags.None, ep)
if result <= 0 then
raise (SocketException())
let epr = ref (ep :> EndPoint)
let inbuffer = Array.create (outbuffer.Length + 256) 0uy
let! result = socket.AsyncReceiveFrom(inbuffer, 0, inbuffer.Length, SocketFlags.None, epr)
if result <= 0 then
raise (SocketException())
return inbuffer
finally
socket.Close()
}
有要重新發明System.Net.NetworkInformation.Ping.SendAsync()什麼特別的原因?它已經支持超時。 – 2010-04-03 17:32:20
SendAsync/SendToAsync與上面的AsyncSendTo不一樣...前者不與F#異步工作流集成(極大地簡化了編寫異步代碼)。 – 2010-04-03 18:12:02
呃,重點是你不必寫它。 – 2010-04-03 18:44:15