Tôi đang viết một ứng dụng điều khiển cho Logitech Media Server (trước đây gọi là Squeezebox Server).Xóa mã bắt buộc từ F # async dòng làm việc
Một phần nhỏ trong số đó là khám phá mà máy chủ đang chạy trên mạng nội bộ. Điều này được thực hiện bằng cách phát sóng một gói UDP đặc biệt đến cổng 3483 và chờ trả lời. Nếu không có máy chủ trả lời sau một thời gian nhất định, (hoặc một máy chủ ưa thích trả lời) ứng dụng sẽ ngừng nghe.
Tôi có nó làm việc trong C#, sử dụng async/chờ đợi tính năng của C# 5, nhưng tôi đã tò mò muốn xem làm thế nào nó sẽ tìm trong F #. Tôi có chức năng sau (ít nhiều được dịch trực tiếp từ C#):
let broadCast (timeout:TimeSpan) onServerDiscovered = async {
use udp = new UdpClient (EnableBroadcast = true)
let endPoint = new IPEndPoint(IPAddress.Broadcast, 3483)
let! _ = udp.SendAsync(discoveryPacket, discoveryPacket.Length, endPoint)
|> Async.AwaitTask
let timeoutTask = Task.Delay(timeout)
let finished = ref false
while not !finished do
let recvTask = udp.ReceiveAsync()
let! _ = Task.WhenAny(timeoutTask, recvTask) |> Async.AwaitTask
finished := if not recvTask.IsCompleted then true
else let udpResult = recvTask.Result
let hostName = udpResult.RemoteEndPoint.Address.ToString()
let serverName = udpResult.Buffer |> getServerName
onServerDiscovered serverName hostName 9090
}
discoveryPacket
là mảng byte chứa dữ liệu phát sóng. getServerName
là một hàm được định nghĩa ở nơi khác, trích xuất tên máy chủ có thể đọc được từ dữ liệu trả lời của máy chủ.
Vì vậy, ứng dụng gọi broadCast
với hai đối số, thời gian chờ và chức năng gọi lại sẽ được gọi khi máy chủ trả lời. Hàm gọi lại này sau đó có thể quyết định kết thúc nghe hay không, bằng cách trả về true hoặc false. Nếu không có máy chủ trả lời, hoặc không gọi lại trả về true, hàm trả về sau khi hết thời gian chờ.
Mã này hoạt động tốt, nhưng tôi mơ hồ bị làm phiền bởi việc sử dụng ô ref imperative finished
.
Vì vậy, đây là câu hỏi: liệu có một F # -y cách thành ngữ để làm điều này loại điều mà không chuyển sang mặt tối bắt buộc?
Cập nhật
Dựa trên câu trả lời chấp nhận bên dưới (mà đã rất gần bên phải), đây là chương trình thử nghiệm đầy đủ tôi đã kết thúc với:
open System
open System.Linq
open System.Text
open System.Net
open System.Net.Sockets
open System.Threading.Tasks
let discoveryPacket =
[| byte 'd'; 0uy; 2uy; 23uy; 0uy; 0uy; 0uy; 0uy;
0uy; 0uy; 0uy; 0uy; 0uy; 1uy; 2uy; 3uy; 4uy; 5uy |]
let getUTF8String data start length =
Encoding.UTF8.GetString(data, start, length)
let getServerName data =
data |> Seq.skip 1
|> Seq.takeWhile ((<) 0uy)
|> Seq.length
|> getUTF8String data 1
let broadCast (timeout : TimeSpan) onServerDiscovered = async {
use udp = new UdpClient (EnableBroadcast = true)
let endPoint = IPEndPoint (IPAddress.Broadcast, 3483)
do! udp.SendAsync (discoveryPacket, Array.length discoveryPacket, endPoint)
|> Async.AwaitTask
|> Async.Ignore
let timeoutTask = Task.Delay timeout
let rec loop() = async {
let recvTask = udp.ReceiveAsync()
do! Task.WhenAny(timeoutTask, recvTask)
|> Async.AwaitTask
|> Async.Ignore
if recvTask.IsCompleted then
let udpResult = recvTask.Result
let hostName = udpResult.RemoteEndPoint.Address.ToString()
let serverName = getServerName udpResult.Buffer
if onServerDiscovered serverName hostName 9090 then
return() // bailout signalled from callback
else
return! loop() // we should keep listening
}
return! loop()
}
[<EntryPoint>]
let main argv =
let serverDiscovered serverName hostName hostPort =
printfn "%s @ %s : %d" serverName hostName hostPort
false
let timeout = TimeSpan.FromSeconds(5.0)
broadCast timeout serverDiscovered |> Async.RunSynchronously
printfn "Done listening"
0 // return an integer exit code
Tại sao bạn không viết một vòng lặp vô hạn nhưng chạy nó sử dụng 'Async.RunSynchronously' quy định cụ thể một thời gian chờ 5 giây? –
@ Jon Harrop - bởi vì trong chương trình thực tế (đây chỉ là một ví dụ bare-bones) Tôi không muốn chạy việc khám phá cách đồng bộ. – corvuscorax