aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs')
-rw-r--r--OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs1003
1 files changed, 557 insertions, 446 deletions
diff --git a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
index c779b08..7964c50 100644
--- a/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
+++ b/OpenSim/Region/ClientStack/LindenUDP/LLUDPServer.cs
@@ -26,616 +26,727 @@
26 */ 26 */
27 27
28using System; 28using System;
29using System.Collections;
30using System.Collections.Generic; 29using System.Collections.Generic;
31using System.Net; 30using System.Net;
32using System.Net.Sockets; 31using System.Net.Sockets;
33using System.Reflection; 32using System.Reflection;
33using System.Threading;
34using log4net; 34using log4net;
35using Nini.Config; 35using Nini.Config;
36using OpenMetaverse.Packets; 36using OpenMetaverse.Packets;
37using OpenSim.Framework; 37using OpenSim.Framework;
38using OpenSim.Region.Framework.Scenes; 38using OpenSim.Region.Framework.Scenes;
39using OpenMetaverse;
39 40
40namespace OpenSim.Region.ClientStack.LindenUDP 41namespace OpenSim.Region.ClientStack.LindenUDP
41{ 42{
42 /// <summary> 43 public class LLUDPServerShim : IClientNetworkServer
43 /// This class handles the initial UDP circuit setup with a client and passes on subsequent packets to the LLPacketServer
44 /// </summary>
45 public class LLUDPServer : ILLClientStackNetworkHandler, IClientNetworkServer
46 { 44 {
47 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 45 LLUDPServer m_udpServer;
48
49 /// <value>
50 /// The client circuits established with this UDP server. If a client exists here we can also assume that
51 /// it is populated in clientCircuits_reverse and proxyCircuits (if relevant)
52 /// </value>
53 protected Dictionary<EndPoint, uint> clientCircuits = new Dictionary<EndPoint, uint>();
54 public Hashtable clientCircuits_reverse = Hashtable.Synchronized(new Hashtable());
55 protected Dictionary<uint, EndPoint> proxyCircuits = new Dictionary<uint, EndPoint>();
56
57 private Socket m_socket;
58 protected IPEndPoint ServerIncoming;
59 protected byte[] RecvBuffer = new byte[4096];
60 protected byte[] ZeroBuffer = new byte[8192];
61
62 /// <value>
63 /// This is an endpoint that is reused where we don't need to protect the information from potentially
64 /// being stomped on by other threads.
65 /// </value>
66 protected EndPoint reusedEpSender = new IPEndPoint(IPAddress.Any, 0);
67
68 protected int proxyPortOffset;
69
70 protected AsyncCallback ReceivedData;
71 protected LLPacketServer m_packetServer;
72 protected Location m_location;
73
74 protected uint listenPort;
75 protected bool Allow_Alternate_Port;
76 protected IPAddress listenIP = IPAddress.Parse("0.0.0.0");
77 protected IScene m_localScene;
78 protected int m_clientSocketReceiveBuffer = 0;
79 46
80 /// <value> 47 public LLUDPServerShim()
81 /// Manages authentication for agent circuits
82 /// </value>
83 protected AgentCircuitManager m_circuitManager;
84
85 public IScene LocalScene
86 { 48 {
87 set
88 {
89 m_localScene = value;
90 m_packetServer.LocalScene = m_localScene;
91
92 m_location = new Location(m_localScene.RegionInfo.RegionHandle);
93 }
94 } 49 }
95 50
96 public ulong RegionHandle 51 public void Initialise(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager)
97 { 52 {
98 get { return m_location.RegionHandle; } 53 m_udpServer = new LLUDPServer(listenIP, ref port, proxyPortOffsetParm, allow_alternate_port, configSource, circuitManager);
99 } 54 }
100 55
101 Socket IClientNetworkServer.Server 56 public void NetworkStop()
102 { 57 {
103 get { return m_socket; } 58 m_udpServer.Stop();
104 } 59 }
105 60
106 public bool HandlesRegion(Location x) 61 public void AddScene(IScene scene)
107 { 62 {
108 //return x.RegionHandle == m_location.RegionHandle; 63 m_udpServer.AddScene(scene);
109 return x == m_location;
110 } 64 }
111 65
112 public void AddScene(IScene x) 66 public bool HandlesRegion(Location x)
113 { 67 {
114 LocalScene = x; 68 return m_udpServer.HandlesRegion(x);
115 } 69 }
116 70
117 public void Start() 71 public void Start()
118 { 72 {
119 ServerListener(); 73 m_udpServer.Start();
120 } 74 }
121 75
122 public void Stop() 76 public void Stop()
123 { 77 {
124 m_socket.Close(); 78 m_udpServer.Stop();
125 } 79 }
80 }
81
82 public class LLUDPServer : UDPBase
83 {
84 private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
126 85
127 public LLUDPServer() 86 /// <summary>Handlers for incoming packets</summary>
87 //PacketEventDictionary packetEvents = new PacketEventDictionary();
88 /// <summary>Incoming packets that are awaiting handling</summary>
89 private OpenMetaverse.BlockingQueue<IncomingPacket> packetInbox = new OpenMetaverse.BlockingQueue<IncomingPacket>();
90 /// <summary></summary>
91 private UDPClientCollection clients = new UDPClientCollection();
92 /// <summary>Bandwidth throttle for this UDP server</summary>
93 private TokenBucket m_throttle;
94 /// <summary>Bandwidth throttle rates for this UDP server</summary>
95 private ThrottleRates m_throttleRates;
96 /// <summary>Manages authentication for agent circuits</summary>
97 private AgentCircuitManager m_circuitManager;
98 /// <summary>Reference to the scene this UDP server is attached to</summary>
99 private IScene m_scene;
100 /// <summary>The X/Y coordinates of the scene this UDP server is attached to</summary>
101 private Location m_location;
102 /// <summary>The measured resolution of Environment.TickCount</summary>
103 private float m_tickCountResolution;
104
105 /// <summary>The measured resolution of Environment.TickCount</summary>
106 public float TickCountResolution { get { return m_tickCountResolution; } }
107 public Socket Server { get { return null; } }
108
109 public LLUDPServer(IPAddress listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource, AgentCircuitManager circuitManager)
110 : base((int)port)
128 { 111 {
112 #region Environment.TickCount Measurement
113
114 // Measure the resolution of Environment.TickCount
115 m_tickCountResolution = 0f;
116 for (int i = 0; i < 5; i++)
117 {
118 int start = Environment.TickCount;
119 int now = start;
120 while (now == start)
121 now = Environment.TickCount;
122 m_tickCountResolution += (float)(now - start) * 0.2f;
123 }
124 m_log.Info("[LLUDPSERVER]: Average Environment.TickCount resolution: " + TickCountResolution + "ms");
125
126 #endregion Environment.TickCount Measurement
127
128 m_circuitManager = circuitManager;
129
130 // TODO: Config support for throttling the entire connection
131 m_throttle = new TokenBucket(null, 0, 0);
132 m_throttleRates = new ThrottleRates(configSource);
129 } 133 }
130 134
131 public LLUDPServer( 135 public new void Start()
132 IPAddress _listenIP, ref uint port, int proxyPortOffset, bool allow_alternate_port, IConfigSource configSource,
133 AgentCircuitManager authenticateClass)
134 { 136 {
135 Initialise(_listenIP, ref port, proxyPortOffset, allow_alternate_port, configSource, authenticateClass); 137 if (m_scene == null)
138 throw new InvalidOperationException("Cannot LLUDPServer.Start() without an IScene reference");
139
140 base.Start();
141
142 // Start the incoming packet processing thread
143 Thread incomingThread = new Thread(IncomingPacketHandler);
144 incomingThread.Name = "Incoming Packets (" + m_scene.RegionInfo.RegionName + ")";
145 incomingThread.Start();
146
147 Thread outgoingThread = new Thread(OutgoingPacketHandler);
148 outgoingThread.Name = "Outgoing Packets (" + m_scene.RegionInfo.RegionName + ")";
149 outgoingThread.Start();
136 } 150 }
137 151
138 /// <summary> 152 public new void Stop()
139 /// Initialize the server
140 /// </summary>
141 /// <param name="_listenIP"></param>
142 /// <param name="port"></param>
143 /// <param name="proxyPortOffsetParm"></param>
144 /// <param name="allow_alternate_port"></param>
145 /// <param name="configSource"></param>
146 /// <param name="assetCache"></param>
147 /// <param name="circuitManager"></param>
148 public void Initialise(
149 IPAddress _listenIP, ref uint port, int proxyPortOffsetParm, bool allow_alternate_port, IConfigSource configSource,
150 AgentCircuitManager circuitManager)
151 { 153 {
152 ClientStackUserSettings userSettings = new ClientStackUserSettings(); 154 base.Stop();
153 155 }
154 IConfig config = configSource.Configs["ClientStack.LindenUDP"];
155 156
156 if (config != null) 157 public void AddScene(IScene scene)
158 {
159 if (m_scene == null)
157 { 160 {
158 if (config.Contains("client_throttle_max_bps")) 161 m_scene = scene;
159 { 162 m_location = new Location(m_scene.RegionInfo.RegionHandle);
160 int maxBPS = config.GetInt("client_throttle_max_bps", 1500000); 163 }
161 userSettings.TotalThrottleSettings = new ThrottleSettings(0, maxBPS, 164 else
162 maxBPS > 28000 ? maxBPS : 28000); 165 {
163 } 166 m_log.Error("[LLUDPSERVER]: AddScene() called on an LLUDPServer that already has a scene");
164 167 }
165 if (config.Contains("client_throttle_multiplier"))
166 userSettings.ClientThrottleMultipler = config.GetFloat("client_throttle_multiplier");
167 if (config.Contains("client_socket_rcvbuf_size"))
168 m_clientSocketReceiveBuffer = config.GetInt("client_socket_rcvbuf_size");
169 }
170
171 m_log.DebugFormat("[CLIENT]: client_throttle_multiplier = {0}", userSettings.ClientThrottleMultipler);
172 m_log.DebugFormat("[CLIENT]: client_socket_rcvbuf_size = {0}", (m_clientSocketReceiveBuffer != 0 ?
173 m_clientSocketReceiveBuffer.ToString() : "OS default"));
174
175 proxyPortOffset = proxyPortOffsetParm;
176 listenPort = (uint) (port + proxyPortOffsetParm);
177 listenIP = _listenIP;
178 Allow_Alternate_Port = allow_alternate_port;
179 m_circuitManager = circuitManager;
180 CreatePacketServer(userSettings);
181
182 // Return new port
183 // This because in Grid mode it is not really important what port the region listens to as long as it is correctly registered.
184 // So the option allow_alternate_ports="true" was added to default.xml
185 port = (uint)(listenPort - proxyPortOffsetParm);
186 } 168 }
187 169
188 protected virtual void CreatePacketServer(ClientStackUserSettings userSettings) 170 public bool HandlesRegion(Location x)
189 { 171 {
190 new LLPacketServer(this, userSettings); 172 return x == m_location;
191 } 173 }
192 174
193 /// <summary> 175 public void RemoveClient(IClientAPI client)
194 /// This method is called every time that we receive new UDP data.
195 /// </summary>
196 /// <param name="result"></param>
197 protected virtual void OnReceivedData(IAsyncResult result)
198 { 176 {
199 Packet packet = null; 177 m_scene.ClientManager.Remove(client.CircuitCode);
200 int numBytes = 1; 178 client.Close(false);
201 EndPoint epSender = new IPEndPoint(IPAddress.Any, 0);
202 EndPoint epProxy = null;
203 179
204 try 180 LLUDPClient udpClient;
181 if (clients.TryGetValue(client.AgentId, out udpClient))
205 { 182 {
206 if (EndReceive(out numBytes, result, ref epSender)) 183 m_log.Debug("[LLUDPSERVER]: Removing LLUDPClient for " + client.Name);
207 { 184 udpClient.Shutdown();
208 // Make sure we are getting zeroes when running off the 185 clients.Remove(client.AgentId, udpClient.RemoteEndPoint);
209 // end of grab / degrab packets from old clients
210 Array.Clear(RecvBuffer, numBytes, RecvBuffer.Length - numBytes);
211
212 int packetEnd = numBytes - 1;
213 if (proxyPortOffset != 0) packetEnd -= 6;
214
215 try
216 {
217 packet = PacketPool.Instance.GetPacket(RecvBuffer, ref packetEnd, ZeroBuffer);
218 }
219 catch (MalformedDataException e)
220 {
221 m_log.DebugFormat("[CLIENT]: Dropped Malformed Packet due to MalformedDataException: {0}", e.StackTrace);
222 }
223 catch (IndexOutOfRangeException e)
224 {
225 m_log.DebugFormat("[CLIENT]: Dropped Malformed Packet due to IndexOutOfRangeException: {0}", e.StackTrace);
226 }
227 catch (Exception e)
228 {
229 m_log.Debug("[CLIENT]: " + e);
230 }
231 }
232
233
234 if (proxyPortOffset != 0)
235 {
236 // If we've received a use circuit packet, then we need to decode an endpoint proxy, if one exists,
237 // before allowing the RecvBuffer to be overwritten by the next packet.
238 if (packet != null && packet.Type == PacketType.UseCircuitCode)
239 {
240 epProxy = epSender;
241 }
242
243 // Now decode the message from the proxy server
244 epSender = ProxyCodec.DecodeProxyMessage(RecvBuffer, ref numBytes);
245 }
246 } 186 }
247 catch (Exception ex) 187 else
248 { 188 {
249 m_log.ErrorFormat("[CLIENT]: Exception thrown during EndReceive(): {0}", ex); 189 m_log.Warn("[LLUDPSERVER]: Failed to remove LLUDPClient for " + client.Name);
250 } 190 }
191 }
251 192
252 BeginRobustReceive(); 193 public void RemoveClient(LLUDPClient udpClient)
194 {
195 IClientAPI client;
196
197 if (m_scene.ClientManager.TryGetClient(udpClient.CircuitCode, out client))
198 RemoveClient(client);
199 else
200 m_log.Warn("[LLUDPSERVER]: Failed to lookup IClientAPI for LLUDPClient " + udpClient.AgentID);
201 }
253 202
254 if (packet != null) 203 public void SetClientPaused(UUID agentID, bool paused)
204 {
205 LLUDPClient client;
206 if (clients.TryGetValue(agentID, out client))
255 { 207 {
256 if (packet.Type == PacketType.UseCircuitCode) 208 client.IsPaused = paused;
257 AddNewClient((UseCircuitCodePacket)packet, epSender, epProxy); 209 }
258 else 210 else
259 ProcessInPacket(packet, epSender); 211 {
212 m_log.Warn("[LLUDPSERVER]: Attempted to pause/unpause unknown agent " + agentID);
260 } 213 }
261 } 214 }
262 215
263 /// <summary> 216 public void BroadcastPacket(Packet packet, ThrottleOutPacketType category, bool sendToPausedAgents, bool allowSplitting)
264 /// Process a successfully received packet.
265 /// </summary>
266 /// <param name="packet"></param>
267 /// <param name="epSender"></param>
268 protected virtual void ProcessInPacket(Packet packet, EndPoint epSender)
269 { 217 {
270 try 218 if (allowSplitting && packet.HasVariableBlocks)
271 { 219 {
272 // do we already have a circuit for this endpoint 220 byte[][] datas = packet.ToBytesMultiple();
273 uint circuit; 221 int packetCount = datas.Length;
274 bool ret;
275
276 lock (clientCircuits)
277 {
278 ret = clientCircuits.TryGetValue(epSender, out circuit);
279 }
280 222
281 if (ret) 223 if (packetCount > 1)
282 { 224 m_log.Debug("[LLUDPSERVER]: Split " + packet.Type + " packet into " + packetCount + " packets");
283 //if so then send packet to the packetserver
284 //m_log.DebugFormat(
285 // "[UDPSERVER]: For circuit {0} {1} got packet {2}", circuit, epSender, packet.Type);
286 225
287 m_packetServer.InPacket(circuit, packet); 226 for (int i = 0; i < packetCount; i++)
227 {
228 byte[] data = datas[i];
229 clients.ForEach(
230 delegate(LLUDPClient client)
231 { SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); });
288 } 232 }
289 } 233 }
290 catch (Exception e) 234 else
291 { 235 {
292 m_log.Error("[CLIENT]: Exception in processing packet - ignoring: ", e); 236 byte[] data = packet.ToBytes();
237 clients.ForEach(
238 delegate(LLUDPClient client)
239 { SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category); });
293 } 240 }
294 } 241 }
295 242
296 /// <summary> 243 public void SendPacket(UUID agentID, Packet packet, ThrottleOutPacketType category, bool allowSplitting)
297 /// Begin an asynchronous receive of the next bit of raw data
298 /// </summary>
299 protected virtual void BeginReceive()
300 { 244 {
301 m_socket.BeginReceiveFrom( 245 LLUDPClient client;
302 RecvBuffer, 0, RecvBuffer.Length, SocketFlags.None, ref reusedEpSender, ReceivedData, null); 246 if (clients.TryGetValue(agentID, out client))
247 SendPacket(client, packet, category, allowSplitting);
248 else
249 m_log.Warn("[LLUDPSERVER]: Attempted to send a packet to unknown agentID " + agentID);
303 } 250 }
304 251
305 /// <summary> 252 public void SendPacket(LLUDPClient client, Packet packet, ThrottleOutPacketType category, bool allowSplitting)
306 /// Begin a robust asynchronous receive of the next bit of raw data. Robust means that SocketExceptions are
307 /// automatically dealt with until the next set of valid UDP data is received.
308 /// </summary>
309 private void BeginRobustReceive()
310 { 253 {
311 bool done = false; 254 if (allowSplitting && packet.HasVariableBlocks)
312
313 while (!done)
314 { 255 {
315 try 256 byte[][] datas = packet.ToBytesMultiple();
257 int packetCount = datas.Length;
258
259 if (packetCount > 1)
260 m_log.Debug("[LLUDPSERVER]: Split " + packet.Type + " packet into " + packetCount + " packets");
261
262 for (int i = 0; i < packetCount; i++)
316 { 263 {
317 BeginReceive(); 264 byte[] data = datas[i];
318 done = true; 265 SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category);
319 } 266 }
320 catch (SocketException e) 267 }
321 { 268 else
322 // ENDLESS LOOP ON PURPOSE! 269 {
323 // Reset connection and get next UDP packet off the buffer 270 byte[] data = packet.ToBytes();
324 // If the UDP packet is part of the same stream, this will happen several hundreds of times before 271 SendPacketData(client, data, data.Length, packet.Type, packet.Header.Zerocoded, category);
325 // the next set of UDP data is for a valid client. 272 }
273 }
326 274
327 try 275 public void SendPacketData(LLUDPClient client, byte[] data, int dataLength, PacketType type, bool doZerocode, ThrottleOutPacketType category)
328 { 276 {
329 CloseCircuit(e); 277 // Frequency analysis of outgoing packet sizes shows a large clump of packets at each end of the spectrum.
330 } 278 // The vast majority of packets are less than 200 bytes, although due to asset transfers and packet splitting
331 catch (Exception e2) 279 // there are a decent number of packets in the 1000-1140 byte range. We allocate one of two sizes of data here
332 { 280 // to accomodate for both common scenarios and provide ample room for ACK appending in both
333 m_log.ErrorFormat( 281 int bufferSize = (dataLength > 180) ? Packet.MTU : 200;
334 "[CLIENT]: Exception thrown when trying to close the circuit for {0} - {1}", reusedEpSender, 282
335 e2); 283 UDPPacketBuffer buffer = new UDPPacketBuffer(client.RemoteEndPoint, bufferSize);
336 }
337 }
338 catch (ObjectDisposedException)
339 {
340 m_log.Info(
341 "[UDPSERVER]: UDP Object disposed. No need to worry about this if you're restarting the simulator.");
342 284
343 done = true; 285 // Zerocode if needed
286 if (doZerocode)
287 {
288 try { dataLength = Helpers.ZeroEncode(data, dataLength, buffer.Data); }
289 catch (IndexOutOfRangeException)
290 {
291 // The packet grew larger than the bufferSize while zerocoding.
292 // Remove the MSG_ZEROCODED flag and send the unencoded data
293 // instead
294 m_log.Info("[LLUDPSERVER]: Packet exceeded buffer size during zerocoding. Removing MSG_ZEROCODED flag");
295 data[0] = (byte)(data[0] & ~Helpers.MSG_ZEROCODED);
296 Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength);
344 } 297 }
345 catch (Exception ex) 298 }
299 else
300 {
301 Buffer.BlockCopy(data, 0, buffer.Data, 0, dataLength);
302 }
303 buffer.DataLength = dataLength;
304
305 #region Queue or Send
306
307 // Look up the UDPClient this is going to
308 OutgoingPacket outgoingPacket = new OutgoingPacket(client, buffer, category);
309
310 if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket))
311 SendPacketFinal(outgoingPacket);
312
313 #endregion Queue or Send
314 }
315
316 public void SendAcks(LLUDPClient client)
317 {
318 uint ack;
319
320 if (client.PendingAcks.Dequeue(out ack))
321 {
322 List<PacketAckPacket.PacketsBlock> blocks = new List<PacketAckPacket.PacketsBlock>();
323 PacketAckPacket.PacketsBlock block = new PacketAckPacket.PacketsBlock();
324 block.ID = ack;
325 blocks.Add(block);
326
327 while (client.PendingAcks.Dequeue(out ack))
346 { 328 {
347 m_log.ErrorFormat("[CLIENT]: Exception thrown during BeginReceive(): {0}", ex); 329 block = new PacketAckPacket.PacketsBlock();
330 block.ID = ack;
331 blocks.Add(block);
348 } 332 }
333
334 PacketAckPacket packet = new PacketAckPacket();
335 packet.Header.Reliable = false;
336 packet.Packets = blocks.ToArray();
337
338 SendPacket(client, packet, ThrottleOutPacketType.Unknown, true);
349 } 339 }
350 } 340 }
351 341
352 /// <summary> 342 public void ResendUnacked(LLUDPClient client)
353 /// Close a client circuit. This is done in response to an exception on receive, and should not be called
354 /// normally.
355 /// </summary>
356 /// <param name="e">The exception that caused the close. Can be null if there was no exception</param>
357 private void CloseCircuit(Exception e)
358 { 343 {
359 uint circuit; 344 if (client.NeedAcks.Count > 0)
360 lock (clientCircuits)
361 { 345 {
362 if (clientCircuits.TryGetValue(reusedEpSender, out circuit)) 346 List<OutgoingPacket> expiredPackets = client.NeedAcks.GetExpiredPackets(client.RTO);
347
348 if (expiredPackets != null)
363 { 349 {
364 m_packetServer.CloseCircuit(circuit); 350 // Resend packets
365 351 for (int i = 0; i < expiredPackets.Count; i++)
366 if (e != null) 352 {
367 m_log.ErrorFormat( 353 OutgoingPacket outgoingPacket = expiredPackets[i];
368 "[CLIENT]: Closed circuit {0} {1} due to exception {2}", circuit, reusedEpSender, e); 354
355 // FIXME: Make this an .ini setting
356 if (outgoingPacket.ResendCount < 3)
357 {
358 //Logger.Debug(String.Format("Resending packet #{0} (attempt {1}), {2}ms have passed",
359 // outgoingPacket.SequenceNumber, outgoingPacket.ResendCount, Environment.TickCount - outgoingPacket.TickCount));
360
361 // Set the resent flag
362 outgoingPacket.Buffer.Data[0] = (byte)(outgoingPacket.Buffer.Data[0] | Helpers.MSG_RESENT);
363 outgoingPacket.Category = ThrottleOutPacketType.Resend;
364
365 // The TickCount will be set to the current time when the packet
366 // is actually sent out again
367 outgoingPacket.TickCount = 0;
368
369 Interlocked.Increment(ref outgoingPacket.ResendCount);
370 //Interlocked.Increment(ref Stats.ResentPackets);
371
372 // Queue or (re)send the packet
373 if (!outgoingPacket.Client.EnqueueOutgoing(outgoingPacket))
374 SendPacketFinal(outgoingPacket);
375 }
376 else
377 {
378 m_log.DebugFormat("[LLUDPSERVER]: Dropping packet #{0} for agent {1} after {2} failed attempts",
379 outgoingPacket.SequenceNumber, outgoingPacket.Client.RemoteEndPoint, outgoingPacket.ResendCount);
380
381 lock (client.NeedAcks.SyncRoot)
382 client.NeedAcks.RemoveUnsafe(outgoingPacket.SequenceNumber);
383
384 //Interlocked.Increment(ref Stats.DroppedPackets);
385
386 // Disconnect an agent if no packets are received for some time
387 //FIXME: Make 60 an .ini setting
388 if (Environment.TickCount - client.TickLastPacketReceived > 1000 * 60)
389 {
390 m_log.Warn("[LLUDPSERVER]: Ack timeout, disconnecting " + client.RemoteEndPoint);
391
392 RemoveClient(client);
393 return;
394 }
395 }
396 }
369 } 397 }
370 } 398 }
371 } 399 }
372 400
373 /// <summary> 401 public void Flush()
374 /// Finish the process of asynchronously receiving the next bit of raw data 402 {
375 /// </summary> 403 }
376 /// <param name="numBytes">The number of bytes received. Will return 0 if no bytes were recieved 404
377 /// <param name="result"></param> 405 protected override void PacketReceived(UDPPacketBuffer buffer)
378 /// <param name="epSender">The sender of the data</param>
379 /// <returns></returns>
380 protected virtual bool EndReceive(out int numBytes, IAsyncResult result, ref EndPoint epSender)
381 { 406 {
382 bool hasReceivedOkay = false; 407 // Debugging/Profiling
383 numBytes = 0; 408 //try { Thread.CurrentThread.Name = "PacketReceived (" + scene.RegionName + ")"; }
384 409 //catch (Exception) { }
410
411 LLUDPClient client = null;
412 Packet packet = null;
413 int packetEnd = buffer.DataLength - 1;
414 IPEndPoint address = (IPEndPoint)buffer.RemoteEndPoint;
415
416 #region Decoding
417
385 try 418 try
386 { 419 {
387 numBytes = m_socket.EndReceiveFrom(result, ref epSender); 420 packet = Packet.BuildPacket(buffer.Data, ref packetEnd,
388 hasReceivedOkay = true; 421 // Only allocate a buffer for zerodecoding if the packet is zerocoded
422 ((buffer.Data[0] & Helpers.MSG_ZEROCODED) != 0) ? new byte[4096] : null);
389 } 423 }
390 catch (SocketException e) 424 catch (MalformedDataException)
391 { 425 {
392 // TODO : Actually only handle those states that we have control over, re-throw everything else, 426 m_log.ErrorFormat("[LLUDPSERVER]: Malformed data, cannot parse packet:\n{0}",
393 // TODO: implement cases as we encounter them. 427 Utils.BytesToHexString(buffer.Data, buffer.DataLength, null));
394 //m_log.Error("[CLIENT]: Connection Error! - " + e.ToString()); 428 }
395 switch (e.SocketErrorCode) 429
396 { 430 // Fail-safe check
397 case SocketError.AlreadyInProgress: 431 if (packet == null)
398 return hasReceivedOkay; 432 {
433 m_log.Warn("[LLUDPSERVER]: Couldn't build a message from the incoming data");
434 return;
435 }
399 436
400 case SocketError.NetworkReset: 437 //Stats.RecvBytes += (ulong)buffer.DataLength;
401 case SocketError.ConnectionReset: 438 //++Stats.RecvPackets;
402 case SocketError.OperationAborted:
403 break;
404 439
405 default: 440 #endregion Decoding
406 throw; 441
442 #region UseCircuitCode Handling
443
444 if (packet.Type == PacketType.UseCircuitCode)
445 {
446 UseCircuitCodePacket useCircuitCode = (UseCircuitCodePacket)packet;
447 IClientAPI newuser;
448 uint circuitCode = useCircuitCode.CircuitCode.Code;
449
450 // Check if the client is already established
451 if (!m_scene.ClientManager.TryGetClient(circuitCode, out newuser))
452 {
453 AddNewClient(useCircuitCode, (IPEndPoint)buffer.RemoteEndPoint);
407 } 454 }
408 } 455 }
409 catch (ObjectDisposedException e) 456
457 // Determine which agent this packet came from
458 if (!clients.TryGetValue(address, out client))
410 { 459 {
411 m_log.DebugFormat("[CLIENT]: ObjectDisposedException: Object {0} disposed.", e.ObjectName); 460 m_log.Warn("[LLUDPSERVER]: Received a " + packet.Type + " packet from an unrecognized source: " + address);
412 // Uhh, what object, and why? this needs better handling. 461 return;
413 } 462 }
414
415 return hasReceivedOkay;
416 }
417 463
418 /// <summary> 464 #endregion UseCircuitCode Handling
419 /// Add a new client circuit. 465
420 /// </summary> 466 //if (packet.Header.Resent)
421 /// <param name="packet"></param> 467 // Interlocked.Increment(ref Stats.ReceivedResends);
422 /// <param name="epSender"></param> 468
423 /// <param name="epProxy"></param> 469 #region ACK Receiving
424 protected virtual void AddNewClient(UseCircuitCodePacket useCircuit, EndPoint epSender, EndPoint epProxy) 470
425 { 471 int now = Environment.TickCount;
426 //Slave regions don't accept new clients 472 client.TickLastPacketReceived = now;
427 if (m_localScene.RegionStatus != RegionStatus.SlaveScene) 473
474 // Handle appended ACKs
475 if (packet.Header.AppendedAcks && packet.Header.AckList != null)
428 { 476 {
429 AuthenticateResponse sessionInfo; 477 lock (client.NeedAcks.SyncRoot)
430 bool isNewCircuit = false;
431
432 if (!m_packetServer.IsClientAuthorized(useCircuit, m_circuitManager, out sessionInfo))
433 { 478 {
434 m_log.WarnFormat( 479 for (int i = 0; i < packet.Header.AckList.Length; i++)
435 "[CONNECTION FAILURE]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}", 480 AcknowledgePacket(client, packet.Header.AckList[i], now, packet.Header.Resent);
436 useCircuit.CircuitCode.ID, useCircuit.CircuitCode.Code, epSender);
437
438 return;
439 } 481 }
440 482 }
441 lock (clientCircuits) 483
484 // Handle PacketAck packets
485 if (packet.Type == PacketType.PacketAck)
486 {
487 PacketAckPacket ackPacket = (PacketAckPacket)packet;
488
489 lock (client.NeedAcks.SyncRoot)
442 { 490 {
443 if (!clientCircuits.ContainsKey(epSender)) 491 for (int i = 0; i < ackPacket.Packets.Length; i++)
444 { 492 AcknowledgePacket(client, ackPacket.Packets[i].ID, now, packet.Header.Resent);
445 clientCircuits.Add(epSender, useCircuit.CircuitCode.Code);
446 isNewCircuit = true;
447 }
448 } 493 }
494 }
449 495
450 if (isNewCircuit) 496 #endregion ACK Receiving
451 {
452 // This doesn't need locking as it's synchronized data
453 clientCircuits_reverse[useCircuit.CircuitCode.Code] = epSender;
454 497
455 lock (proxyCircuits) 498 #region ACK Sending
456 { 499
457 proxyCircuits[useCircuit.CircuitCode.Code] = epProxy; 500 if (packet.Header.Reliable)
458 } 501 client.PendingAcks.Enqueue((uint)packet.Header.Sequence);
459 502
460 m_packetServer.AddNewClient(epSender, useCircuit, sessionInfo, epProxy); 503 // This is a somewhat odd sequence of steps to pull the client.BytesSinceLastACK value out,
461 504 // add the current received bytes to it, test if 2*MTU bytes have been sent, if so remove
462 //m_log.DebugFormat( 505 // 2*MTU bytes from the value and send ACKs, and finally add the local value back to
463 // "[CONNECTION SUCCESS]: Incoming client {0} (circuit code {1}) received and authenticated for {2}", 506 // client.BytesSinceLastACK. Lockless thread safety
464 // useCircuit.CircuitCode.ID, useCircuit.CircuitCode.Code, m_localScene.RegionInfo.RegionName); 507 int bytesSinceLastACK = Interlocked.Exchange(ref client.BytesSinceLastACK, 0);
465 } 508 bytesSinceLastACK += buffer.DataLength;
509 if (bytesSinceLastACK > Packet.MTU * 2)
510 {
511 bytesSinceLastACK -= Packet.MTU * 2;
512 SendAcks(client);
513 }
514 Interlocked.Add(ref client.BytesSinceLastACK, bytesSinceLastACK);
515
516 #endregion ACK Sending
517
518 #region Incoming Packet Accounting
519
520 // Check the archive of received reliable packet IDs to see whether we already received this packet
521 if (packet.Header.Reliable && !client.PacketArchive.TryEnqueue(packet.Header.Sequence))
522 {
523 if (packet.Header.Resent)
524 m_log.Debug("[LLUDPSERVER]: Received a resend of already processed packet #" + packet.Header.Sequence + ", type: " + packet.Type);
525 else
526 m_log.Warn("[LLUDPSERVER]: Received a duplicate (not marked as resend) of packet #" + packet.Header.Sequence + ", type: " + packet.Type);
527
528 // Avoid firing a callback twice for the same packet
529 return;
466 } 530 }
467
468 // Ack the UseCircuitCode packet
469 PacketAckPacket ack_it = (PacketAckPacket)PacketPool.Instance.GetPacket(PacketType.PacketAck);
470 // TODO: don't create new blocks if recycling an old packet
471 ack_it.Packets = new PacketAckPacket.PacketsBlock[1];
472 ack_it.Packets[0] = new PacketAckPacket.PacketsBlock();
473 ack_it.Packets[0].ID = useCircuit.Header.Sequence;
474 // ((useCircuit.Header.Sequence < uint.MaxValue) ? useCircuit.Header.Sequence : 0) is just a failsafe to ensure that we don't overflow.
475 ack_it.Header.Sequence = ((useCircuit.Header.Sequence < uint.MaxValue) ? useCircuit.Header.Sequence : 0) + 1;
476 ack_it.Header.Reliable = false;
477 531
478 byte[] ackmsg = ack_it.ToBytes(); 532 #endregion Incoming Packet Accounting
479 533
480 // Need some extra space in case we need to add proxy 534 // Don't bother clogging up the queue with PacketAck packets that are already handled here
481 // information to the message later 535 if (packet.Type != PacketType.PacketAck)
482 byte[] msg = new byte[4096]; 536 {
483 Buffer.BlockCopy(ackmsg, 0, msg, 0, ackmsg.Length); 537 // Inbox insertion
538 IncomingPacket incomingPacket;
539 incomingPacket.Client = client;
540 incomingPacket.Packet = packet;
484 541
485 SendPacketTo(msg, ackmsg.Length, SocketFlags.None, useCircuit.CircuitCode.Code); 542 packetInbox.Enqueue(incomingPacket);
543 }
544 }
486 545
487 PacketPool.Instance.ReturnPacket(useCircuit); 546 protected override void PacketSent(UDPPacketBuffer buffer, int bytesSent)
547 {
488 } 548 }
489 549
490 public void ServerListener() 550 private bool IsClientAuthorized(UseCircuitCodePacket useCircuitCode, out AuthenticateResponse sessionInfo)
491 { 551 {
492 uint newPort = listenPort; 552 UUID agentID = useCircuitCode.CircuitCode.ID;
493 m_log.Info("[UDPSERVER]: Opening UDP socket on " + listenIP + " " + newPort + "."); 553 UUID sessionID = useCircuitCode.CircuitCode.SessionID;
554 uint circuitCode = useCircuitCode.CircuitCode.Code;
494 555
495 ServerIncoming = new IPEndPoint(listenIP, (int)newPort); 556 sessionInfo = m_circuitManager.AuthenticateSession(sessionID, agentID, circuitCode);
496 m_socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); 557 return sessionInfo.Authorised;
497 if (0 != m_clientSocketReceiveBuffer) 558 }
498 m_socket.ReceiveBufferSize = m_clientSocketReceiveBuffer;
499 m_socket.Bind(ServerIncoming);
500 // Add flags to the UDP socket to prevent "Socket forcibly closed by host"
501 // uint IOC_IN = 0x80000000;
502 // uint IOC_VENDOR = 0x18000000;
503 // uint SIO_UDP_CONNRESET = IOC_IN | IOC_VENDOR | 12;
504 // TODO: this apparently works in .NET but not in Mono, need to sort out the right flags here.
505 // m_socket.IOControl((int)SIO_UDP_CONNRESET, new byte[] { Convert.ToByte(false) }, null);
506 559
507 listenPort = newPort; 560 private void AddNewClient(UseCircuitCodePacket useCircuitCode, IPEndPoint remoteEndPoint)
561 {
562 //Slave regions don't accept new clients
563 if (m_scene.RegionStatus != RegionStatus.SlaveScene)
564 {
565 AuthenticateResponse sessionInfo;
566 bool isNewCircuit = !clients.ContainsKey(remoteEndPoint);
508 567
509 m_log.Info("[UDPSERVER]: UDP socket bound, getting ready to listen"); 568 if (!IsClientAuthorized(useCircuitCode, out sessionInfo))
569 {
570 m_log.WarnFormat(
571 "[CONNECTION FAILURE]: Connection request for client {0} connecting with unnotified circuit code {1} from {2}",
572 useCircuitCode.CircuitCode.ID, useCircuitCode.CircuitCode.Code, remoteEndPoint);
573 return;
574 }
510 575
511 ReceivedData = OnReceivedData; 576 if (isNewCircuit)
512 BeginReceive(); 577 {
578 UUID agentID = useCircuitCode.CircuitCode.ID;
579 UUID sessionID = useCircuitCode.CircuitCode.SessionID;
580 uint circuitCode = useCircuitCode.CircuitCode.Code;
513 581
514 m_log.Info("[UDPSERVER]: Listening on port " + newPort); 582 AddClient(circuitCode, agentID, sessionID, remoteEndPoint, sessionInfo);
583 }
584 }
515 } 585 }
516 586
517 public virtual void RegisterPacketServer(LLPacketServer server) 587 private void AddClient(uint circuitCode, UUID agentID, UUID sessionID, IPEndPoint remoteEndPoint, AuthenticateResponse sessionInfo)
518 { 588 {
519 m_packetServer = server; 589 // Create the LLUDPClient
590 LLUDPClient client = new LLUDPClient(this, m_throttleRates, m_throttle, circuitCode, agentID, remoteEndPoint);
591 clients.Add(agentID, client.RemoteEndPoint, client);
592
593 // Create the IClientAPI
594 IClientAPI clientApi = new LLClientView(remoteEndPoint, m_scene, this, client, sessionInfo, agentID, sessionID, circuitCode);
595 clientApi.OnViewerEffect += m_scene.ClientManager.ViewerEffectHandler;
596 clientApi.OnLogout += LogoutHandler;
597 clientApi.OnConnectionClosed += RemoveClient;
598
599 // Give LLUDPClient a reference to IClientAPI
600 client.ClientAPI = clientApi;
601
602 // Start the IClientAPI
603 m_scene.ClientManager.Add(circuitCode, clientApi);
604 clientApi.Start();
520 } 605 }
521 606
522 public virtual void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode) 607 private void AcknowledgePacket(LLUDPClient client, uint ack, int currentTime, bool fromResend)
523 //EndPoint packetSender)
524 { 608 {
525 // find the endpoint for this circuit 609 OutgoingPacket ackedPacket;
526 EndPoint sendto; 610 if (client.NeedAcks.RemoveUnsafe(ack, out ackedPacket) && !fromResend)
527 try
528 {
529 sendto = (EndPoint)clientCircuits_reverse[circuitcode];
530 }
531 catch
532 { 611 {
533 // Exceptions here mean there is no circuit 612 // Calculate the round-trip time for this packet and its ACK
534 m_log.Warn("[CLIENT]: Circuit not found, not sending packet"); 613 int rtt = currentTime - ackedPacket.TickCount;
535 return; 614 if (rtt > 0)
615 client.UpdateRoundTrip(rtt);
536 } 616 }
617 }
618
619 private void IncomingPacketHandler()
620 {
621 IncomingPacket incomingPacket = new IncomingPacket();
622 Packet packet = null;
623 LLUDPClient client = null;
537 624
538 if (sendto != null) 625 while (base.IsRunning)
539 { 626 {
540 //we found the endpoint so send the packet to it 627 // Reset packet to null for the check below
541 if (proxyPortOffset != 0) 628 packet = null;
542 { 629
543 //MainLog.Instance.Verbose("UDPSERVER", "SendPacketTo proxy " + proxyCircuits[circuitcode].ToString() + ": client " + sendto.ToString()); 630 if (packetInbox.Dequeue(100, ref incomingPacket))
544 ProxyCodec.EncodeProxyMessage(buffer, ref size, sendto);
545 m_socket.SendTo(buffer, size, flags, proxyCircuits[circuitcode]);
546 }
547 else
548 { 631 {
549 //MainLog.Instance.Verbose("UDPSERVER", "SendPacketTo : client " + sendto.ToString()); 632 packet = incomingPacket.Packet;
550 try 633 client = incomingPacket.Client;
551 { 634
552 m_socket.SendTo(buffer, size, flags, sendto); 635 if (packet != null && client != null)
553 } 636 client.ClientAPI.ProcessInPacket(packet);
554 catch (SocketException SockE)
555 {
556 m_log.ErrorFormat("[UDPSERVER]: Caught Socket Error in the send buffer!. {0}",SockE.ToString());
557 }
558 } 637 }
559 } 638 }
639
640 if (packetInbox.Count > 0)
641 m_log.Warn("[LLUDPSERVER]: IncomingPacketHandler is shutting down, dropping " + packetInbox.Count + " packets");
642 packetInbox.Clear();
560 } 643 }
561 644
562 public virtual void RemoveClientCircuit(uint circuitcode) 645 private void OutgoingPacketHandler()
563 { 646 {
564 EndPoint sendto; 647 int now = Environment.TickCount;
565 if (clientCircuits_reverse.Contains(circuitcode)) 648 int elapsedMS = 0;
649 int elapsed100MS = 0;
650
651 while (base.IsRunning)
566 { 652 {
567 sendto = (EndPoint)clientCircuits_reverse[circuitcode]; 653 bool resendUnacked = false;
654 bool sendAcks = false;
655 bool packetSent = false;
568 656
569 clientCircuits_reverse.Remove(circuitcode); 657 elapsedMS += Environment.TickCount - now;
570 658
571 lock (clientCircuits) 659 // Check for packets that need to be resent every 100ms
660 if (elapsedMS >= 100)
572 { 661 {
573 if (sendto != null) 662 resendUnacked = true;
574 { 663 elapsedMS -= 100;
575 clientCircuits.Remove(sendto); 664 ++elapsed100MS;
576 }
577 else
578 {
579 m_log.DebugFormat(
580 "[CLIENT]: endpoint for circuit code {0} in RemoveClientCircuit() was unexpectedly null!", circuitcode);
581 }
582 } 665 }
583 lock (proxyCircuits) 666 // Check for ACKs that need to be sent out every 500ms
667 if (elapsed100MS >= 5)
584 { 668 {
585 proxyCircuits.Remove(circuitcode); 669 sendAcks = true;
670 elapsed100MS = 0;
586 } 671 }
672
673 clients.ForEach(
674 delegate(LLUDPClient client)
675 {
676 if (client.DequeueOutgoing())
677 packetSent = true;
678 if (resendUnacked)
679 ResendUnacked(client);
680 if (sendAcks)
681 SendAcks(client);
682 }
683 );
684
685 if (!packetSent)
686 Thread.Sleep(20);
587 } 687 }
588 } 688 }
589 689
590 public void RestoreClient(AgentCircuitData circuit, EndPoint userEP, EndPoint proxyEP) 690 private void LogoutHandler(IClientAPI client)
591 { 691 {
592 //MainLog.Instance.Verbose("UDPSERVER", "RestoreClient"); 692 client.SendLogoutPacket();
693 RemoveClient(client);
694 }
695
696 internal void SendPacketFinal(OutgoingPacket outgoingPacket)
697 {
698 UDPPacketBuffer buffer = outgoingPacket.Buffer;
699 byte flags = buffer.Data[0];
700 bool isResend = (flags & Helpers.MSG_RESENT) != 0;
701 bool isReliable = (flags & Helpers.MSG_RELIABLE) != 0;
702 LLUDPClient client = outgoingPacket.Client;
703
704 // Keep track of when this packet was sent out (right now)
705 outgoingPacket.TickCount = Environment.TickCount;
593 706
594 UseCircuitCodePacket useCircuit = new UseCircuitCodePacket(); 707 #region ACK Appending
595 useCircuit.CircuitCode.Code = circuit.circuitcode; 708
596 useCircuit.CircuitCode.ID = circuit.AgentID; 709 int dataLength = buffer.DataLength;
597 useCircuit.CircuitCode.SessionID = circuit.SessionID; 710
598 711 // Keep appending ACKs until there is no room left in the packet or there are
599 AuthenticateResponse sessionInfo; 712 // no more ACKs to append
600 713 uint ackCount = 0;
601 if (!m_packetServer.IsClientAuthorized(useCircuit, m_circuitManager, out sessionInfo)) 714 uint ack;
715 while (dataLength + 5 < buffer.Data.Length && client.PendingAcks.Dequeue(out ack))
602 { 716 {
603 m_log.WarnFormat( 717 Utils.UIntToBytesBig(ack, buffer.Data, dataLength);
604 "[CLIENT]: Restore request denied to avatar {0} connecting with unauthorized circuit code {1}", 718 dataLength += 4;
605 useCircuit.CircuitCode.ID, useCircuit.CircuitCode.Code); 719 ++ackCount;
606
607 return;
608 } 720 }
609 721
610 lock (clientCircuits) 722 if (ackCount > 0)
611 { 723 {
612 if (!clientCircuits.ContainsKey(userEP)) 724 // Set the last byte of the packet equal to the number of appended ACKs
613 clientCircuits.Add(userEP, useCircuit.CircuitCode.Code); 725 buffer.Data[dataLength++] = (byte)ackCount;
614 else 726 // Set the appended ACKs flag on this packet
615 m_log.Error("[CLIENT]: clientCircuits already contains entry for user " + useCircuit.CircuitCode.Code + ". NOT adding."); 727 buffer.Data[0] = (byte)(buffer.Data[0] | Helpers.MSG_APPENDED_ACKS);
616 } 728 }
617 729
618 // This data structure is synchronized, so we don't need the lock 730 buffer.DataLength = dataLength;
619 if (!clientCircuits_reverse.ContainsKey(useCircuit.CircuitCode.Code))
620 clientCircuits_reverse.Add(useCircuit.CircuitCode.Code, userEP);
621 else
622 m_log.Error("[CLIENT]: clientCurcuits_reverse already contains entry for user " + useCircuit.CircuitCode.Code + ". NOT adding.");
623 731
624 lock (proxyCircuits) 732 #endregion ACK Appending
733
734 if (!isResend)
625 { 735 {
626 if (!proxyCircuits.ContainsKey(useCircuit.CircuitCode.Code)) 736 // Not a resend, assign a new sequence number
627 { 737 uint sequenceNumber = (uint)Interlocked.Increment(ref client.CurrentSequence);
628 proxyCircuits.Add(useCircuit.CircuitCode.Code, proxyEP); 738 Utils.UIntToBytesBig(sequenceNumber, buffer.Data, 1);
629 } 739 outgoingPacket.SequenceNumber = sequenceNumber;
630 else 740
741 if (isReliable)
631 { 742 {
632 // re-set proxy endpoint 743 // Add this packet to the list of ACK responses we are waiting on from the server
633 proxyCircuits.Remove(useCircuit.CircuitCode.Code); 744 client.NeedAcks.Add(outgoingPacket);
634 proxyCircuits.Add(useCircuit.CircuitCode.Code, proxyEP);
635 } 745 }
636 } 746 }
637 747
638 m_packetServer.AddNewClient(userEP, useCircuit, sessionInfo, proxyEP); 748 // Put the UDP payload on the wire
749 AsyncBeginSend(buffer);
639 } 750 }
640 } 751 }
641} 752}