/// <summary> /// Called when a message has been successfully sent. /// /// Decodes the unsent bytes and tries sending them again. /// </summary> private void MessageSent(IAsyncResult result) { // Protect SendQueue lock (SendQueue) { try { // Find out how many bytes were actually sent int bytes = socket.EndSend(result); // Get the bytes that we attempted to send byte[] outgoingBuffer = (byte[])result.AsyncState; // If no bytes were received, close the socket. //if (bytes == 0) //{ // //what to do here??? // socket.Close(); //} // Prepend the unsent bytes and try sending again. //else { outgoing = encoding.GetString(outgoingBuffer, bytes, outgoingBuffer.Length - bytes) + outgoing; SendBytes(); } } catch (Exception e) { SendRequest sr2 = SendQueue.Dequeue(); ThreadPool.QueueUserWorkItem(x => sr2.sendCallback(e, sr2.payload)); } } }
/// <summary> /// We can write a string to a StringSocket ss by doing /// /// ss.BeginSend("Hello world", callback, payload); /// /// where callback is a SendCallback (see below) and payload is an arbitrary object. /// This is a non-blocking, asynchronous operation. When the StringSocket has /// successfully written the string to the underlying Socket, or failed in the /// attempt, it invokes the callback. The parameters to the callback are a /// (possibly null) Exception and the payload. If the Exception is non-null, it is /// the Exception that caused the send attempt to fail. /// /// This method is non-blocking. This means that it does not wait until the string /// has been sent before returning. Instead, it arranges for the string to be sent /// and then returns. When the send is completed (at some time in the future), the /// callback is called on another thread. /// /// This method is thread safe. This means that multiple threads can call BeginSend /// on a shared socket without worrying around synchronization. The implementation of /// BeginSend must take care of synchronization instead. On a given StringSocket, each /// string arriving via a BeginSend method call must be sent (in its entirety) before /// a later arriving string can be sent. /// </summary> public void BeginSend(String s, SendCallback callback, object payload) { SendRequest sendRequest = new SendRequest(s, callback, payload); SendQueue.Enqueue(sendRequest); SendMessage(SendQueue.Peek()); SendRequest currnetSR = SendQueue.Dequeue(); lock (sendSync) { ThreadPool.QueueUserWorkItem(x => currnetSR.sendCallBack(null, currnetSR.payload)); } }
/// <summary> /// Checks if the entire message has been sent. If the message hasn't been sent /// or there is another request, begin sending it. /// </summary> private void SendBytes() { if (SendQueue.Count > 0) { // If the entire message has been sent. if (outgoing == "") { // Dequeue the send request. SendRequest sr = SendQueue.Dequeue(); // Call the appropriate callback. ThreadPool.QueueUserWorkItem(x => sr.sendCallback(null, sr.payload)); // If there's another request in the queue, get its message and begin sending it. if (SendQueue.Count > 0) { outgoing = SendQueue.Peek().message; byte[] outgoingBuffer = encoding.GetBytes(outgoing); outgoing = ""; try { socket.BeginSend(outgoingBuffer, 0, outgoingBuffer.Length, SocketFlags.None, MessageSent, outgoingBuffer); } catch (Exception e) { SendRequest sr1 = SendQueue.Dequeue(); ThreadPool.QueueUserWorkItem(x => sr1.sendCallback(e, sr1.payload)); } } } // Otherwise, the entire message has not been sent. Send more. else { byte[] outgoingBuffer = encoding.GetBytes(outgoing); outgoing = ""; try { socket.BeginSend(outgoingBuffer, 0, outgoingBuffer.Length, SocketFlags.None, MessageSent, outgoingBuffer); } catch (Exception e) { SendRequest sr2 = SendQueue.Dequeue(); ThreadPool.QueueUserWorkItem(x => sr2.sendCallback(e, sr2.payload)); } } } }
/// <summary> /// This should be called only after a lock on sendRequests has been acquired. /// It pings back and forth with the BytesSent callback to send out all the strings in /// the queue. This method gets the string at the front of the queue and attempts /// to send it. BytesSent takes care of making sure all of the bytes are actually sent /// before calling this method again to send the next string. /// </summary> private void ProcessSendQueue() { while (sendRequests.Count > 0) { sendBytes = encoding.GetBytes(sendRequests.First().Text); try { socket.BeginSend(sendBytes, sendCount = 0, sendBytes.Length, SocketFlags.None, BytesSent, null); break; } catch (Exception e) { SendRequest req = sendRequests.Dequeue(); ThreadPool.QueueUserWorkItem(x => req.Callback(e, req.Payload)); } } }
/// <summary> /// Sends a string to the client /// </summary> private void SendMessage(SendRequest sr) { // Get exclusive access to send mechanism lock (sendSync) { String message = sr.message; // Append the message to the unsent string outgoing += message; // If there's not a send ongoing, start one. if (!sendIsOngoing) { sendIsOngoing = true; SendBytes(); } } }
/// <summary> /// This helper method sends out all the strings in the queue by calling /// the BytesSent callback again and again. It tries to send the string at the /// front of queue. It contains a call to another helper method BytesSent /// to ensure all the bytes from the front string are sent before calling this /// method again to send the next string. /// /// IMPLEMENTATION NOTE: Should be called from within a lock for thread-safety. /// </summary> private void HandleSendQueue() { while (sendQueue.Count > 0) { sendBytes = encoding.GetBytes(sendQueue.First <SendRequest>().Text); try { socket.BeginSend(sendBytes, sendCount = 0, sendBytes.Length, SocketFlags.None, new AsyncCallback(BytesSent), (object)null); break; } catch (Exception ex) { SendRequest request = sendQueue.Dequeue(); ThreadPool.QueueUserWorkItem((WaitCallback)(x => request.SendBack(ex, request.Payload))); } } }
/// <summary> /// This method is the callback used when bytes are being sent. It makes sure that all of /// the bytes have been sent, then calls the appropriate callback and calls ProcessSendQueue. /// </summary> private void BytesSent(IAsyncResult ar) { try { // Compute how many bytes have been sent so far sendCount += socket.EndSend(ar); } catch (Exception e) { SendRequest req = sendRequests.Dequeue(); ThreadPool.QueueUserWorkItem(x => req.Callback(e, req.Payload)); ProcessSendQueue(); return; } // If all the bytes were sent, remove the request from the queue, notify the // callback, and process the next entry in the send queue. if (sendCount == sendBytes.Length) { lock (sendRequests) { SendRequest req = sendRequests.Dequeue(); ThreadPool.QueueUserWorkItem(x => req.Callback(null, req.Payload)); ProcessSendQueue(); } } // If all the bytes weren't sent, send the rest. else { try { socket.BeginSend(sendBytes, sendCount, sendBytes.Length - sendCount, SocketFlags.None, BytesSent, null); } catch (Exception e) { SendRequest req = sendRequests.Dequeue(); ThreadPool.QueueUserWorkItem(x => req.Callback(e, req.Payload)); ProcessSendQueue(); } } }
/// <summary> /// We can write a string to a StringSocket ss by doing /// /// ss.BeginSend("Hello world", callback, payload); /// /// where callback is a SendCallback (see below) and payload is an arbitrary object. /// This is a non-blocking, asynchronous operation. When the StringSocket has /// successfully written the string to the underlying Socket, or failed in the /// attempt, it invokes the callback. The parameters to the callback are a /// (possibly null) Exception and the payload. If the Exception is non-null, it is /// the Exception that caused the send attempt to fail. /// /// This method is non-blocking. This means that it does not wait until the string /// has been sent before returning. Instead, it arranges for the string to be sent /// and then returns. When the send is completed (at some time in the future), the /// callback is called on another thread. /// /// This method is thread safe. This means that multiple threads can call BeginSend /// on a shared socket without worrying around synchronization. The implementation of /// BeginSend must take care of synchronization instead. On a given StringSocket, each /// string arriving via a BeginSend method call must be sent (in its entirety) before /// a later arriving string can be sent. /// </summary> public void BeginSend(String s, SendCallback callback, object payload) { // Protect SendQueue lock (SendQueue) { // Create and store the send request. SendRequest sendRequest = new SendRequest(s, callback, payload); SendQueue.Enqueue(sendRequest); // If there's not a send ongoing, start one. if (SendQueue.Count() == 1) { // Append the message to the unsent string outgoing += s; SendBytes(); } } }
/// <summary> /// The callback method used when sending bytes. It makes sure that all of the bytes /// are sent before making the approriate callback and call to HandleSendQueue. /// /// IMPLEMENTATION NOTE: Should be called from within a lock for thread-safety. /// </summary> /// <param name="arg">Object containing status of asyn operation</param> private void BytesSent(IAsyncResult arg) { try { sendCount = sendCount + socket.EndSend(arg); } catch (Exception ex) { SendRequest request = sendQueue.Dequeue(); ThreadPool.QueueUserWorkItem((WaitCallback)(x => request.SendBack(ex, request.Payload))); HandleSendQueue(); return; } if (sendCount == sendBytes.Length) { lock (sendQueue) { SendRequest req = sendQueue.Dequeue(); ThreadPool.QueueUserWorkItem((WaitCallback)(x => req.SendBack((Exception)null, req.Payload))); HandleSendQueue(); } } else { try { socket.BeginSend(sendBytes, sendCount, sendBytes.Length - sendCount, SocketFlags.None, new AsyncCallback(BytesSent), (object)null); } catch (Exception ex) { SendRequest request = sendQueue.Dequeue(); ThreadPool.QueueUserWorkItem((WaitCallback)(x => request.SendBack(ex, request.Payload))); HandleSendQueue(); } } }