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