/// <summary> /// Reads bytes up to and including the passed data paramter, which acts as a separator. /// The bytes and the separator are returned by the delegate method. /// /// If you pass null or zero-length data as the separator, this method will do nothing. /// To read a line from the socket, use the line separator (e.g. CRLF for HTTP) as the data parameter. /// Note that this method is not character-set aware, so if a separator can occur naturally /// as part of the encoding for a character, the read will prematurely end. /// </summary> /// <param name="term"> /// The separator/delimeter to use. /// </param> /// <param name="timeout"> /// Timeout in milliseconds. Specify negative value for no timeout. /// </param> /// <param name="tag"> /// Tag to identify read request. /// </param> public void Read(byte[] term, int timeout, long tag) { if ((term == null) || (term.Length == 0)) return; if ((flags & kForbidReadsWrites) > 0) return; MutableData buffer = new MutableData(0); // readQueue is synchronized readQueue.Enqueue(new AsyncReadPacket(buffer, timeout, -1, tag, false, false, term)); // Queue a call to MaybeDequeueRead ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeDequeueRead)); }
/// <summary> /// Reads a certain number of bytes, and calls the delegate method when those bytes have been read. /// If length is 0, this method does nothing and no delgate methods are called. /// </summary> /// <param name="length"> /// The number of bytes to read. /// </param> /// <param name="timeout"> /// Timeout in milliseconds. Specify negative value for no timeout. /// </param> /// <param name="tag"> /// Tag to identify read request. /// </param> public void Read(int length, int timeout, long tag) { if (length <= 0) return; if ((flags & kForbidReadsWrites) > 0) return; MutableData buffer = new MutableData(length); // readQueue is synchronized readQueue.Enqueue(new AsyncReadPacket(buffer, timeout, -1, tag, false, true, null)); // Queue a call to maybeDequeueRead ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeDequeueRead)); }
public AsyncReadPacket(MutableData buffer, int timeout, int maxLength, long tag, bool readAllAvailableData, bool fixedLengthRead, byte[] term) { this.buffer = buffer; this.bytesDone = 0; this.bytesProcessing = 0; this.timeout = timeout; this.maxLength = maxLength; this.tag = tag; this.readAllAvailableData = readAllAvailableData; this.fixedLengthRead = fixedLengthRead; this.term = term; }
/// <summary> /// This method extracts any unprocessed data, and makes it available to the client. /// /// Called solely from CloseWithException, which is only called from thread safe methods. /// </summary> private void RecoverUnreadData() { if (currentRead != null) { // We never finished the current read. int bytesAvailable = currentRead.bytesDone + currentRead.bytesProcessing; if (readOverflow == null) { readOverflow = new MutableData(currentRead.buffer, 0, bytesAvailable); } else { // We need to move the data into the front of the read overflow readOverflow.InsertData(0, currentRead.buffer, 0, bytesAvailable); } } }
/// <summary> /// This method fills the currentRead buffer with data from the readOverflow variable. /// After this is properly completed, DoFinishRead is called to process the bytes. /// /// This method is called from MaybeDequeueRead(). /// /// The above method is thread safe, so this method is inherently thread safe. /// It is not explicitly thread safe though, and should not be called outside thread safe methods. /// </summary> private void DoReadOverflow() { Debug.Assert(currentRead.bytesDone == 0); Debug.Assert(readOverflow.Length > 0); try { if (currentRead.readAllAvailableData) { // We're supposed to read what's available. // What we have in the readOverflow is what we have available, so just use it. currentRead.buffer = readOverflow; currentRead.bytesProcessing = readOverflow.Length; readOverflow = null; } else if (currentRead.fixedLengthRead) { // We're reading a certain length of data. if (currentRead.buffer.Length < readOverflow.Length) { byte[] src = readOverflow.ByteArray; byte[] dst = currentRead.buffer.ByteArray; Buffer.BlockCopy(src, 0, dst, 0, dst.Length); currentRead.bytesProcessing = dst.Length; readOverflow.TrimStart(dst.Length); // Note that this is the only case in which the readOverflow isn't emptied. // This is OK because the read is guaranteed to finish in DoFinishRead(). } else { byte[] src = readOverflow.ByteArray; byte[] dst = currentRead.buffer.ByteArray; Buffer.BlockCopy(src, 0, dst, 0, src.Length); currentRead.bytesProcessing = src.Length; readOverflow = null; } } else { // We're reading up to a termination sequence // So we can just set the currentRead buffer to the readOverflow // and the DoStartRead method will automatically handle any further overflow. currentRead.buffer = readOverflow; currentRead.bytesProcessing = readOverflow.Length; readOverflow = null; } // At this point we've filled a currentRead buffer with some data // And the currentRead.bytesProcessing is set to the amount of data we filled it with // It's now time to process the data. DoFinishRead(); } catch (Exception e) { CloseWithException(e); } }
/// <summary> /// This method is called when either: /// A) a new read is taken from the read queue /// B) or when data has just been read from the stream. /// /// More specifically, it is called from either: /// A) DoReadOverflow() /// B) stream_DidRead() /// /// The above methods are thread safe, so this method is inherently thread safe. /// It is not explicitly thread safe though, and should not be called outside thread safe methods. /// </summary> private void DoFinishRead() { Debug.Assert(currentRead != null); Debug.Assert(currentRead.bytesProcessing > 0); int totalBytesRead = 0; bool done = false; bool maxoutError = false; if(currentRead.readAllAvailableData) { // We're done because we read everything that was available (up to a max size). currentRead.bytesDone += currentRead.bytesProcessing; totalBytesRead = currentRead.bytesProcessing; currentRead.bytesProcessing = 0; done = true; } else if (currentRead.fixedLengthRead) { // We're reading up to a fixed size currentRead.bytesDone += currentRead.bytesProcessing; totalBytesRead = currentRead.bytesProcessing; currentRead.bytesProcessing = 0; done = currentRead.buffer.Length == currentRead.bytesDone; } else { // We're reading up to a terminator // So let's start searching for the termination sequence in the new data while (!done && !maxoutError && (currentRead.bytesProcessing > 0)) { currentRead.bytesDone++; totalBytesRead++; currentRead.bytesProcessing--; bool match = currentRead.bytesDone >= currentRead.term.Length; int offset = currentRead.bytesDone - currentRead.term.Length; for (int i = 0; match && i < currentRead.term.Length; i++) { match = (currentRead.term[i] == currentRead.buffer[offset + i]); } done = match; if (!done && (currentRead.maxLength >= 0) && (currentRead.bytesDone >= currentRead.maxLength)) { maxoutError = true; } } } // If there was any overflow data, extract it and save it. // This may occur if our read maxed out. // Or if we received Y bytes, but only needed X bytes to finish the read (X < Y). if (currentRead.bytesProcessing > 0) { readOverflow = new MutableData(currentRead.buffer, currentRead.bytesDone, currentRead.bytesProcessing); currentRead.bytesProcessing = 0; } if (done) { // Truncate any excess unused buffer space in the read packet currentRead.buffer.SetLength(currentRead.bytesDone); CompleteCurrentRead(); ThreadPool.QueueUserWorkItem(new WaitCallback(MaybeDequeueRead)); } else if (maxoutError) { CloseWithException(GetReadMaxedOutException()); } else { // We're not done yet, but we have read in some bytes OnSocketDidReadPartial(totalBytesRead, currentRead.tag); // It appears that we've read all immediately available data on the socket // So begin asynchronously reading data again DoStartRead(); } }