10
10
#endregion
11
11
12
12
using System ;
13
+ using System . Collections . Concurrent ;
13
14
using System . Collections . Generic ;
14
15
using System . IO ;
16
+ using System . Linq ;
17
+ using System . Net ;
15
18
using System . Net . Sockets ;
16
19
using System . Threading ;
17
20
using OpenRA . Server ;
@@ -30,12 +33,63 @@ public interface IConnection : IDisposable
30
33
{
31
34
int LocalClientId { get ; }
32
35
ConnectionState ConnectionState { get ; }
36
+ IPEndPoint EndPoint { get ; }
37
+ string ErrorMessage { get ; }
33
38
void Send ( int frame , List < byte [ ] > orders ) ;
34
39
void SendImmediate ( IEnumerable < byte [ ] > orders ) ;
35
40
void SendSync ( int frame , byte [ ] syncData ) ;
36
41
void Receive ( Action < int , byte [ ] > packetFn ) ;
37
42
}
38
43
44
+ public class ConnectionTarget
45
+ {
46
+ readonly DnsEndPoint [ ] endpoints ;
47
+
48
+ public ConnectionTarget ( )
49
+ {
50
+ endpoints = new [ ] { new DnsEndPoint ( "invalid" , 0 ) } ;
51
+ }
52
+
53
+ public ConnectionTarget ( string host , int port )
54
+ {
55
+ endpoints = new [ ] { new DnsEndPoint ( host , port ) } ;
56
+ }
57
+
58
+ public ConnectionTarget ( IEnumerable < DnsEndPoint > endpoints )
59
+ {
60
+ this . endpoints = endpoints . ToArray ( ) ;
61
+ if ( this . endpoints . Length == 0 )
62
+ {
63
+ throw new ArgumentException ( "ConnectionTarget must have at least one address." ) ;
64
+ }
65
+ }
66
+
67
+ public IEnumerable < IPEndPoint > GetConnectEndPoints ( )
68
+ {
69
+ return endpoints
70
+ . SelectMany ( e =>
71
+ {
72
+ try
73
+ {
74
+ return Dns . GetHostAddresses ( e . Host )
75
+ . Select ( a => new IPEndPoint ( a , e . Port ) ) ;
76
+ }
77
+ catch ( Exception )
78
+ {
79
+ return Enumerable . Empty < IPEndPoint > ( ) ;
80
+ }
81
+ } )
82
+ . ToList ( ) ;
83
+ }
84
+
85
+ public override string ToString ( )
86
+ {
87
+ return endpoints
88
+ . Select ( e => "{0}:{1}" . F ( e . Host , e . Port ) )
89
+ . JoinWith ( "/" ) ;
90
+ }
91
+ }
92
+
39
93
class EchoConnection : IConnection
40
94
{
41
95
protected struct ReceivedPacket
@@ -57,6 +111,16 @@ public virtual ConnectionState ConnectionState
57
111
get { return ConnectionState . PreConnecting ; }
58
112
}
59
113
114
+ public virtual IPEndPoint EndPoint
115
+ {
116
+ get { throw new NotSupportedException ( "An echo connection doesn't have an endpoint" ) ; }
117
+ }
118
+
119
+ public virtual string ErrorMessage
120
+ {
121
+ get { return null ; }
122
+ }
123
+
60
124
public virtual void Send ( int frame , List < byte [ ] > orders )
61
125
{
62
126
var ms = new MemoryStream ( ) ;
@@ -138,35 +202,100 @@ public void Dispose()
138
202
139
203
sealed class NetworkConnection : EchoConnection
140
204
{
141
- readonly TcpClient tcp ;
205
+ readonly ConnectionTarget target ;
206
+ TcpClient tcp ;
207
+ IPEndPoint endpoint ;
142
208
readonly List < byte [ ] > queuedSyncPackets = new List < byte [ ] > ( ) ;
143
209
volatile ConnectionState connectionState = ConnectionState . Connecting ;
144
210
volatile int clientId ;
145
211
bool disposed ;
212
+ string errorMessage ;
213
+
214
+ public override IPEndPoint EndPoint { get { return endpoint ; } }
146
215
147
- public NetworkConnection ( string host , int port )
216
+ public override string ErrorMessage { get { return errorMessage ; } }
217
+
218
+ public NetworkConnection ( ConnectionTarget target )
148
219
{
149
- try
220
+ this . target = target ;
221
+ new Thread ( NetworkConnectionConnect )
222
+ {
223
+ Name = "{0} (connect to {1})" . F ( GetType ( ) . Name , target ) ,
224
+ IsBackground = true
225
+ } . Start ( ) ;
226
+ }
227
+
228
+ void NetworkConnectionConnect ( )
229
+ {
230
+ var queue = new BlockingCollection < TcpClient > ( ) ;
231
+
232
+ var atLeastOneEndpoint = false ;
233
+ foreach ( var endpoint in target . GetConnectEndPoints ( ) )
234
+ {
235
+ atLeastOneEndpoint = true ;
236
+ new Thread ( ( ) =>
237
+ {
238
+ try
239
+ {
240
+ var client = new TcpClient ( endpoint . AddressFamily ) { NoDelay = true } ;
241
+ client . Connect ( endpoint . Address , endpoint . Port ) ;
242
+
243
+ try
244
+ {
245
+ queue . Add ( client ) ;
246
+ }
247
+ catch ( InvalidOperationException )
248
+ {
249
+ // Another connection was faster, close this one.
250
+ client . Close ( ) ;
251
+ }
252
+ }
253
+ catch ( Exception ex )
254
+ {
255
+ errorMessage = "Failed to connect to {0}" . F ( endpoint ) ;
256
+ Log . Write ( "client" , "Failed to connect to {0}: {1}" . F ( endpoint , ex . Message ) ) ;
257
+ }
258
+ } )
259
+ {
260
+ Name = "{0} (connect to {1})" . F ( GetType ( ) . Name , endpoint ) ,
261
+ IsBackground = true
262
+ } . Start ( ) ;
263
+ }
264
+
265
+ if ( ! atLeastOneEndpoint )
266
+ {
267
+ errorMessage = "Failed to resolve addresses for {0}" . F ( target ) ;
268
+ connectionState = ConnectionState . NotConnected ;
269
+ }
270
+
271
+ // Wait up to 5s for a successful connection. This should hopefully be enough because such high latency makes the game unplayable anyway.
272
+ else if ( queue . TryTake ( out tcp , 5000 ) )
150
273
{
151
- tcp = new TcpClient ( host , port ) { NoDelay = true } ;
274
+ // Copy endpoint here to have it even after getting disconnected.
275
+ endpoint = ( IPEndPoint ) tcp . Client . RemoteEndPoint ;
276
+
152
277
new Thread ( NetworkConnectionReceive )
153
278
{
154
- Name = GetType ( ) . Name + " " + host + ":" + port ,
279
+ Name = "{0} (receive from {1})" . F ( GetType ( ) . Name , tcp . Client . RemoteEndPoint ) ,
155
280
IsBackground = true
156
- } . Start ( tcp . GetStream ( ) ) ;
281
+ } . Start ( ) ;
157
282
}
158
- catch
283
+ else
159
284
{
160
285
connectionState = ConnectionState . NotConnected ;
161
286
}
287
+
288
+ // Close all unneeded connections in the queue and make sure new ones are closed on the connect thread.
289
+ queue . CompleteAdding ( ) ;
290
+ foreach ( var client in queue )
291
+ client . Close ( ) ;
162
292
}
163
293
164
- void NetworkConnectionReceive ( object networkStreamObject )
294
+ void NetworkConnectionReceive ( )
165
295
{
166
296
try
167
297
{
168
- var networkStream = ( NetworkStream ) networkStreamObject ;
169
- var reader = new BinaryReader ( networkStream ) ;
298
+ var reader = new BinaryReader ( tcp . GetStream ( ) ) ;
170
299
var handshakeProtocol = reader . ReadInt32 ( ) ;
171
300
172
301
if ( handshakeProtocol != ProtocolVersion . Handshake )
@@ -187,7 +316,11 @@ void NetworkConnectionReceive(object networkStreamObject)
187
316
AddPacket ( new ReceivedPacket { FromClient = client , Data = buf } ) ;
188
317
}
189
318
}
190
- catch { }
319
+ catch ( Exception ex )
320
+ {
321
+ errorMessage = "Connection to {0} failed" . F ( endpoint ) ;
322
+ Log . Write ( "client" , "Connection to {0} failed: {1}" . F ( endpoint , ex . Message ) ) ;
323
+ }
191
324
finally
192
325
{
193
326
connectionState = ConnectionState . NotConnected ;
0 commit comments