Click here to Skip to main content
Click here to Skip to main content

Handling basic NNTP commands

, 5 Aug 2003
Rate this:
Please Sign up or sign in to vote.
A C# class to wrap around some of the basic NNTP commands.

Sample Image - nntpcomponent.jpg

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
{
    // TCP Connection to the Newsgroup Server
    protected TcpClient tcpServer;            
    // Name for the current connected news server
    private string serverName = string.Empty; 
    // Boolean variable indicating whether the connection is active
    private bool isConnected = false;
    // Number of Millisecond for server timeout, default = 30 sec
    private int timeout = 30000;
    // whether posting to the news server is allowed
    private bool isPostingAllowed = false;
    // Current selected newsgroup
    protected Newsgroup newsgroup;
    // whether events are fired
    private bool enableEvents = true;
    ...
}


public class Newsgroup
{
    // Name of the Newsgroup
    private string groupName;
    // Estimated Number of posts in the newsgroup
    private int numberPosts;
    // Article numbers of the first available article
    private int lowWatermark;
    // Article numbers of the last available article    
    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;  // Article ID of the newsgroup message
    protected string from;       // Sender of the message
    protected string subject;    // Subject of the message
    protected string dateString; // Date the message was sent.   
    ...    
}

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)
{
    // Setup the network stream
    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);       // Execute the XOVER command
    string response = sr.ReadLine();  // Retrieve Response
    
    int headerCount = 0;
    if ( response == null )           // NULL response
    {
        if ( this.OnDownloadHeaderFailed != null && this.enableEvents )
            this.OnDownloadHeaderFailed( this, new EventArgs() );
        return headerCount;
    }

    switch( response.Substring(0,3) )
    {
        case "224":               // Valid command, article list to follow
            bool done = false;
            string article;
            do
            {
                article = sr.ReadLine();  // Read the Header of the article
                if ( article == null )    // NULL response 
                {
                    if ( this.OnDownloadHeaderFailed != null 
                                   && this.enableEvents )
                        this.OnDownloadHeaderFailed
                             ( this, new EventArgs() );
                    return headerCount;
                }    

                if ( article == "." )    // Check for finish
                    done = true;
                else
                {
                    // take care of empty fields
                    article.Replace("\t\t", "\t<empty>\t");
                    // break up the server response
                    string[] fields = article.Split( '\t' );
                    
                    // fill in header
                    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":        // 412 No newsgroup selected
        case "420":        // 420 Current article number is invalid
        case "423":        // 423 No articles in that range
            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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

TY Lee

United States United States
No Biography provided

Comments and Discussions

 
QuestionHow to add Username And Password Support PinmemberExtremest27-May-06 9:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141223.1 | Last Updated 6 Aug 2003
Article Copyright 2003 by TY Lee
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid