我目前在F#中编写一个小型服务器类型,它基本上存储了与之有效的TCP连接。在它的循环中,它检查新连接,并使用以下代码检查当前连接是否仍然存在:
type TCPListenerServer(discoveryPort:int) =
let server = new TcpListener (IPAddress.Loopback, discoveryPort)
let activeConnections = new List<TcpClient>()
let cancellationToken = new CancellationTokenSource()
let connectionIsStillActive (client:TcpClient) =
let ipProperties = IPGlobalProperties.GetIPGlobalProperties ()
let allTcpConnections = ipProperties.GetActiveTcpConnections ()
let relevantTcpConnections = Array.filter (fun (connectionInfo:TcpConnectionInformation) ->
(connectionInfo.LocalEndPoint = (client.Client.LocalEndPoint :?> IPEndPoint)) && (connectionInfo.RemoteEndPoint = (client.Client.RemoteEndPoint :?> IPEndPoint))) allTcpConnections
try
let stateOfConnection = (Array.get relevantTcpConnections 0).State
match stateOfConnection with
| TcpState.Established ->
true
| _ ->
false
with
| :? System.IndexOutOfRangeException as ex ->
false
let rec loop (pendingConnection:Task<TcpClient>) = async {
let newPendingConnection, client =
match pendingConnection.Status with
| TaskStatus.Created | TaskStatus.WaitingForActivation | TaskStatus.WaitingToRun
| TaskStatus.WaitingForChildrenToComplete | TaskStatus.Running ->
(None, None)
| TaskStatus.Faulted ->
let result = pendingConnection.Exception
raise (new System.NotImplementedException())
| TaskStatus.Canceled ->
raise (new System.NotImplementedException())
| TaskStatus.RanToCompletion ->
let connectionTask = server.AcceptTcpClientAsync ()
(Some connectionTask, Some pendingConnection.Result)
| _ ->
raise (new System.NotImplementedException())
// Add the new client to the list
Option.iter (fun c -> activeConnections.Add c) client
// Switch the new pending connection if there is one
let connectionAttempt = defaultArg newPendingConnection pendingConnection
// Check that the connections are still alive
let connectionsToRemove = Seq.filter (fun (connection:TcpClient) -> not (connectionIsStillActive connection)) activeConnections
Seq.iter (fun connection -> activeConnections.Remove connection |> ignore) connectionsToRemove // <-- Exception happens here
Async.Sleep 1000 |> Async.RunSynchronously
return! loop connectionAttempt
}
member x.Start () =
if not cancellationToken.Token.CanBeCanceled
then
raise (new System.Exception("Cancellation token cannot be used to cancel server loop task."))
try
server.Start ()
let connectionTask = server.AcceptTcpClientAsync ()
Async.Start (loop connectionTask, cancellationToken.Token)
with
| :? SocketException as ex ->
server.Stop ()
raise ex
member x.Stop () =
cancellationToken.Cancel ()
Async.Sleep 2000 |> Async.RunSynchronously
server.Stop ()
activeConnections.Clear ()
member x.ActiveConnections =
activeConnections
为了看看这是否运作良好,我实施了这个简单的单元测试:
[<TestMethod>]
[<TestCategory(Networking)>]
member x.``Start Server, Client Connects, then Disconnects`` () =
let server = new TCPListenerServer(44000)
server.Start ()
let client = createClientAndConnect 44000
Async.Sleep 5000 |> Async.RunSynchronously
client.GetStream().Close()
client.Close()
Async.Sleep 5000 |> Async.RunSynchronously
Assert.IsTrue(server.ActiveConnections.Count = 0, "There are still connections in the server's active connections list.")
cleanupTest server (Some [client])
不幸的是,当我运行此测试时,一旦在客户端上调用Close()
,我就会收到以下异常:
System.InvalidOperationException:修改了集合;枚举操作可能无法执行。 在System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource资源) 在System.Collections.Generic.List
1.Enumerator.MoveNextRare() at System.Collections.Generic.List
1.Enumerator.MoveNext() 在Microsoft.FSharp.Collections.IEnumerator.next@224 [T](FSharpFunc2 f, IEnumerator
1 e,FSharpRef1 started, Unit unitVar0) at Microsoft.FSharp.Collections.IEnumerator.filter@219.System-Collections-IEnumerator-MoveNext() at Microsoft.FSharp.Collections.SeqModule.Iterate[T](FSharpFunc
2动作,IEnumerable1 source) at TCPListenerServer.clo@35.Invoke(Unit unitVar) in E:\Documents\Source Control Projects\WiDroid\Core.Networking\TCPListenerServer.fs:line 61 at Microsoft.FSharp.Control.AsyncBuilderImpl.callA@851.Invoke(AsyncParams
1 args)
我知道当您在迭代时修改集合时会发生此异常。但我不能直接在我的代码中这样做。我想知道为什么我得到这个例外?
答案 0 :(得分:3)
let connectionsToRemove = Seq.filter (fun (connection:TcpClient) -> not (connectionIsStillActive connection)) activeConnections
Seq.iter (fun connection -> activeConnections.Remove connection |> ignore) connectionsToRemove
我认为问题出在那里,Seq.filter
返回一个seq(IEnumerable<T>
的别名)。
代表的不是某种数据容器(如数组或列表),而是一种计算,它会在某些时候给出项目&#34;。
这意味着当你执行Seq.iter
时,它必须&#34;计算&#34; connectionsToRemove
有效地迭代activeConnections
。但是在同一个Seq.iter中,你要求从迭代集合中删除项目,因此错误。
如果您熟悉C#,那么代码可以看起来就行,然后您就会清楚地看到问题
foreach (TcpClient connection in activeConnections)
{
if (!connectionIsStillActive connection)
{
activeConnections.Remove (connection);
}
}
您可以通过拨打List<T>.RemoveAll
//doesn't seems to work directly (can't convert (TcpClient -> bool) to Predicate<TcpClient>)
//activeConnections.RemoveAll (not << connectionIsStillActive)
//this would work though
//activeConnections.RemoveAll (Predicate (not << connectionIsStillActive))
activeConnections.RemoveAll (fun connection -> not <| connectionIsStillActive connection)
|> ignore