org/sonews/daemon/NNTPDaemon.java
author chris <chris@marvin>
Fri Jun 26 16:48:50 2009 +0200 (2009-06-26)
changeset 1 6fceb66e1ad7
child 3 2fdc9cc89502
permissions -rw-r--r--
Hooray... sonews/0.5.0 final

HG: Enter commit message. Lines beginning with 'HG:' are removed.
HG: Remove all lines to abort the collapse operation.
     1 /*
     2  *   SONEWS News Server
     3  *   see AUTHORS for the list of contributors
     4  *
     5  *   This program is free software: you can redistribute it and/or modify
     6  *   it under the terms of the GNU General Public License as published by
     7  *   the Free Software Foundation, either version 3 of the License, or
     8  *   (at your option) any later version.
     9  *
    10  *   This program is distributed in the hope that it will be useful,
    11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13  *   GNU General Public License for more details.
    14  *
    15  *   You should have received a copy of the GNU General Public License
    16  *   along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17  */
    18 
    19 package org.sonews.daemon;
    20 
    21 import org.sonews.util.Log;
    22 import java.io.IOException;
    23 import java.net.BindException;
    24 import java.net.InetSocketAddress;
    25 import java.net.ServerSocket;
    26 import java.nio.channels.CancelledKeyException;
    27 import java.nio.channels.ClosedChannelException;
    28 import java.nio.channels.SelectionKey;
    29 import java.nio.channels.Selector;
    30 import java.nio.channels.ServerSocketChannel;
    31 import java.nio.channels.SocketChannel;
    32 
    33 /**
    34  * NNTP daemon using SelectableChannels.
    35  * @author Christian Lins
    36  * @since sonews/0.5.0
    37  */
    38 public final class NNTPDaemon extends AbstractDaemon
    39 {
    40 
    41   public static final Object RegisterGate = new Object();
    42   
    43   private static NNTPDaemon instance = null;
    44   
    45   public static synchronized NNTPDaemon createInstance(int port)
    46   {
    47     if(instance == null)
    48     {
    49       instance = new NNTPDaemon(port);
    50       return instance;
    51     }
    52     else
    53     {
    54       throw new RuntimeException("NNTPDaemon.createInstance() called twice");
    55     }
    56   }
    57   
    58   private int port;
    59   
    60   private NNTPDaemon(final int port)
    61   {
    62     Log.msg("Server listening on port " + port, false);
    63     this.port = port;
    64   }
    65 
    66   @Override
    67   public void run()
    68   {
    69     try
    70     {
    71       // Create a Selector that handles the SocketChannel multiplexing
    72       final Selector readSelector  = Selector.open();
    73       final Selector writeSelector = Selector.open();
    74       
    75       // Start working threads
    76       final int workerThreads = Runtime.getRuntime().availableProcessors() * 4;
    77       ConnectionWorker[] cworkers = new ConnectionWorker[workerThreads];
    78       for(int n = 0; n < workerThreads; n++)
    79       {
    80         cworkers[n] = new ConnectionWorker();
    81         cworkers[n].start();
    82       }
    83       
    84       ChannelWriter.getInstance().setSelector(writeSelector);
    85       ChannelReader.getInstance().setSelector(readSelector);
    86       ChannelWriter.getInstance().start();
    87       ChannelReader.getInstance().start();
    88       
    89       final ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
    90       serverSocketChannel.configureBlocking(true);  // Set to blocking mode
    91       
    92       // Configure ServerSocket; bind to socket...
    93       final ServerSocket serverSocket = serverSocketChannel.socket();
    94       serverSocket.bind(new InetSocketAddress(this.port));
    95       
    96       while(isRunning())
    97       {
    98         SocketChannel socketChannel;
    99         
   100         try
   101         {
   102           // As we set the server socket channel to blocking mode the accept()
   103           // method will block.
   104           socketChannel = serverSocketChannel.accept();
   105           socketChannel.configureBlocking(false);
   106           assert socketChannel.isConnected();
   107           assert socketChannel.finishConnect();
   108         }
   109         catch(IOException ex)
   110         {
   111           // Under heavy load an IOException "Too many open files may
   112           // be thrown. It most cases we should slow down the connection
   113           // accepting, to give the worker threads some time to process work.
   114           Log.msg("IOException while accepting connection: " + ex.getMessage(), false);
   115           Log.msg("Connection accepting sleeping for seconds...", true);
   116           Thread.sleep(5000); // 5 seconds
   117           continue;
   118         }
   119         
   120         final NNTPConnection conn;
   121         try
   122         {
   123           conn = new NNTPConnection(socketChannel);
   124           Connections.getInstance().add(conn);
   125         }
   126         catch(IOException ex)
   127         {
   128           Log.msg(ex.getLocalizedMessage(), false);
   129           socketChannel.close();
   130           continue;
   131         }
   132         
   133         try
   134         {
   135           SelectionKey selKeyWrite =
   136             registerSelector(writeSelector, socketChannel, SelectionKey.OP_WRITE);
   137           registerSelector(readSelector, socketChannel, SelectionKey.OP_READ);
   138           
   139           Log.msg("Connected: " + socketChannel.socket().getRemoteSocketAddress(), true);
   140 
   141           // Set write selection key and send hello to client
   142           conn.setWriteSelectionKey(selKeyWrite);
   143           conn.println("200 " + Config.getInstance().get(Config.HOSTNAME, "localhost")
   144               + " " + Main.VERSION + " news server ready - (posting ok).");
   145         }
   146         catch(CancelledKeyException cke)
   147         {
   148           Log.msg("CancelledKeyException " + cke.getMessage() + " was thrown: " 
   149             + socketChannel.socket(), false);
   150         }
   151         catch(ClosedChannelException cce)
   152         {
   153           Log.msg("ClosedChannelException " + cce.getMessage() + " was thrown: " 
   154             + socketChannel.socket(), false);
   155         }
   156       }
   157     }
   158     catch(BindException ex)
   159     {
   160       // Could not bind to socket; this is a fatal problem; so perform shutdown
   161       ex.printStackTrace();
   162       System.exit(1);
   163     }
   164     catch(IOException ex)
   165     {
   166       ex.printStackTrace();
   167     }
   168     catch(Exception ex)
   169     {
   170       ex.printStackTrace();
   171     }
   172   }
   173   
   174   public static SelectionKey registerSelector(final Selector selector,
   175     final SocketChannel channel, final int op)
   176     throws CancelledKeyException, ClosedChannelException
   177   {
   178     // Register the selector at the channel, so that it will be notified
   179     // on the socket's events
   180     synchronized(RegisterGate)
   181     {
   182       // Wakeup the currently blocking reader/writer thread; we have locked
   183       // the RegisterGate to prevent the awakened thread to block again
   184       selector.wakeup();
   185       
   186       // Lock the selector to prevent the waiting worker threads going into
   187       // selector.select() which would block the selector.
   188       synchronized (selector)
   189       {
   190         return channel.register(selector, op, null);
   191       }
   192     }
   193   }
   194   
   195 }