44
55using System ;
66using System . Collections . Generic ;
7- using System . Diagnostics ;
87using System . Linq ;
98using System . Net ;
109using System . Net . Sockets ;
@@ -21,12 +20,10 @@ namespace FubarDev.FtpServer
2120 public class MultiBindingTcpListener
2221 {
2322 private readonly string ? _address ;
24-
2523 private readonly int _port ;
26-
2724 private readonly ILogger ? _logger ;
28- private readonly IList < TcpListener > _listeners = new List < TcpListener > ( ) ;
29- private readonly IList < Task < TcpClient ? > > _acceptors = new List < Task < TcpClient ? > > ( ) ;
25+ private Task < AcceptInfo > [ ] _acceptors = Array . Empty < Task < AcceptInfo > > ( ) ;
26+ private TcpListener [ ] _listeners = Array . Empty < TcpListener > ( ) ;
3027
3128 /// <summary>
3229 /// Initializes a new instance of the <see cref="MultiBindingTcpListener"/> class.
@@ -107,8 +104,8 @@ public void Stop()
107104 listener . Stop ( ) ;
108105 }
109106
110- _listeners . Clear ( ) ;
111- _acceptors . Clear ( ) ;
107+ _listeners = Array . Empty < TcpListener > ( ) ;
108+ _acceptors = Array . Empty < Task < AcceptInfo > > ( ) ;
112109 Port = 0 ;
113110 _logger ? . LogInformation ( "Listener stopped" ) ;
114111 }
@@ -120,46 +117,80 @@ public void Stop()
120117 /// <returns>The new TCP client.</returns>
121118 public async Task < TcpClient > WaitAnyTcpClientAsync ( CancellationToken token )
122119 {
120+ // The task that just waits indefinitely for a triggered cancellation token
121+ var cancellationTask = Task . Delay ( - 1 , token ) ;
122+
123+ // Build the list of awaitable tasks
124+ var tasks = new Task [ _acceptors . Length + 1 ] ;
125+ Array . Copy ( _acceptors , tasks , _acceptors . Length ) ;
126+
127+ // Add the cancellation task as last task
128+ tasks [ _acceptors . Length ] = cancellationTask ;
129+
123130 TcpClient ? result ;
124131 do
125132 {
126- var tasks = _acceptors . Cast < Task > ( ) . ToList ( ) ;
127- tasks . Add ( Task . Delay ( - 1 , token ) ) ;
133+ // Wait for any task to be finished
128134 var retVal = await Task . WhenAny ( tasks ) . ConfigureAwait ( false ) ;
135+
136+ // Test if the cancellation token was triggered
129137 token . ThrowIfCancellationRequested ( ) ;
130- var index = tasks . IndexOf ( retVal ) ;
131- _acceptors [ index ] = _listeners [ index ] . AcceptTcpClientAsync ( ) ;
132- result = ( ( Task < TcpClient ? > ) retVal ) . Result ;
138+
139+ // It was a listener task when the cancellation token was not triggered
140+ #if NETSTANDARD1_3
141+ var acceptInfo = await ( ( Task < AcceptInfo > ) retVal ) . ConfigureAwait ( false ) ;
142+ #else
143+ var acceptInfo = ( ( Task < AcceptInfo > ) retVal ) . Result ;
144+ retVal . Dispose ( ) ;
145+ #endif
146+
147+ // Avoid indexed access into the list of acceptors
148+ var index = acceptInfo . Index ;
149+
150+ // Gets the result of the finished task.
151+ result = acceptInfo . Client ;
152+
153+ // Start accepting the next TCP client for the
154+ // listener whose task was finished.
155+ var listener = _listeners [ index ] ;
156+ var newAcceptor = AcceptForListenerAsync ( listener , index ) ;
157+
158+ // Start accepting the next TCP client for the
159+ // listener whose task was finished.
160+ tasks [ index ] = _acceptors [ index ] = newAcceptor ;
133161 }
134162 while ( result == null ) ;
135163
136164 return result ;
137165 }
138166
139167 /// <summary>
140- /// Start the asynchronous acception for all listeners.
168+ /// Start the asynchronous accept operation for all listeners.
141169 /// </summary>
142170 public void StartAccepting ( )
143171 {
144- _listeners . ToList ( ) . ForEach ( x => _acceptors . Add ( AcceptForListenerAsync ( x ) ) ) ;
172+ _acceptors = _listeners . Select ( AcceptForListenerAsync ) . ToArray ( ) ;
145173 }
146174
147- private async Task < TcpClient ? > AcceptForListenerAsync ( TcpListener listener )
175+ private async Task < AcceptInfo > AcceptForListenerAsync ( TcpListener listener , int index )
148176 {
149177 try
150178 {
151- return await listener . AcceptTcpClientAsync ( ) . ConfigureAwait ( false ) ;
179+ var client = await listener . AcceptTcpClientAsync ( ) . ConfigureAwait ( false ) ;
180+ return new AcceptInfo ( client , index ) ;
152181 }
153182 catch ( ObjectDisposedException )
154183 {
155184 // Ignore the exception. This happens when the listener gets stopped.
156- return null ;
185+ return new AcceptInfo ( null , index ) ;
157186 }
158187 }
159188
160189 private int StartListening ( IEnumerable < IPAddress > addresses , int port )
161190 {
162191 var selectedPort = port ;
192+
193+ var listeners = new List < TcpListener > ( ) ;
163194 foreach ( var address in addresses )
164195 {
165196 var listener = new TcpListener ( address , selectedPort ) ;
@@ -172,10 +203,24 @@ private int StartListening(IEnumerable<IPAddress> addresses, int port)
172203
173204 _logger ? . LogInformation ( "Started listening on {address}:{port}" , address , selectedPort ) ;
174205
175- _listeners . Add ( listener ) ;
206+ listeners . Add ( listener ) ;
176207 }
177208
209+ _listeners = listeners . ToArray ( ) ;
210+
178211 return selectedPort ;
179212 }
213+
214+ private struct AcceptInfo
215+ {
216+ public AcceptInfo ( TcpClient ? client , int index )
217+ {
218+ Client = client ;
219+ Index = index ;
220+ }
221+
222+ public TcpClient ? Client { get ; }
223+ public int Index { get ; }
224+ }
180225 }
181226}
0 commit comments