9

Based on the advice of @Len-Holgate in this question, I'm asynchronously requesting 0-byte reads, and in the callback, accept bytes the available bytes with synchronous reads, since I know the data is available and won't block. This seems so efficient and wonderful.

But then I add the option for SslStream, and the approach falls apart. The zero-byte read is fine, but the SslStream decrypts the bytes, leaving a zero byte-count in the TcpClient's buffer (appropriately so), and I cannot determine how many bytes are now in the SslStream available for reading.

Is there a simple trick around this?


Some code, just for context:

sslStream.BeginRead(this.zeroByteBuffer, 0, 0, DataAvailable, this);

And after the EndRead() ( which correctly returns 0 ), DataAvailable contains:

// by now this is 0, because sslStream has already consumed the bytes
available = myTcpClient.Available; 

if (0 < available) // Never occurs
{
    // this part can be distractingly complicated, but 
    // it's based on the available byte count
    sslStream.Read(...); 
}

And due to the protocol, I need to evaluate byte-by-byte and decode variable byte-width unicode and stuff. I don't want to have to read byte-by-byte asynchronously!

Community
  • 1
  • 1
Jason Kleban
  • 20,024
  • 18
  • 75
  • 125
  • 3
    I've built many Socket-based applications, with or without SSL, synchronous or asynchronous. But I've never encountered a situation in which I'd need to know the number of available bytes. I'd go so far and call an application that relies on it broken. Can you explain why your code needs this? Why don't you simply request byteBuffer.Length many bytes? – dtb Jul 13 '11 at 23:28
  • 1
    Because I don't know how many bytes I will need to receive before, in this case, a newline character. If I ask for 1024 bytes, then the callback won't return until 1024 bytes are available (or the end-of-stream is reached due to the socket being closed). Unless I'm mistaken about this? – Jason Kleban Jul 13 '11 at 23:44
  • 2
    Both Read and BeginRead return only what's available, unless nothing is available in which case they wait until at least one byte becomes available or the stream is closed. For example, if you request 1024 bytes, they will return between 1 and 1024 bytes, but will not wait until exactly 1024 bytes have been received. – dtb Jul 14 '11 at 00:18
  • Hmm, I had wondered about that but the documentation and other post took me in a different direction. Still, because I'm searching for delimiters in the stream, I'm having to read one byte at a time and async seems to be a lot of overhead for a single byte at a time. Otherwise I have to keep a dynamic buffer to store what I haven't yet processed between delimiters. The buffered stream might be a solution to the overhead to the OS, but not the async itself. The zero-byte buffer seems like a neat solution. Is there a way to track Available for a SslStream otherwise? – Jason Kleban Jul 14 '11 at 00:42
  • 3
    You can use BeginRead requesting 0 bytes, and when your callback is called you can use Read requesting byteBuffer.Length many bytes to retrieve the available bytes. You just have to make sure that your buffer is large enough or, better yet, retrieve the available bytes in multiple chunks. Note that, since TCP is stream- and not message-oriented, the delimiter is not guaranteed to appear exactly at the end of a chunk of available bytes. So you have to do some buffering and finding delimiters in the middle of buffers anyway. – dtb Jul 14 '11 at 01:04
  • But Read() may block in that case, defeating the whole point of async io. And yes, I am buffering in the data in a StringBuilder, after having converted it to Chars via a selected Decoder. – Jason Kleban Jul 14 '11 at 01:12
  • 2
    Read blocks only if no bytes are available. But since you know that bytes *are* available (because your callback has been called) you can safely call Read without having to fear that it will block. That said, I'm not sure why reading the bytes directly with BeginRead should be worse than asynchronously waiting for bytes and synchronously reading them. – dtb Jul 14 '11 at 02:39
  • @dtb let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/1455/discussion-between-uos-and-dtb) – Jason Kleban Jul 14 '11 at 11:22

1 Answers1

1

If I understood correctly, your messages are delimited by a certain character, and you are already using a StringBuilder to cover the case when a message is fragmented into multiple pieces.

You could consider ignoring the delimiter when reading data, adding any data to it when it becomes available, and then inspecting the local StringBuilder for the delimiter character. When found, you can extract a single message using sb.ToString(0, delimiterIndex) and sb.Remove(0, delimiterIndex) until no delimiters remain.

This would also cover the case when two messages are received simultaneously.

C.Evenhuis
  • 25,996
  • 2
  • 58
  • 72