In my previous post we created a MessageConnection to be used with our IRC-library. The MessageConnection in it self might be used for other things than IRC though, but for but our goal is to implement it in our IRC-library. However, as I might have stated in my previous post, the code posted there was never tested. Thus, the likeliness of there being bugs is pretty high, and as one would suspect I found several of them. Some of the bugs were pretty trivial (like forgetting to append CRLF to the messages we send to the server), but others were more severe. Let’s start by fixing the first one, which was that we need to append CRLF to all messages sent from our application. To fix this is fairly simple, just update the Send-method to look like this:
/// <summary> /// Sends the specified message. /// </summary> /// <param name="message">The message.</param> public void Send(byte[] message) { message = RemoveCrLf(message); var buffer = new byte[message.Length + 2]; message.CopyTo(buffer, 0); buffer[buffer.Length - 2] = 13; // CR buffer[buffer.Length - 1] = 10; // LF logger.Debug(">> {0}", Encoding.UTF8.GetString(message)); lock (sendBuffer) sendBuffer.Write(buffer, 0, buffer.Length); TransferData(null); }
The next couple of bugs are a bit worse. They concern the DataReceived-method, and two of them is about copying arrays, and the last one is about what happens when the connection to the other end is lost. To tackle the problem with lost connections I’ve added a event called “Disconnect”, and a “OnDisconnect”-method (same as with the other events), and then I’ve added the following piece of code right after the EndReceive-call in DataReceived
if (receivedDataLength == 0) { logger.Debug("Disconnected"); client.Close(); OnDisconnect(); return; }
Next, in the same function you want to replace
buffer.CopyTo(data, bufferLength);
with
Array.Copy(buffer, 0, data, bufferLength, receivedDataLength);
and then you want to replace
byte[] message = new byte[i]; Array.Copy(data, msgStart, message, 0, i); // Copy data[msgStart:i] to message
with
byte[] message = new byte[i - msgStart]; Array.Copy(data, msgStart, message, 0, i - msgStart); // Copy data[msgStart:i] to message
After this is done we can move on to the next topic.
Asynchronism
As I might have stated earlier, I want dotRant to run async. There are several good reasons to this and I’ve already taken measures to make sure most of dotRant is async, but there is still one thing that can cause a freeze if it takes too long time. This can be bad, it will also make connecting to several servers at once take unnecessarily long time as we’d have to wait for each connection to finish before starting on the next. So, I’ve changed the Connect-methods to ConnectAsync-methods that returns a Task-instance, and then I’ve also created two new Connect-methods that runs synchronously, that use our async implementations and wait for their completion. The code is pretty simple and self-explanatory, so I’m just gone post it.
/// <summary> /// Connects the specified end point. /// </summary> /// <param name="endPoint">The end point.</param> /// <param name="secureConnection">if set to <c>true</c> connects using ssl.</param> public Task ConnectAsync(IPEndPoint endPoint, bool secureConnection) { if (Connected) throw new InvalidOperationException("Cannot connect when already connected"); if (client == null) client = new TcpClient(); logger.Debug("Connecting to {0}:{1}", endPoint.Address, endPoint.Port); var asyncResult = client.BeginConnect(endPoint.Address, endPoint.Port, null, null); return Task.Factory.FromAsync(asyncResult, result => { client.EndConnect(result); if (!secureConnection) { networkStream = client.GetStream(); } else { if (String.IsNullOrWhiteSpace(hostname)) throw new InvalidOperationException("Can't use ssl without specifying a host-name"); networkStream = new SslStream(client.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCert), null); try { ((SslStream)networkStream).AuthenticateAsClient(hostname); } catch (AuthenticationException ex) { logger.Warn("Certificate not accepted, exception: {0}", ex); throw ex; } } messagesBuffer = new MemoryStream(); sendBuffer = new MemoryStream(); networkStream.BeginRead(buffer, 0, buffer.Length, new AsyncCallback(DataReceived), null); }); } /// <summary> /// Connects the specified hostname. /// </summary> /// <param name="hostname">The hostname.</param> /// <param name="port">The port.</param> /// <param name="secureConnection">if set to <c>true</c> connects using ssl.</param> public Task ConnectAsync(string hostname, int port, bool secureConnection) { this.hostname = hostname; return ConnectAsync(new IPEndPoint(Dns.GetHostAddresses(hostname)[0], port), secureConnection); } /// <summary> /// Connects the specified end point. /// </summary> /// <param name="endPoint">The end point.</param> /// <param name="secureConnection">if set to <c>true</c> connects using ssl.</param> public void Connect(IPEndPoint endPoint, bool secureConnection) { ConnectAsync(endPoint, secureConnection).Wait(); } /// <summary> /// Connects the specified hostname. /// </summary> /// <param name="hostname">The hostname.</param> /// <param name="port">The port.</param> /// <param name="secureConnection">if set to <c>true</c> connects using ssl.</param> public void Connect(string hostname, int port, bool secureConnection) { ConnectAsync(hostname, port, secureConnection).Wait(); }
Should you find any mistakes, or have any questions, please contact me or leave a comment. The entire source-code can be found on link below:
https://github.com/Alxandr/dotRant/tree/part-3/src/dotRant
Until then. Alxandr.
