Developing an NNTP Newsgroup Reader

Learn how to develop a C# application that retrieves the available newsgroups from an NNTP server and then displays the articles from those specific newsgroups.

Latest technology meets 1986 ARPA-Internet
In this tutorial we’re going to develop an application that communicates through a transfer protocol developed over 20 years ago but that is still commonly used to this day. NNTP is the Network News Transfer Protocol that’s responsible for all those Usenet articles but also the protocol used by news.microsoft.com – a large message board for developers and other IT professionals using Microsoft technologies. But let’s move on to coding the actual thing.

Start Visual Studio 2005 and create a new C# Windows Application. Add four TextBoxes to it, a ComboBox, and three Buttons: txtNNTPServer, txtLog, txtHead, txtBody, cmbNewsgroups, btnGo, btnGetNews and finally btnNextArticle. Except for txtNNTPServer, the other three TextBoxes should have the Multiline property set to True . Arrange your form to look similar to this one below:

Now we can do some coding. A using statement for the System.Net.Sockets namespace is the first thing:

using System.Net.Sockets;

Now inside the class we’re going to create some of the objects that we’re going to use:

// Used for receiving info

byte[] downBuffer = new byte[2048];

// Used for sending commands

byte[] byteSendInfo = new byte[2048];

// Used for connecting a socket to the NNTP server

TcpClient tcpClient;

// Used for sending and receiving information

NetworkStream strRemote;

// Stores various responses

string Response;

// Number of bytes in the buffer

int bytesSize;

// Stores the ID of the first message in a newsgroup

int firstID;

// Stores the ID of the last message in a newsgroup

int lastID;

// Stores chunks of the articles from the buffer

string NewChunk;

We’re planning to send written commands to the NNTP server, but the NNTP server expects a stream of bytes. Thus, in order to send commands to the server we need to create a simple method that will handle this conversion:

public static byte[] StringToByteArr(string str)

{

    System.Text.ASCIIEncoding encoding = new System.Text.ASCIIEncoding();

    return encoding.GetBytes(str);

}

The first real action that the application takes is when the btnGo button gets clicked. The following piece of code will connect to the actual NNTP server specified in the txtNNTPServer TextBox. It then sends a LIST command to the server, which in returns responds with a list of the newsgroups available. The list of newsgroups however needs to be looped line by line before it can be used, so that we can put the appropriate values in the ComboBox.

private void btnGo_Click(object sender, EventArgs e)

