views:

45

answers:

2
var buffer = new byte[short.MaxValue];
var splitString = new string[] {"\r\n"};
while (_tcpClient.Connected)
{  
  if (!_networkStream.CanRead || !_networkStream.DataAvailable)
    continue;

  var bytesRead = _networkStream.Read(buffer, 0, buffer.Length);
  var stringBuffer = Encoding.ASCII.GetString(buffer, 0, bytesRead);
  var messages = 
     stringBuffer.Split(splitString, StringSplitOptions.RemoveEmptyEntries);
  foreach (var message in messages)
  {
    if (MessageReceived != null)
    {
      MessageReceived(this, new SimpleTextClientEventArgs(message));
    }
  }
}

Problem is that even with a buffer as big as short.MaxValue, you can actually fill the buffer. When you split the string that you create from the buffer, the last string gets chomped, and the rest of it comes with the next read.

I was thinking of creating a buffer large enough for a single line (which according to RFC2812 is 512 chars), extracting a substring up until the first "\r\n", then array-copying the rest of the data to the beginning of the buffer and using the offset parameter to read more data onto the end of the data that wasn't extracted last iteration. Sorry if that was hard to follow...

Is that the best solution, or am I missing the obvious here?

+1  A: 

You're dealing with TCP/IP, which means you're dealing with stream data. You must not rely on how the data comes in terms of whether one call to Read will give you the whole of the data or not. In a case like this, you probably want to just keep reading (it will block until there's some data) and find convert the binary data into a text buffer. When you see a line terminator in the text buffer, you can notify the higher level of that message, and remove it from the buffer... but don't assume anything about what comes after that message. You may well still have more data to read.

As a side-note, is IRC really only ASCII? If so, that at least makes things a bit simpler...

Jon Skeet
-This is what it says:-"No specific character set is specified. The protocol is based on a set of codes which are composed of eight (8) bits, making up an octet. Each message may be composed of any number of these octets; however, some octet values are used for control codes, which act as message delimiters."-But I know there have been undocumented/unclearly documented enhancements to the IRC protocol, so I would like to make things as robust as possible. I think your suggestion is what I'm suggesting in my final paragraph
carlsb3rg
@carlsb3rg: Yes, it sounds like you might be thinking along the same lines as me. It sounds like the protocol is completely broken if it's not specifying an encoding though... and by assuming ASCII you could easily be losing important data. In particular, it sounds like you should be looking through the byte array for the message delimiter rather than the decoded text.
Jon Skeet
A: 

So here's how I ended up solving it:

var buffer = new byte[Resources.MaxBufferSize];
var contentLength = 0;
while (_tcpClient.Connected)
{
  if (!_networkStream.CanRead || !_networkStream.DataAvailable)
    continue;

  var bytesRead = _networkStream.Read(buffer, contentLength, buffer.Length - contentLength - 1);
  contentLength += bytesRead;
  var message = string.Empty;
  do
  {
    message = ExtractMessage(ref buffer, ref contentLength);
    if (!String.IsNullOrEmpty(message))
    {
      if (MessageReceived != null)
      {
        MessageReceived(this, new SimpleTextClientEventArgs(message));
      }                        
    }
  } while (message != string.Empty);
}

private string ExtractMessage(ref byte[] buffer, ref int length)
{
  var message = string.Empty;
  var stringBuffer = Encoding.UTF8.GetString(buffer, 0, length);
  var lineBreakPosition = stringBuffer.IndexOf(Resources.LineBreak);
  if (lineBreakPosition > -1)
  {
    message = stringBuffer.Substring(0, lineBreakPosition);
    var tempBuffer = new byte[Resources.MaxBufferSize];
    length = length - message.Length - Resources.LineBreak.Length;
    if (length > 0)
    {
      Array.Copy(buffer, lineBreakPosition + Resources.LineBreak.Length, tempBuffer, 0, length);
      buffer = tempBuffer;
    }
  }
  return message;
}
carlsb3rg