
Introduction
This article describes a component that supports operations such as connecting to a news server, retrieving a list of newsgroups, and retrieving message headers from newsgroups using the Network News Transfer Protocol (NNTP).
Background
My friends and I have newsgroups on several different newsgroup servers. From time to time (especially at the end of each month), we would like to find out how many people have been posting in our newsgroups and how many messages did each of them post. We were looking at a simple program that can poll the newsgroup we specified, collect information on messages available and generate some report or table. Finally, I've decided to write the program myself and the very first thing that I ran into is the NNTP.
Using the code
There is another article on Code Project by Ly Hoang Hai on retrieving newsgroups from a news server. Since the protocol is the same, I am skipping some duplicate material such as how to send commands to the server via network stream, which has already been covered by him. What is different is that I packaged the functionalities I need into a NNTP
class that inherits from the Component
class. By doing so, I could simply drag the NNTP component from the toolbox and reuse it in my later projects. The NNTP
class also publishes a few events to notify others of the connection/header retrieval process. The NNTP
class and the Newsgroup
class (which contains information about the current selected newsgroup) has the following fields:
public class NNTP : System.ComponentModel.Component
{
protected TcpClient tcpServer;
private string serverName = string.Empty;
private bool isConnected = false;
private int timeout = 30000;
private bool isPostingAllowed = false;
protected Newsgroup newsgroup;
private bool enableEvents = true;
...
}
public class Newsgroup
{
private string groupName;
private int numberPosts;
private int lowWatermark;
private int highWatermark;
...
}
Each of the private fields in each class has a public property corresponding to them.
Many of the functions of the NNTP
class such as Connect()
and ListNewsgroup()
are self-explanatory. Rather then going through each of them, I would like to draw your attention to the DownloadHeaders
function that retrieves message headers from the servers. First, a simple data structure, ArticleHeader
, is needed to hold the retrieved information. The following code is part of its definition. Notice that some of the information retrieved from the server such as Message-ID and References header are not included. You can modify this class and the corresponding sections in DownloadHeaders
to suit your project needs.
public class ArticleHeader
{
protected string articleID;
protected string from;
protected string subject;
protected string dateString;
...
}
The private version of DownloadHeaders
is listed below. In the real world, you only have to call one of its public version and pass to the function, the range of article IDs that you would like to retrieve. To retrieve message headers from the newsgroup, I used the XOVER
command in the function. This will just give me the header information, not the whole article, but that's exactly what I need. Here comes the tricky part. If you look at the source code of the function, you will notice that the DownloadHeaders
function only returns a value indicating whether the retrieval succeeded (int.Max
), failed (-1), or stalled due to a null response. The actual header information is returned to the caller one by one through the OnDownLoadHeaderOne
event. Therefore, you have to have an event handler attached to this event to get the headers.
private int DownloadHeaders(string XOVERCommand)
{
NetworkStream ns = this.tcpServer.GetStream();
StreamReader sr = new StreamReader(ns, System.Text.Encoding.Default);
StreamWriter sw = new StreamWriter(ns);
sw.AutoFlush = true;
sw.WriteLine(XOVERCommand);
string response = sr.ReadLine();
int headerCount = 0;
if ( response == null )
{
if ( this.OnDownloadHeaderFailed != null && this.enableEvents )
this.OnDownloadHeaderFailed( this, new EventArgs() );
return headerCount;
}
switch( response.Substring(0,3) )
{
case "224":
bool done = false;
string article;
do
{
article = sr.ReadLine();
if ( article == null )
{
if ( this.OnDownloadHeaderFailed != null
&& this.enableEvents )
this.OnDownloadHeaderFailed
( this, new EventArgs() );
return headerCount;
}
if ( article == "." )
done = true;
else
{
article.Replace("\t\t", "\t<empty>\t");
string[] fields = article.Split( '\t' );
ArticleHeader h = new ArticleHeader();
h.ArticleID = fields[0];
h.Subject = Utils.DecodeHeaderString(fields[1]);
h.From = Utils.DecodeHeaderString(fields[2]);
h.DateString = fields[3];
headerCount++;
if ( this.enableEvents &&
this.OnDownLoadHeaderOne != null )
this.OnDownLoadHeaderOne(this, h);
}
} while (!done);
if ( this.OnDownloadHeaderSucceeded != null
&& this.enableEvents )
this.OnDownloadHeaderSucceeded
( this, new EventArgs() );
headerCount = int.MaxValue;
break;
case "412":
case "420":
case "423":
if (this.OnDownloadHeaderFailed != null
&& this.enableEvents )
this.OnDownloadHeaderFailed
( this, new EventArgs() );
headerCount = -1;
break;
default:
throw new Exception
("XOVER : Unexpected response from server. Received: "
+ response);
}
return headerCount;
}
Reminder: If you modify the ArticleHeader
class shown earlier, you will also have to modify this function to match your changes.
The NNTP
also publishes quite a few number of events such as OnConnectStart
and OnListNewsgroupDone
. By attaching different event handlers to these events, you would be able to let the user know exactly what is going on with the undergoing connection/retrieval operation.
Tips
2 tips I learned from my later projects. First, be careful of what you ask for, when calling DownloadHeaders()
with no argument. Since the function will then attempt to retrieve all the available headers from the server, it might take some time for the process to finish depending on the number of messages and your connection speed. Also check the return value to see if your StreamReader
read a null value. If so, you can use ReconnectNewsgroup()
to reestablish the connection to the newsgroup.
Second, you can setup a nice progress bar to indicate the retrieval process. You already know how many messages you are retrieving. All you have to do next is increment the progress bar's value in the event handler of the OnDownLoadHeaderOne
event. This way, your user will know that something is indeed going on.
About the demo
The demo shows this NNTP component in action. You enter a news server, hit Connect, select a newsgroup and click Retrieve. The result is that you get the 5 most recent messages available on the newsgroup you specified. If you get less than 5, it may be because some of the messages have been removed from the newsgroup. If they aren't there, we can't retrieve them.