{

    // Open the socket to the server

    tcpClient = new System.Net.Sockets.TcpClient(txtNNTPServer.Text, 119);

    strRemote = tcpClient.GetStream();

    // Read the bytes

    bytesSize = strRemote.Read(downBuffer, 0, 2048);

    // Retrieve the response

    Response = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);

    // Just as in HTTP, if code 200 is not returned, something's not right

    if (Response.Substring(0, 3) != "200")

    {

        MessageBox.Show("The server returned an unexpected response.", "Connection failed", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

    // Show the response

    txtLog.Text = Response + "\n";

 

    // Make the request to list all newsgroups

    byteSendInfo = StringToByteArr("LIST\r\n");

    strRemote.Write(byteSendInfo, 0, byteSendInfo.Length);

    Response = "";

 

    // Loop to retrieve a list of newsgroups

    while ((bytesSize = strRemote.Read(downBuffer, 0, downBuffer.Length)) > 0)

    {

        // Get the chunk of string

        NewChunk = Encoding.ASCII.GetString(downBuffer, 0, bytesSize);

        Response += NewChunk;

        // If the string ends in a "\r\n.\r\n" then the list is over

        if (NewChunk.Substring(NewChunk.Length - 5, 5) == "\r\n.\r\n")

        {

            // Remove the "\r\n.\r\n" from the end of the string

            Response = Response.Substring(0, Response.Length - 3);

            break;

        }

    }

    // Split lines into an array

    string[] ListLines = Response.Split('\n');

    // Loop line by line

    foreach (String ListLine in ListLines)

    {

        // If the response starts with 215, it's the line that indicates the status

        if (ListLine.Length > 3 && ListLine.Substring(0, 3) == "215")

        {

            // Add the status response line to the log window

            txtLog.Text += ListLine + "\r\n";

        }

        else

        {

            // Add the newsgroup to the combobox

            string[] Newsgroup = ListLine.Split(' ');

            cmbNewsgroups.Items.Add(Newsgroup[0]);

        }

    }

}

At this point we’re done with the code that retrieves the newsgroups. The next button that should be clicked is btnGetNews which – using the GROUP command followed by the ID of the desired newsgroup – will retrieve certain information about that newsgroup, more importantly the ID of the first article and of the last article. This will help us get the actual articles of that newsgroup, since we’re going to retrieve them by ID.

private void btnGetNews_Click(object sender, EventArgs e)

{

    // If a newsgroup is selected in the ComboBox

    if (cmbNewsgroups.SelectedIndex != -1)

    {

        // Request a certain newsgroup

        byteSendInfo = StringToByteArr("GROUP " + cmbNewsgroups.SelectedItem.ToString() + "\r\n");

        strRemote.Write(byteSendInfo, 0, byteSendInfo.Length);

        Response = "";

        bytesSize = strRemote.Read(downBuffer, 0, 2048);

        Response = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);

        // Split the information about the newsgroup by blank spaces

        string[] Group = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize).Split(' ');

        // Show information about the newsgroup in the txtLog TextBox

        Response += Group[1] + " messages in the group (messages " + Group[2] + " through " + Group[3] + ")\r\n";

        txtLog.Text += Response;

        Response = "";

        // The ID of the first article in this newsgroup

        firstID = Convert.ToInt32(Group[2]);

        // The ID of the last article in this newsgroup

        lastID = Convert.ToInt32(Group[3]);

    }

    else

    {

        MessageBox.Show("Please connect to a server and select a newsgroup from the dropdown list first.", "Newsgroup retrieval", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

}

Believe it or not, all that's left is the actual retrieveal of the message/article. The ARTICLE command sounds like the straightforward solution to this, however that will return the header and body of the message into a single piece of string, and most of the time you'll want to retrieve these two separately. This can be easily done by sending the HEAD and BODY commands, each followed by the ID of the article you wish to retrieve. We start with the latest message moving on to the older ones with each click of btnNext:
private void btnNext_Click(object sender, EventArgs e)

{

    if (tcpClient != null && tcpClient.Connected == true && firstID >= 0)

    {

        // Get the header

        txtHead.Text = "";

        // Initialize the buffer to 2048 bytes

        downBuffer = new byte[2048];

        // Request the headers of the article

        byteSendInfo = StringToByteArr("HEAD " + firstID + "\r\n");

        // Send the request to the NNTP server

        strRemote.Write(byteSendInfo, 0, byteSendInfo.Length);

        while ((bytesSize = strRemote.Read(downBuffer, 0, downBuffer.Length)) > 0)

        {

            NewChunk = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);

            txtHead.Text += NewChunk;

            // If the last thing in the buffer is "\r\n.\r\n" the message's finished

            if (NewChunk.Substring(NewChunk.Length - 5, 5) == "\r\n.\r\n")

            {

                break;

            }

        }

 

        // Get the body

        txtBody.Text = "";

        // Initialize the buffer to 2048 bytes

        downBuffer = new byte[2048];

        // Request the headers of the article

        byteSendInfo = StringToByteArr("BODY " + firstID + "\r\n");

        // Send the request to the NNTP server

        strRemote.Write(byteSendInfo, 0, byteSendInfo.Length);

        while ((bytesSize = strRemote.Read(downBuffer, 0, downBuffer.Length)) > 0)

        {

            NewChunk = System.Text.Encoding.ASCII.GetString(downBuffer, 0, bytesSize);

            txtBody.Text += NewChunk;

            // If the last thing in the buffer is "\r\n.\r\n" the message's finished

            if (NewChunk.Substring(NewChunk.Length - 5, 5) == "\r\n.\r\n")

            {

                break;

            }

        }

 

        // Ready for the next article, unless there is nothing else there...

        if (firstID <= lastID)

        {

            firstID++;

        }

    }

    else

    {

        MessageBox.Show("Please select a newsgroup from the dropdown list and click on 'Get News' first.", "Newsgroup retrieval", MessageBoxButtons.OK, MessageBoxIcon.Error);

    }

}

Well, that’s all there is to retrieving the newsgroups and messages from an NNTP server. From this point on to creating a full blown NNTP reader there is plenty of parsing to be done in the headers so as to arrange the messages in a meaningful manner. Also another command that’s not covered in this tutorial because it’s simple enough to integrate is POST which allows the posting of a message in a newsgroup, as a new subject or as a reply to existing messages.

To get more information on NNTP, read the protocol’s reference dated February 1986: http://www.ietf.org/rfc/rfc977.txt

And here’s our little application in action:

Nathan Pakovskie is an esteemed senior developer and educator in the tech community, best known for his contributions to Geekpedia.com. With a passion for coding and a knack for simplifying complex tech concepts, Nathan has authored several popular tutorials on C# programming, ranging from basic operations to advanced coding techniques. His articles, often characterized by clarity and precision, serve as invaluable resources for both novice and experienced programmers. Beyond his technical expertise, Nathan is an advocate for continuous learning and enjoys exploring emerging technologies in AI and software development. When he’s not coding or writing, Nathan engages in mentoring upcoming developers, emphasizing the importance of both technical skills and creative problem-solving in the ever-evolving world of technology. Specialties: C# Programming, Technical Writing, Software Development, AI Technologies, Educational Outreach

Leave a Reply

Your email address will not be published. Required fields are marked *

Back To Top