diff options
Merge branch 'master' into vehicles
Diffstat (limited to 'OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs')
-rw-r--r-- | OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs | 420 |
1 files changed, 219 insertions, 201 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs index c356f02..545a0bc 100644 --- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs +++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs | |||
@@ -96,7 +96,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
96 | /// <summary>Incoming packets that are awaiting handling</summary> | 96 | /// <summary>Incoming packets that are awaiting handling</summary> |
97 | private OpenMetaverse.BlockingQueue<IncomingPacket> packetInbox = new OpenMetaverse.BlockingQueue<IncomingPacket>(); | 97 | private OpenMetaverse.BlockingQueue<IncomingPacket> packetInbox = new OpenMetaverse.BlockingQueue<IncomingPacket>(); |
98 | /// <summary></summary> | 98 | /// <summary></summary> |
99 | private UDPClientCollection clients = new UDPClientCollection(); | 99 | //private UDPClientCollection m_clients = new UDPClientCollection(); |
100 | /// <summary>Bandwidth throttle for this UDP server</summary> | 100 | /// <summary>Bandwidth throttle for this UDP server</summary> |
101 | private TokenBucket m_throttle; | 101 | private TokenBucket m_throttle; |
102 | /// <summary>Bandwidth throttle rates for this UDP server</summary> | 102 | /// <summary>Bandwidth throttle rates for this UDP server</summary> |
@@ -109,13 +109,19 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
109 | private Location m_location; | 109 | private Location m_location; |
110 | /// <summary>The measured resolution of Environment.TickCount</summary> | 110 | /// <summary>The measured resolution of Environment.TickCount</summary> |
111 | private float m_tickCountResolution; | 111 | private float m_tickCountResolution; |
112 | /// <summary>The size of the receive buffer for the UDP socket. This value | ||
113 | /// is passed up to the operating system and used in the system networking | ||
114 | /// stack. Use zero to leave this value as the default</summary> | ||
115 | private int m_recvBufferSize; | ||
116 | /// <summary>Flag to process packets asynchronously or synchronously</summary> | ||
117 | private bool m_asyncPacketHandling; | ||
112 | 118 | ||
113 | /// <summary>The measured resolution of Environment.TickCount</summary> | 119 | /// <summary>The measured resolution of Environment.TickCount</summary> |
114 | public float TickCountResolution { get { return m_tickCountResolution; } } | 120 | public float TickCountResolution { get { return m_tickCountResolution; } } |
115 | public Socket Server { get { return null; } } | 121 | public Socket Server { get { return null; } } |
116 | 122 | ||
117 | public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) | 123 | public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager) |
118 | : base((int)port) | 124 | : base(listenIP, (int)port) |
119 | { | 125 | { |
120 | #region Environment.TickCount Measurement | 126 | #region Environment.TickCount Measurement |
121 | 127 | ||
@@ -134,18 +140,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
134 | #endregion Environment.TickCount Measurement | 140 | #endregion Environment.TickCount Measurement |
135 | 141 | ||
136 | m_circuitManager = circuitManager; | 142 | m_circuitManager = circuitManager; |
143 | int sceneThrottleBps = 0; | ||
137 | 144 | ||
138 | // TODO: Config support for throttling the entire connection | 145 | IConfig config = configSource.Configs["ClientStack.LindenUDP"]; |
139 | m_throttle = new TokenBucket(null, 0, 0); | 146 | if (config != null) |
147 | { | ||
148 | m_asyncPacketHandling = config.GetBoolean("async_packet_handling", false); | ||
149 | m_recvBufferSize = config.GetInt("client_socket_rcvbuf_size", 0); | ||
150 | sceneThrottleBps = config.GetInt("scene_throttle_max_bps", 0); | ||
151 | } | ||
152 | |||
153 | m_throttle = new TokenBucket(null, sceneThrottleBps, sceneThrottleBps); | ||
140 | m_throttleRates = new ThrottleRates(configSource); | 154 | m_throttleRates = new ThrottleRates(configSource); |
141 | } | 155 | } |
142 | 156 | ||
143 | public new void Start() | 157 | public void Start() |
144 | { | 158 | { |
145 | if (m_scene == null) | 159 | if (m_scene == null) |
146 | throw new InvalidOperationException("Cannot LLUDPServer.Start() without an IScene reference"); | 160 | throw new InvalidOperationException("[LLUDPSERVER]: Cannot LLUDPServer.Start() without an IScene reference"); |
161 | |||
162 | m_log.Info("[LLUDPSERVER]: Starting the LLUDP server in " + (m_asyncPacketHandling ? "asynchronous" : "synchronous") + " mode"); | ||
147 | 163 | ||
148 | base.Start(); | 164 | base.Start(m_recvBufferSize, m_asyncPacketHandling); |
149 | 165 | ||
150 | // Start the incoming packet processing thread | 166 | // Start the incoming packet processing thread |
151 | Thread incomingThread = new Thread(IncomingPacketHandler); | 167 | Thread incomingThread = new Thread(IncomingPacketHandler); |
@@ -181,24 +197,6 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
181 | return x == m_location; | 197 | return x == m_location; |
182 | } | 198 | } |
183 | 199 | ||
184 | public void RemoveClient(IClientAPI client) | ||
185 | { | ||
186 | m_scene.ClientManager.Remove(client.CircuitCode); | ||
187 | client.Close(false); | ||
188 | |||
189 | LLUDPClient udpClient; | ||
190 | if (clients.TryGetValue(client.AgentId, out udpClient)) | ||
191 | { | ||
192 | m_log.Debug("[LLUDPSERVER]: Removing LLUDPClient for " + client.Name + " in " + m_scene.RegionInfo.RegionName); | ||
193 | udpClient.Shutdown(); | ||
194 | clients.Remove(client.AgentId, udpClient.RemoteEndPoint); | ||
195 | } | ||
196 | else | ||
197 | { | ||
198 | m_log.Warn("[LLUDPSERVER]: Failed to remove LLUDPClient for " + client.Name); | ||
199 | } | ||
200 | } | ||
201 | |||
202 | public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) | 200 | public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting) |
203 | { | 201 | { |
204 | // CoarseLocationUpdate packets cannot be split in an automated way | 202 | // CoarseLocationUpdate packets cannot be split in an automated way |
@@ -216,30 +214,29 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
216 | for (int i = 0; i < packetCount; i++) | 214 | for (int i = 0; i < packetCount; i++) |
217 | { | 215 | { |
218 | byte[] data = datas[i]; | 216 | byte[] data = datas[i]; |
219 | clients.ForEach( | 217 | m_scene.ClientManager.ForEach( |
220 | delegate(LLUDPClient client) | 218 | delegate(IClientAPI client) |
221 | { SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); }); | 219 | { |
220 | if (client is LLClientView) | ||
221 | SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category); | ||
222 | } | ||
223 | ); | ||
222 | } | 224 | } |
223 | } | 225 | } |
224 | else | 226 | else |
225 | { | 227 | { |
226 | byte[] data = packet.ToBytes(); | 228 | byte[] data = packet.ToBytes(); |
227 | clients.ForEach( | 229 | m_scene.ClientManager.ForEach( |
228 | delegate(LLUDPClient client) | 230 | delegate(IClientAPI client) |
229 | { SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); }); | 231 | { |
232 | if (client is LLClientView) | ||
233 | SendPacketData(((LLClientView)client).UDPClient, data, packet.Type, category); | ||
234 | } | ||
235 | ); | ||
230 | } | 236 | } |
231 | } | 237 | } |
232 | 238 | ||
233 | public void SendPacket(UUID agentID, Packet packet, ThrottleOutPacketType category, bool allowSplitting) | 239 | public void SendPacket(LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting) |
234 | { | ||
235 | LLUDPClient client; | ||
236 | if (clients.TryGetValue(agentID, out client)) | ||
237 | SendPacket(client, packet, category, allowSplitting); | ||
238 | else | ||
239 | m_log.Warn("[LLUDPSERVER]: Attempted to send a packet to unknown agentID " + agentID); | ||
240 | } | ||
241 | |||
242 | public void SendPacket(LLUDPClient client, Packet packet, ThrottleOutPacketType category, bool allowSplitting) | ||
243 | { | 240 | { |
244 | // CoarseLocationUpdate packets cannot be split in an automated way | 241 | // CoarseLocationUpdate packets cannot be split in an automated way |
245 | if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) | 242 | if (packet.Type == PacketType.CoarseLocationUpdate && allowSplitting) |
@@ -256,25 +253,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
256 | for (int i = 0; i < packetCount; i++) | 253 | for (int i = 0; i < packetCount; i++) |
257 | { | 254 | { |
258 | byte[] data = datas[i]; | 255 | byte[] data = datas[i]; |
259 | SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); | 256 | SendPacketData(udpClient, data, packet.Type, category); |
260 | } | 257 | } |
261 | } | 258 | } |
262 | else | 259 | else |
263 | { | 260 | { |
264 | byte[] data = packet.ToBytes(); | 261 | byte[] data = packet.ToBytes(); |
265 | SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); | 262 | SendPacketData(udpClient, data, packet.Type, category); |
266 | } | 263 | } |
267 | } | 264 | } |
268 | 265 | ||
269 | public void SendPacketData(LLUDPClient client, byte[] data, int dataLength, PacketType type, bool doZerocode, ThrottleOutPacketType category) | 266 | public void SendPacketData(LLUDPClient udpClient, byte[] data, PacketType type, ThrottleOutPacketType category) |
270 | { | 267 | { |
268 | int dataLength = data.Length; | ||
269 | bool doZerocode = (data[0] & Helpers.MSG_ZEROCODED) != 0; | ||
270 | |||
271 | // Frequency analysis of outgoing packet sizes shows a large clump of packets at each end of the spectrum. | 271 | // Frequency analysis of outgoing packet sizes shows a large clump of packets at each end of the spectrum. |
272 | // The vast majority of packets are less than 200 bytes, although due to asset transfers and packet splitting | 272 | // The vast majority of packets are less than 200 bytes, although due to asset transfers and packet splitting |
273 | // there are a decent number of packets in the 1000-1140 byte range. We allocate one of two sizes of data here | 273 | // there are a decent number of packets in the 1000-1140 byte range. We allocate one of two sizes of data here |
274 | // to accomodate for both common scenarios and provide ample room for ACK appending in both | 274 | // to accomodate for both common scenarios and provide ample room for ACK appending in both |
275 | int bufferSize = (dataLength > 180) ? Packet.MTU : 200; | 275 | int bufferSize = (dataLength > 180) ? Packet.MTU : 200; |
276 | 276 | ||
277 | UDPPacketBuffer buffer = new UDPPacketBuffer(client.RemoteEndPoint, bufferSize); | 277 | UDPPacketBuffer buffer = new UDPPacketBuffer(udpClient.RemoteEndPoint, bufferSize); |
278 | 278 | ||
279 | // Zerocode if needed | 279 | // Zerocode if needed |
280 | if (doZerocode) | 280 | if (doZerocode) |
@@ -285,17 +285,13 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
285 | // The packet grew larger than the bufferSize while zerocoding. | 285 | // The packet grew larger than the bufferSize while zerocoding. |
286 | // Remove the MSG_ZEROCODED flag and send the unencoded data | 286 | // Remove the MSG_ZEROCODED flag and send the unencoded data |
287 | // instead | 287 | // instead |
288 | m_log.Info("[LLUDPSERVER]: Packet exceeded buffer size during zerocoding. Removing MSG_ZEROCODED flag"); | 288 | m_log.Debug("[LLUDPSERVER]: Packet exceeded buffer size during zerocoding for " + type + ". Removing MSG_ZEROCODED flag"); |
289 | data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED); | 289 | data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED); |
290 | // | ||
291 | buffer = new UDPPacketBuffer(client.RemoteEndPoint, dataLength); | ||
292 | // | ||
293 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); | 290 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); |
294 | } | 291 | } |
295 | } | 292 | } |
296 | else | 293 | else |
297 | { | 294 | { |
298 | // ??? will it fit? | ||
299 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); | 295 | Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength); |
300 | } | 296 | } |
301 | buffer.DataLength = dataLength; | 297 | buffer.DataLength = dataLength; |
@@ -303,7 +299,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
303 | #region Queue or Send | 299 | #region Queue or Send |
304 | 300 | ||
305 | // Look up the UDPClient this is going to | 301 | // Look up the UDPClient this is going to |
306 | OutgoingPacket outgoingPacket = new OutgoingPacket(client, buffer, category); | 302 | OutgoingPacket outgoingPacket = new OutgoingPacket(udpClient, buffer, category); |
307 | 303 | ||
308 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket)) | 304 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket)) |
309 | SendPacketFinal(outgoingPacket); | 305 | SendPacketFinal(outgoingPacket); |
@@ -311,18 +307,18 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
311 | #endregion Queue or Send | 307 | #endregion Queue or Send |
312 | } | 308 | } |
313 | 309 | ||
314 | public void SendAcks(LLUDPClient client) | 310 | public void SendAcks(LLUDPClient udpClient) |
315 | { | 311 | { |
316 | uint ack; | 312 | uint ack; |
317 | 313 | ||
318 | if (client.PendingAcks.Dequeue(out ack)) | 314 | if (udpClient.PendingAcks.Dequeue(out ack)) |
319 | { | 315 | { |
320 | List<PacketAckPacket.PacketsBlock> blocks = new List<PacketAckPacket.PacketsBlock>(); | 316 | List<PacketAckPacket.PacketsBlock> blocks = new List<PacketAckPacket.PacketsBlock>(); |
321 | PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock(); | 317 | PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock(); |
322 | block.ID = ack; | 318 | block.ID = ack; |
323 | blocks.Add(block); | 319 | blocks.Add(block); |
324 | 320 | ||
325 | while (client.PendingAcks.Dequeue(out ack)) | 321 | while (udpClient.PendingAcks.Dequeue(out ack)) |
326 | { | 322 | { |
327 | block = new PacketAckPacket.PacketsBlock(); | 323 | block = new PacketAckPacket.PacketsBlock(); |
328 | block.ID = ack; | 324 | block.ID = ack; |
@@ -333,22 +329,39 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
333 | packet.Header.Reliable = false; | 329 | packet.Header.Reliable = false; |
334 | packet.Packets = blocks.ToArray(); | 330 | packet.Packets = blocks.ToArray(); |
335 | 331 | ||
336 | SendPacket(client, packet, ThrottleOutPacketType.Unknown, true); | 332 | SendPacket(udpClient, packet, ThrottleOutPacketType.Unknown, true); |
337 | } | 333 | } |
338 | } | 334 | } |
339 | 335 | ||
340 | public void SendPing(LLUDPClient client) | 336 | public void SendPing(LLUDPClient udpClient) |
341 | { | 337 | { |
342 | IClientAPI api = client.ClientAPI; | 338 | StartPingCheckPacket pc = (StartPingCheckPacket)PacketPool.Instance.GetPacket(PacketType.StartPingCheck); |
343 | if (api != null) | 339 | pc.Header.Reliable = false; |
344 | api.SendStartPingCheck(client.CurrentPingSequence++); | 340 | |
341 | OutgoingPacket oldestPacket = udpClient.NeedAcks.GetOldest(); | ||
342 | |||
343 | pc.PingID.PingID = (byte)udpClient.CurrentPingSequence++; | ||
344 | pc.PingID.OldestUnacked = (oldestPacket != null) ? oldestPacket.SequenceNumber : 0; | ||
345 | |||
346 | SendPacket(udpClient, pc, ThrottleOutPacketType.Unknown, false); | ||
345 | } | 347 | } |
346 | 348 | ||
347 | public void ResendUnacked(LLUDPClient client) | 349 | public void ResendUnacked(LLUDPClient udpClient) |
348 | { | 350 | { |
349 | if (client.NeedAcks.Count > 0) | 351 | if (udpClient.IsConnected && udpClient.NeedAcks.Count > 0) |
350 | { | 352 | { |
351 | List<OutgoingPacket> expiredPackets = client.NeedAcks.GetExpiredPackets(client.RTO); | 353 | // Disconnect an agent if no packets are received for some time |
354 | //FIXME: Make 60 an .ini setting | ||
355 | if (Environment.TickCount - udpClient.TickLastPacketReceived > 1000 * 60) | ||
356 | { | ||
357 | m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + udpClient.AgentID); | ||
358 | |||
359 | RemoveClient(udpClient); | ||
360 | return; | ||
361 | } | ||
362 | |||
363 | // Get a list of all of the packets that have been sitting unacked longer than udpClient.RTO | ||
364 | List<OutgoingPacket> expiredPackets = udpClient.NeedAcks.GetExpiredPackets(udpClient.RTO); | ||
352 | 365 | ||
353 | if (expiredPackets != null) | 366 | if (expiredPackets != null) |
354 | { | 367 | { |
@@ -357,54 +370,30 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
357 | { | 370 | { |
358 | OutgoingPacket outgoingPacket = expiredPackets[i]; | 371 | OutgoingPacket outgoingPacket = expiredPackets[i]; |
359 | 372 | ||
360 | // FIXME: Make this an .ini setting | 373 | //m_log.DebugFormat("[LLUDPSERVER]: Resending packet #{0} (attempt {1}), {2}ms have passed", |
361 | if (outgoingPacket.ResendCount < 3) | 374 | // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount); |
362 | { | ||
363 | //Logger.Debug(String.Format("Resending packet #{0} (attempt {1}), {2}ms have passed", | ||
364 | // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount)); | ||
365 | |||
366 | // Set the resent flag | ||
367 | outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT); | ||
368 | outgoingPacket.Category = ThrottleOutPacketType.Resend; | ||
369 | 375 | ||
370 | // The TickCount will be set to the current time when the packet | 376 | // Set the resent flag |
371 | // is actually sent out again | 377 | outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT); |
372 | outgoingPacket.TickCount = 0; | 378 | outgoingPacket.Category = ThrottleOutPacketType.Resend; |
373 | 379 | ||
374 | // Bump up the resend count on this packet | 380 | // The TickCount will be set to the current time when the packet |
375 | Interlocked.Increment(ref outgoingPacket.ResendCount); | 381 | // is actually sent out again |
376 | //Interlocked.Increment(ref Stats.ResentPackets); | 382 | outgoingPacket.TickCount = 0; |
377 | 383 | ||
378 | // Queue or (re)send the packet | 384 | // Bump up the resend count on this packet |
379 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket)) | 385 | Interlocked.Increment(ref outgoingPacket.ResendCount); |
380 | SendPacketFinal(outgoingPacket); | 386 | //Interlocked.Increment(ref Stats.ResentPackets); |
381 | } | ||
382 | else | ||
383 | { | ||
384 | m_log.DebugFormat("[LLUDPSERVER]: Dropping packet #{0} for agent {1} after {2} failed attempts", | ||
385 | outgoingPacket.SequenceNumber, outgoingPacket.Client.RemoteEndPoint, outgoingPacket.ResendCount); | ||
386 | 387 | ||
387 | lock (client.NeedAcks.SyncRoot) | 388 | // Requeue or resend the packet |
388 | client.NeedAcks.RemoveUnsafe(outgoingPacket.SequenceNumber); | 389 | if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket)) |
389 | 390 | SendPacketFinal(outgoingPacket); | |
390 | //Interlocked.Increment(ref Stats.DroppedPackets); | ||
391 | |||
392 | // Disconnect an agent if no packets are received for some time | ||
393 | //FIXME: Make 60 an .ini setting | ||
394 | if (Environment.TickCount - client.TickLastPacketReceived > 1000 * 60) | ||
395 | { | ||
396 | m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + client.ClientAPI.Name); | ||
397 | |||
398 | RemoveClient(client.ClientAPI); | ||
399 | return; | ||
400 | } | ||
401 | } | ||
402 | } | 391 | } |
403 | } | 392 | } |
404 | } | 393 | } |
405 | } | 394 | } |
406 | 395 | ||
407 | public void Flush() | 396 | public void Flush(LLUDPClient udpClient) |
408 | { | 397 | { |
409 | // FIXME: Implement? | 398 | // FIXME: Implement? |
410 | } | 399 | } |
@@ -415,7 +404,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
415 | byte flags = buffer.Data[0]; | 404 | byte flags = buffer.Data[0]; |
416 | bool isResend = (flags & Helpers.MSG_RESENT) != 0; | 405 | bool isResend = (flags & Helpers.MSG_RESENT) != 0; |
417 | bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0; | 406 | bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0; |
418 | LLUDPClient client = outgoingPacket.Client; | 407 | LLUDPClient udpClient = outgoingPacket.Client; |
408 | |||
409 | if (!udpClient.IsConnected) | ||
410 | return; | ||
419 | 411 | ||
420 | // Keep track of when this packet was sent out (right now) | 412 | // Keep track of when this packet was sent out (right now) |
421 | outgoingPacket.TickCount = Environment.TickCount; | 413 | outgoingPacket.TickCount = Environment.TickCount; |
@@ -424,11 +416,11 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
424 | 416 | ||
425 | int dataLength = buffer.DataLength; | 417 | int dataLength = buffer.DataLength; |
426 | 418 | ||
427 | // Keep appending ACKs until there is no room left in the packet or there are | 419 | // Keep appending ACKs until there is no room left in the buffer or there are |
428 | // no more ACKs to append | 420 | // no more ACKs to append |
429 | uint ackCount = 0; | 421 | uint ackCount = 0; |
430 | uint ack; | 422 | uint ack; |
431 | while (dataLength + 5 < buffer.Data.Length && client.PendingAcks.Dequeue(out ack)) | 423 | while (dataLength + 5 < buffer.Data.Length && udpClient.PendingAcks.Dequeue(out ack)) |
432 | { | 424 | { |
433 | Utils.UIntToBytesBig(ack, buffer.Data, dataLength); | 425 | Utils.UIntToBytesBig(ack, buffer.Data, dataLength); |
434 | dataLength += 4; | 426 | dataLength += 4; |
@@ -447,24 +439,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
447 | 439 | ||
448 | #endregion ACK Appending | 440 | #endregion ACK Appending |
449 | 441 | ||
442 | #region Sequence Number Assignment | ||
443 | |||
450 | if (!isResend) | 444 | if (!isResend) |
451 | { | 445 | { |
452 | // Not a resend, assign a new sequence number | 446 | // Not a resend, assign a new sequence number |
453 | uint sequenceNumber = (uint)Interlocked.Increment(ref client.CurrentSequence); | 447 | uint sequenceNumber = (uint)Interlocked.Increment(ref udpClient.CurrentSequence); |
454 | Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); | 448 | Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1); |
455 | outgoingPacket.SequenceNumber = sequenceNumber; | 449 | outgoingPacket.SequenceNumber = sequenceNumber; |
456 | 450 | ||
457 | if (isReliable) | 451 | if (isReliable) |
458 | { | 452 | { |
459 | // Add this packet to the list of ACK responses we are waiting on from the server | 453 | // Add this packet to the list of ACK responses we are waiting on from the server |
460 | client.NeedAcks.Add(outgoingPacket); | 454 | udpClient.NeedAcks.Add(outgoingPacket); |
461 | } | 455 | } |
462 | } | 456 | } |
463 | 457 | ||
458 | #endregion Sequence Number Assignment | ||
459 | |||
464 | // Stats tracking | 460 | // Stats tracking |
465 | Interlocked.Increment(ref client.PacketsSent); | 461 | Interlocked.Increment(ref udpClient.PacketsSent); |
466 | if (isReliable) | 462 | if (isReliable) |
467 | Interlocked.Add(ref client.UnackedBytes, outgoingPacket.Buffer.DataLength); | 463 | Interlocked.Add(ref udpClient.UnackedBytes, outgoingPacket.Buffer.DataLength); |
468 | 464 | ||
469 | // Put the UDP payload on the wire | 465 | // Put the UDP payload on the wire |
470 | AsyncBeginSend(buffer); | 466 | AsyncBeginSend(buffer); |
@@ -473,10 +469,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
473 | protected override void PacketReceived(UDPPacketBuffer buffer) | 469 | protected override void PacketReceived(UDPPacketBuffer buffer) |
474 | { | 470 | { |
475 | // Debugging/Profiling | 471 | // Debugging/Profiling |
476 | //try { Thread.CurrentThread.Name = "PacketReceived (" + scene.RegionName + ")"; } | 472 | //try { Thread.CurrentThread.Name = "PacketReceived (" + m_scene.RegionInfo.RegionName + ")"; } |
477 | //catch (Exception) { } | 473 | //catch (Exception) { } |
478 | 474 | ||
479 | LLUDPClient client = null; | 475 | LLUDPClient udpClient = null; |
480 | Packet packet = null; | 476 | Packet packet = null; |
481 | int packetEnd = buffer.DataLength - 1; | 477 | int packetEnd = buffer.DataLength - 1; |
482 | IPEndPoint address = (IPEndPoint)buffer.RemoteEndPoint; | 478 | IPEndPoint address = (IPEndPoint)buffer.RemoteEndPoint; |
@@ -491,61 +487,59 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
491 | } | 487 | } |
492 | catch (MalformedDataException) | 488 | catch (MalformedDataException) |
493 | { | 489 | { |
494 | m_log.ErrorFormat("[LLUDPSERVER]: Malformed data, cannot parse packet:\n{0}", | 490 | m_log.ErrorFormat("[LLUDPSERVER]: Malformed data, cannot parse packet from {0}:\n{1}", |
495 | Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)); | 491 | buffer.RemoteEndPoint, Utils.BytesToHexString(buffer.Data, buffer.DataLength, null)); |
496 | } | 492 | } |
497 | 493 | ||
498 | // Fail-safe check | 494 | // Fail-safe check |
499 | if (packet == null) | 495 | if (packet == null) |
500 | { | 496 | { |
501 | m_log.Warn("[LLUDPSERVER]: Couldn't build a message from the incoming data"); | 497 | m_log.Warn("[LLUDPSERVER]: Couldn't build a message from incoming data " + buffer.DataLength + |
498 | " bytes long from " + buffer.RemoteEndPoint); | ||
502 | return; | 499 | return; |
503 | } | 500 | } |
504 | 501 | ||
505 | //Stats.RecvBytes += (ulong)buffer.DataLength; | ||
506 | //++Stats.RecvPackets; | ||
507 | |||
508 | #endregion Decoding | 502 | #endregion Decoding |
509 | 503 | ||
510 | #region UseCircuitCode Handling | 504 | #region Packet to Client Mapping |
511 | 505 | ||
506 | // UseCircuitCode handling | ||
512 | if (packet.Type == PacketType.UseCircuitCode) | 507 | if (packet.Type == PacketType.UseCircuitCode) |
513 | { | 508 | { |
514 | UseCircuitCodePacket useCircuitCode = (UseCircuitCodePacket)packet; | 509 | AddNewClient((UseCircuitCodePacket)packet, (IPEndPoint)buffer.RemoteEndPoint); |
515 | IClientAPI newuser; | ||
516 | uint circuitCode = useCircuitCode.CircuitCode.Code; | ||
517 | |||
518 | // Check if the client is already established | ||
519 | if (!m_scene.ClientManager.TryGetClient(circuitCode, out newuser)) | ||
520 | { | ||
521 | AddNewClient(useCircuitCode, (IPEndPoint)buffer.RemoteEndPoint); | ||
522 | } | ||
523 | } | 510 | } |
524 | 511 | ||
525 | // Determine which agent this packet came from | 512 | // Determine which agent this packet came from |
526 | if (!clients.TryGetValue(address, out client)) | 513 | IClientAPI client; |
514 | if (!m_scene.ClientManager.TryGetValue(address, out client) || !(client is LLClientView)) | ||
527 | { | 515 | { |
528 | m_log.Warn("[LLUDPSERVER]: Received a " + packet.Type + " packet from an unrecognized source: " + address + " in " + m_scene.RegionInfo.RegionName); | 516 | m_log.Warn("[LLUDPSERVER]: Received a " + packet.Type + " packet from an unrecognized source: " + address + |
517 | " in " + m_scene.RegionInfo.RegionName + ", currently tracking " + m_scene.ClientManager.Count + " clients"); | ||
529 | return; | 518 | return; |
530 | } | 519 | } |
531 | 520 | ||
532 | #endregion UseCircuitCode Handling | 521 | udpClient = ((LLClientView)client).UDPClient; |
522 | |||
523 | if (!udpClient.IsConnected) | ||
524 | return; | ||
525 | |||
526 | #endregion Packet to Client Mapping | ||
533 | 527 | ||
534 | // Stats tracking | 528 | // Stats tracking |
535 | Interlocked.Increment(ref client.PacketsReceived); | 529 | Interlocked.Increment(ref udpClient.PacketsReceived); |
536 | 530 | ||
537 | #region ACK Receiving | 531 | #region ACK Receiving |
538 | 532 | ||
539 | int now = Environment.TickCount; | 533 | int now = Environment.TickCount; |
540 | client.TickLastPacketReceived = now; | 534 | udpClient.TickLastPacketReceived = now; |
541 | 535 | ||
542 | // Handle appended ACKs | 536 | // Handle appended ACKs |
543 | if (packet.Header.AppendedAcks && packet.Header.AckList != null) | 537 | if (packet.Header.AppendedAcks && packet.Header.AckList != null) |
544 | { | 538 | { |
545 | lock (client.NeedAcks.SyncRoot) | 539 | lock (udpClient.NeedAcks.SyncRoot) |
546 | { | 540 | { |
547 | for (int i = 0; i < packet.Header.AckList.Length; i++) | 541 | for (int i = 0; i < packet.Header.AckList.Length; i++) |
548 | AcknowledgePacket(client, packet.Header.AckList[i], now, packet.Header.Resent); | 542 | AcknowledgePacket(udpClient, packet.Header.AckList[i], now, packet.Header.Resent); |
549 | } | 543 | } |
550 | } | 544 | } |
551 | 545 | ||
@@ -554,10 +548,10 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
554 | { | 548 | { |
555 | PacketAckPacket ackPacket = (PacketAckPacket)packet; | 549 | PacketAckPacket ackPacket = (PacketAckPacket)packet; |
556 | 550 | ||
557 | lock (client.NeedAcks.SyncRoot) | 551 | lock (udpClient.NeedAcks.SyncRoot) |
558 | { | 552 | { |
559 | for (int i = 0; i < ackPacket.Packets.Length; i++) | 553 | for (int i = 0; i < ackPacket.Packets.Length; i++) |
560 | AcknowledgePacket(client, ackPacket.Packets[i].ID, now, packet.Header.Resent); | 554 | AcknowledgePacket(udpClient, ackPacket.Packets[i].ID, now, packet.Header.Resent); |
561 | } | 555 | } |
562 | } | 556 | } |
563 | 557 | ||
@@ -566,27 +560,27 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
566 | #region ACK Sending | 560 | #region ACK Sending |
567 | 561 | ||
568 | if (packet.Header.Reliable) | 562 | if (packet.Header.Reliable) |
569 | client.PendingAcks.Enqueue((uint)packet.Header.Sequence); | 563 | udpClient.PendingAcks.Enqueue(packet.Header.Sequence); |
570 | 564 | ||
571 | // This is a somewhat odd sequence of steps to pull the client.BytesSinceLastACK value out, | 565 | // This is a somewhat odd sequence of steps to pull the client.BytesSinceLastACK value out, |
572 | // add the current received bytes to it, test if 2*MTU bytes have been sent, if so remove | 566 | // add the current received bytes to it, test if 2*MTU bytes have been sent, if so remove |
573 | // 2*MTU bytes from the value and send ACKs, and finally add the local value back to | 567 | // 2*MTU bytes from the value and send ACKs, and finally add the local value back to |
574 | // client.BytesSinceLastACK. Lockless thread safety | 568 | // client.BytesSinceLastACK. Lockless thread safety |
575 | int bytesSinceLastACK = Interlocked.Exchange(ref client.BytesSinceLastACK, 0); | 569 | int bytesSinceLastACK = Interlocked.Exchange(ref udpClient.BytesSinceLastACK, 0); |
576 | bytesSinceLastACK += buffer.DataLength; | 570 | bytesSinceLastACK += buffer.DataLength; |
577 | if (bytesSinceLastACK > Packet.MTU * 2) | 571 | if (bytesSinceLastACK > Packet.MTU * 2) |
578 | { | 572 | { |
579 | bytesSinceLastACK -= Packet.MTU * 2; | 573 | bytesSinceLastACK -= Packet.MTU * 2; |
580 | SendAcks(client); | 574 | SendAcks(udpClient); |
581 | } | 575 | } |
582 | Interlocked.Add(ref client.BytesSinceLastACK, bytesSinceLastACK); | 576 | Interlocked.Add(ref udpClient.BytesSinceLastACK, bytesSinceLastACK); |
583 | 577 | ||
584 | #endregion ACK Sending | 578 | #endregion ACK Sending |
585 | 579 | ||
586 | #region Incoming Packet Accounting | 580 | #region Incoming Packet Accounting |
587 | 581 | ||
588 | // Check the archive of received reliable packet IDs to see whether we already received this packet | 582 | // Check the archive of received reliable packet IDs to see whether we already received this packet |
589 | if (packet.Header.Reliable && !client.PacketArchive.TryEnqueue(packet.Header.Sequence)) | 583 | if (packet.Header.Reliable && !udpClient.PacketArchive.TryEnqueue(packet.Header.Sequence)) |
590 | { | 584 | { |
591 | if (packet.Header.Resent) | 585 | if (packet.Header.Resent) |
592 | m_log.Debug("[LLUDPSERVER]: Received a resend of already processed packet #" + packet.Header.Sequence + ", type: " + packet.Type); | 586 | m_log.Debug("[LLUDPSERVER]: Received a resend of already processed packet #" + packet.Header.Sequence + ", type: " + packet.Type); |
@@ -603,7 +597,7 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
603 | if (packet.Type != PacketType.PacketAck) | 597 | if (packet.Type != PacketType.PacketAck) |
604 | { | 598 | { |
605 | // Inbox insertion | 599 | // Inbox insertion |
606 | packetInbox.Enqueue(new IncomingPacket(client, packet)); | 600 | packetInbox.Enqueue(new IncomingPacket(udpClient, packet)); |
607 | } | 601 | } |
608 | } | 602 | } |
609 | 603 | ||
@@ -623,53 +617,59 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
623 | 617 | ||
624 | private void AddNewClient(UseCircuitCodePacket useCircuitCode, IPEndPoint remoteEndPoint) | 618 | private void AddNewClient(UseCircuitCodePacket useCircuitCode, IPEndPoint remoteEndPoint) |
625 | { | 619 | { |
626 | //Slave regions don't accept new clients | 620 | UUID agentID = useCircuitCode.CircuitCode.ID; |
621 | UUID sessionID = useCircuitCode.CircuitCode.SessionID; | ||
622 | uint circuitCode = useCircuitCode.CircuitCode.Code; | ||
623 | |||
627 | if (m_scene.RegionStatus != RegionStatus.SlaveScene) | 624 | if (m_scene.RegionStatus != RegionStatus.SlaveScene) |
628 | { | 625 | { |
629 | AuthenticateResponse sessionInfo; | 626 | AuthenticateResponse sessionInfo; |
630 | bool isNewCircuit = !clients.ContainsKey(remoteEndPoint); | 627 | if (IsClientAuthorized(useCircuitCode, out sessionInfo)) |
631 | |||
632 | if (!IsClientAuthorized(useCircuitCode, out sessionInfo)) | ||
633 | { | 628 | { |
634 | m_log.WarnFormat( | 629 | AddClient(circuitCode, agentID, sessionID, remoteEndPoint, sessionInfo); |
635 | "[CONNECTION FAILURE]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}", | ||
636 | useCircuitCode.CircuitCode.ID, useCircuitCode.CircuitCode.Code, remoteEndPoint); | ||
637 | return; | ||
638 | } | 630 | } |
639 | 631 | else | |
640 | if (isNewCircuit) | ||
641 | { | 632 | { |
642 | UUID agentID = useCircuitCode.CircuitCode.ID; | 633 | // Don't create circuits for unauthorized clients |
643 | UUID sessionID = useCircuitCode.CircuitCode.SessionID; | 634 | m_log.WarnFormat( |
644 | uint circuitCode = useCircuitCode.CircuitCode.Code; | 635 | "[LLUDPSERVER]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}", |
645 | 636 | useCircuitCode.CircuitCode.ID, useCircuitCode.CircuitCode.Code, remoteEndPoint); | |
646 | AddClient(circuitCode, agentID, sessionID, remoteEndPoint, sessionInfo); | ||
647 | } | 637 | } |
648 | } | 638 | } |
639 | else | ||
640 | { | ||
641 | // Slave regions don't accept new clients | ||
642 | m_log.Debug("[LLUDPSERVER]: Slave region " + m_scene.RegionInfo.RegionName + " ignoring UseCircuitCode packet"); | ||
643 | } | ||
649 | } | 644 | } |
650 | 645 | ||
651 | private void AddClient(uint circuitCode, UUID agentID, UUID sessionID, IPEndPoint remoteEndPoint, AuthenticateResponse sessionInfo) | 646 | private void AddClient(uint circuitCode, UUID agentID, UUID sessionID, IPEndPoint remoteEndPoint, AuthenticateResponse sessionInfo) |
652 | { | 647 | { |
653 | // Create the LLUDPClient | 648 | // Create the LLUDPClient |
654 | LLUDPClient client = new LLUDPClient(this, m_throttleRates, m_throttle, circuitCode, agentID, remoteEndPoint); | 649 | LLUDPClient udpClient = new LLUDPClient(this, m_throttleRates, m_throttle, circuitCode, agentID, remoteEndPoint); |
655 | |||
656 | // Create the LLClientView | ||
657 | LLClientView clientApi = new LLClientView(remoteEndPoint, m_scene, this, client, sessionInfo, agentID, sessionID, circuitCode); | ||
658 | clientApi.OnViewerEffect += m_scene.ClientManager.ViewerEffectHandler; | ||
659 | clientApi.OnLogout += LogoutHandler; | ||
660 | clientApi.OnConnectionClosed += RemoveClient; | ||
661 | |||
662 | // Start the IClientAPI | ||
663 | m_scene.ClientManager.Add(circuitCode, clientApi); | ||
664 | clientApi.Start(); | ||
665 | 650 | ||
666 | // Give LLUDPClient a reference to IClientAPI | 651 | if (!m_scene.ClientManager.ContainsKey(agentID)) |
667 | client.ClientAPI = clientApi; | 652 | { |
653 | // Create the LLClientView | ||
654 | LLClientView client = new LLClientView(remoteEndPoint, m_scene, this, udpClient, sessionInfo, agentID, sessionID, circuitCode); | ||
655 | client.OnLogout += LogoutHandler; | ||
668 | 656 | ||
669 | // Add the new client to our list of tracked clients | 657 | // Start the IClientAPI |
670 | clients.Add(agentID, client.RemoteEndPoint, client); | 658 | client.Start(); |
659 | } | ||
660 | else | ||
661 | { | ||
662 | m_log.WarnFormat("[LLUDPSERVER]: Ignoring a repeated UseCircuitCode from {0} at {1} for circuit {2}", | ||
663 | udpClient.AgentID, remoteEndPoint, circuitCode); | ||
664 | } | ||
665 | } | ||
671 | 666 | ||
672 | m_log.DebugFormat("[LLUDPSERVER]: Added new client {0} to region {1}", agentID, m_scene.RegionInfo.RegionName); | 667 | private void RemoveClient(LLUDPClient udpClient) |
668 | { | ||
669 | // Remove this client from the scene | ||
670 | IClientAPI client; | ||
671 | if (m_scene.ClientManager.TryGetValue(udpClient.AgentID, out client)) | ||
672 | client.Close(); | ||
673 | } | 673 | } |
674 | 674 | ||
675 | private void AcknowledgePacket(LLUDPClient client, uint ack, int currentTime, bool fromResend) | 675 | private void AcknowledgePacket(LLUDPClient client, uint ack, int currentTime, bool fromResend) |
@@ -747,20 +747,28 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
747 | elapsed500MS = 0; | 747 | elapsed500MS = 0; |
748 | } | 748 | } |
749 | 749 | ||
750 | clients.ForEach( | 750 | m_scene.ClientManager.ForEach( |
751 | delegate(LLUDPClient client) | 751 | delegate(IClientAPI client) |
752 | { | 752 | { |
753 | if (client.DequeueOutgoing()) | 753 | if (client is LLClientView) |
754 | packetSent = true; | ||
755 | if (resendUnacked) | ||
756 | ResendUnacked(client); | ||
757 | if (sendAcks) | ||
758 | { | 754 | { |
759 | SendAcks(client); | 755 | LLUDPClient udpClient = ((LLClientView)client).UDPClient; |
760 | client.SendPacketStats(); | 756 | |
757 | if (udpClient.IsConnected) | ||
758 | { | ||
759 | if (udpClient.DequeueOutgoing()) | ||
760 | packetSent = true; | ||
761 | if (resendUnacked) | ||
762 | ResendUnacked(udpClient); | ||
763 | if (sendAcks) | ||
764 | { | ||
765 | SendAcks(udpClient); | ||
766 | udpClient.SendPacketStats(); | ||
767 | } | ||
768 | if (sendPings) | ||
769 | SendPing(udpClient); | ||
770 | } | ||
761 | } | 771 | } |
762 | if (sendPings) | ||
763 | SendPing(client); | ||
764 | } | 772 | } |
765 | ); | 773 | ); |
766 | 774 | ||
@@ -773,38 +781,48 @@ namespace OpenSim.Region.ClientStack.LindenUDP | |||
773 | { | 781 | { |
774 | IncomingPacket incomingPacket = (IncomingPacket)state; | 782 | IncomingPacket incomingPacket = (IncomingPacket)state; |
775 | Packet packet = incomingPacket.Packet; | 783 | Packet packet = incomingPacket.Packet; |
776 | LLUDPClient client = incomingPacket.Client; | 784 | LLUDPClient udpClient = incomingPacket.Client; |
785 | IClientAPI client; | ||
777 | 786 | ||
778 | // Sanity check | 787 | // Sanity check |
779 | if (packet == null || client == null || client.ClientAPI == null) | 788 | if (packet == null || udpClient == null) |
780 | { | 789 | { |
781 | m_log.WarnFormat("[LLUDPSERVER]: Processing a packet with incomplete state. Packet=\"{0}\", Client=\"{1}\", Client.ClientAPI=\"{2}\"", | 790 | m_log.WarnFormat("[LLUDPSERVER]: Processing a packet with incomplete state. Packet=\"{0}\", UDPClient=\"{1}\"", |
782 | packet, client, (client != null) ? client.ClientAPI : null); | 791 | packet, udpClient); |
783 | } | 792 | } |
784 | 793 | ||
785 | try | 794 | // Make sure this client is still alive |
786 | { | 795 | if (m_scene.ClientManager.TryGetValue(udpClient.AgentID, out client)) |
787 | // Process this packet | ||
788 | client.ClientAPI.ProcessInPacket(packet); | ||
789 | } | ||
790 | catch (ThreadAbortException) | ||
791 | { | 796 | { |
792 | // If something is trying to abort the packet processing thread, take that as a hint that it's time to shut down | 797 | try |
793 | m_log.Info("[LLUDPSERVER]: Caught a thread abort, shutting down the LLUDP server"); | 798 | { |
794 | Stop(); | 799 | // Process this packet |
800 | client.ProcessInPacket(packet); | ||
801 | } | ||
802 | catch (ThreadAbortException) | ||
803 | { | ||
804 | // If something is trying to abort the packet processing thread, take that as a hint that it's time to shut down | ||
805 | m_log.Info("[LLUDPSERVER]: Caught a thread abort, shutting down the LLUDP server"); | ||
806 | Stop(); | ||
807 | } | ||
808 | catch (Exception e) | ||
809 | { | ||
810 | // Don't let a failure in an individual client thread crash the whole sim. | ||
811 | m_log.ErrorFormat("[LLUDPSERVER]: Client packet handler for {0} for packet {1} threw an exception", udpClient.AgentID, packet.Type); | ||
812 | m_log.Error(e.Message, e); | ||
813 | } | ||
795 | } | 814 | } |
796 | catch (Exception e) | 815 | else |
797 | { | 816 | { |
798 | // Don't let a failure in an individual client thread crash the whole sim. | 817 | m_log.DebugFormat("[LLUDPSERVER]: Dropping incoming {0} packet for dead client {1}", packet.Type, udpClient.AgentID); |
799 | m_log.ErrorFormat("[LLUDPSERVER]: Client packet handler for {0} for packet {1} threw an exception", client.AgentID, packet.Type); | ||
800 | m_log.Error(e.Message, e); | ||
801 | } | 818 | } |
802 | } | 819 | } |
803 | 820 | ||
804 | private void LogoutHandler(IClientAPI client) | 821 | private void LogoutHandler(IClientAPI client) |
805 | { | 822 | { |
806 | client.SendLogoutPacket(); | 823 | client.SendLogoutPacket(); |
807 | RemoveClient(client); | 824 | if (client.IsActive) |
825 | RemoveClient(((LLClientView)client).UDPClient); | ||
808 | } | 826 | } |
809 | } | 827 | } |
810 | } | 828 | } |