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: