7 Comments
Advert: IRC7 is theIRC-client for Windows Phone 7. Learn more at www.softwaremk.org/irc.

This is the part III of a tutorial series which will describe the WP7’s sockets-support from a developer’s perspective. This tutorial series will focus on developing WP7 applications which require a long running TCP-connection that send and receive text-based data. In these posts we will go through of building a complete IRC-client.

The focus of these posts is in the WP7’s developer’s perspective. If you want to better understand the inner details of the Internet sockets, Wikipedia has a good introduction on the subject.

We’re almost there. We have opened the connection to the server and we have also received some messages from it. But, we must identify ourselves to the server which we’re not doing yet. This means that we’re not following the IRC-specification, making the server close our connection almost immediately. In this tutorial we will start sending messages to the server.

Background

The IRC-specification states the following:

The recommended order for a client to register is as follows:

  1. Pass message
  2. Nick message
  3. User message

… The PASS command is used to set a 'connection password'.

… NICK message is used to give user a nickname or change the previous one.

… The USER message is used at the beginning of connection to specify the username, hostname, servername and realname of s new user. It is also used in communication between servers to indicate new user arriving on IRC, since only after both USER and NICK have been received from a client does a user become registered.

The server is currently disconnecting us because we’re not sending the required messages to the server. In this tutorial we can skip the PASS-message completely, because it’s only required when connecting to a password protected server. But, we have to send the NICK and USER-messages.

The syntax for the messages are listed in the specification:

Command:    NICK
Parameters:    <nickname>

Command:    USER
Parameters:    <username> <hostname> <servername> <realname>

The code

The server is now waiting for us to identify ourselves. We do this by sending the NICK and USER-messages with the correct parameters. Sending a message to the server happens through our Socket-instance’s SendAsync-method. Similar to receiving a message,  we need a buffer and a SocketAsyncEventArgs-class to do this. We could handle the Completed-event from the SocketAsyncEventArgs-instance to get a notification when the message has been send but we’re going to skip this part: If the connection is open, there’s no reason for the sending to fail.

        public void SendToServer(string message)
        {
            var asyncEvent = new SocketAsyncEventArgs { RemoteEndPoint = new DnsEndPoint(server, serverPort) };

            var buffer = Encoding.UTF8.GetBytes(message + Environment.NewLine);
            asyncEvent.SetBuffer(buffer, 0, buffer.Length);

            connection.SendAsync(asyncEvent);
        }

Every message we send must end with a newline.If the newline is missing, the server won’t process the message and this can cause problems which are hard to debug. Because of this we always add the newline to the message inside the SendToServer-message.

The NICK-message

Every user in the IRC-sever has a nick. This is their unique ID and also a display name which other users see. Sending the NICK-message to the server sets your  unique nick name. Currently our IrcClient.cs raises the CreateConnectionCompleted-event when a connection has been established to the server and we use this event to notify our application so that it can ask the IrcClient to send our NICK to the server.

        private void OnCreateConnectionCompleted(object sender, CreateConnectionAsyncArgs e)
        {
            if (!e.ConnectionOk)
            {
                UpdateStatus("Connection failed");
                return;
            }

            UpdateStatus("Connection OK");

            SendCredentialsToServer();
        }

        private void SendCredentialsToServer()
        {
            var myNickName = "myNick " + DateTime.Now.Millisecond;

            client.SendToServer("NICK " + myNickName);
        }
The USER-message

Sending the NICK-message isn’t enough because the server expects to see the USER-message also. The syntax for this message is little harder: <username> <hostname> <servername> <realname>. Especially the hostname and servername –parameters may be hard to figure out. But, things get easier when we figure out that those parameters aren’t actually needed and one can hardcode the values ‘0’ and ‘*’ instead. So the only parameters we have to deal with are the username and the realname.

Many people use their email-addresses as their realname. And even more people use their nicknames in both the username and realname variables. It’s up to you (or the user) to decide how much info you want to give out of yourself to the other users. We’re going to use our nickname in both of the parameters:

        private void SendCredentialsToServer()
        {
            var myNickName = "myNick " + DateTime.Now.Millisecond;
            client.SendToServer("NICK " + myNickName);

            var userMessage = string.Format("USER {0} 0 * :{1}", myNickName, myNickName);
            client.SendToServer(userMessage);
        }

Almost there

Based on our previous knowledge this should be enough. We’re sending the NICK-message and the USER-message. And in some cases this would be enough but because we are trying to connect to a QuakeNet-server, it isn’t. If you execute the code now you will see some new messages. First, there’s a PING-message with a random number after it. And then, little later the server will close the connection because “Your client may not be compatible with this server.”

image

The problem is that the server expects us to answer to the PING-message. This is done by sending it a PONG-message with the same random number it sent us. This doesn’t happen only when connecting to the server: As long as you are connected to the server, you will receive these PING-messages from time to time. And you always have to respond to them or the server will close the connection.

Some servers send the PING-message withoutthe ‘:’-character. This is one of the big problems when creating a well-working IRC-client: There’s these small differences between the servers which makes parsing the messages rather hard.

So, we must read the number from the PING-message and send the correct PONG-message to the server. For this we’re going to create a new MessageObserver-class which will register to the client’s IrcMessageReceivedFromServer-event, parse the received PING-message and send the answer. First we create the new class:

    public class MessageObserver
    {
        private readonly IrcClient client;

        public MessageObserver(IrcClient client)
        {
            this.client = client;

            this.client.IrcMessageReceivedFromServer += OnIrcMessageReceivedFromServer;
        }

        private void OnIrcMessageReceivedFromServer(object sender, IrcMessageReceivedFromServer e)
        {
        }
    }

And then we create a one instance of it in the same place where the IrcClient-instance is created, in our MainPage’s constructor:

        public MainPage()
        {
            InitializeComponent();

            this.client = new IrcClient();
            this.client.CreateConnectionCompleted += OnCreateConnectionCompleted;

            this.observer = new MessageObserver(client);
        }

Now we just need to create the required parsing logic for the PING-message:

        private void OnIrcMessageReceivedFromServer(object sender, IrcMessageReceivedFromServer e)
        {
            if (string.IsNullOrWhiteSpace(e.Message))
                return;

            if (e.Message.IndexOf("PING :") == 0)
            {
                HandlePing(e.Message);
                return;
            }
        }

        private void HandlePing(string message)
        {
            var index = message.LastIndexOf(":");
            var pingNumber = message.Substring(index + 1);

            var pongMessage = string.Format("PONG :{0}", pingNumber);
            client.SendToServer(pongMessage);
        }

Current functionality

imageAfter these modifications our app is ready from the server connection’s point of view. We can connect to the server and send and receive messages with it. We’re now in point where the server sends us the MOTD (message of the day) message, welcoming us to the server.

Because our app can respond to the PING-messages, it can stay connected to the server as long as it wants. The app isn’t very useful yet because it outputs only into the Visual Studio’s output-window and it doesn’t have a textbox for sending messages to the server. But we have the framework on which we can build on. It’s now just about adding more features.

Next steps

We are off to a good start but there’s still many topics to discuss about:

  • Debugging the socket-connections
  • Adding some basic functions: Joining a channel and sending a message to it
  • Handling application switching / tombstoning

Source code

The whole source code for this tutorial is available from the GitHub.

Links