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