View Javadoc

1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   *
19   */
20  package org.apache.mina.transport.nio;
21  
22  import java.io.IOException;
23  import java.net.InetSocketAddress;
24  import java.net.SocketAddress;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.DatagramChannel;
27  import java.nio.channels.SelectionKey;
28  import java.util.Map;
29  import java.util.concurrent.ConcurrentHashMap;
30  
31  import org.apache.mina.api.IdleStatus;
32  import org.apache.mina.api.IoFuture;
33  import org.apache.mina.api.IoSession;
34  import org.apache.mina.api.MinaRuntimeException;
35  import org.apache.mina.service.executor.IoHandlerExecutor;
36  import org.apache.mina.service.executor.OrderedHandlerExecutor;
37  import org.apache.mina.service.idlechecker.IdleChecker;
38  import org.apache.mina.service.idlechecker.IndexedIdleChecker;
39  import org.apache.mina.transport.udp.AbstractUdpServer;
40  import org.apache.mina.transport.udp.UdpSessionConfig;
41  import org.slf4j.Logger;
42  import org.slf4j.LoggerFactory;
43  
44  /**
45   * This class implements a UDP NIO based server.
46   * 
47   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
48   */
49  public class NioUdpServer extends AbstractUdpServer implements SelectorListener {
50  
51      static final Logger LOG = LoggerFactory.getLogger(NioUdpServer.class);
52  
53      private static final boolean IS_DEBUG = LOG.isDebugEnabled();
54  
55      // the bound local address
56      private SocketAddress address = null;
57  
58      // used for detecting idle sessions
59      private final IdleChecker idleChecker = new IndexedIdleChecker();
60  
61      // the inner channel for read/write UDP datagrams
62      private DatagramChannel datagramChannel = null;
63  
64      // the key used for selecting read event
65      private SelectionKey readKey = null;
66  
67      // list of all the sessions by remote socket address
68      private final Map<SocketAddress /* remote socket address */, NioUdpSession> sessions = new ConcurrentHashMap<SocketAddress, NioUdpSession>();
69  
70      /** The selector loop used to incoming data */
71      private final SelectorLoop readSelectorLoop;
72  
73      /**
74       * Create an UDP server with a new selector pool of default size and a {@link IoHandlerExecutor} of default type (
75       * {@link OrderedHandlerExecutor})
76       */
77      public NioUdpServer() {
78          this(new NioSelectorLoop("accept", 0), null);
79      }
80  
81      /**
82       * Create an UDP server with a new selector pool of default size and a {@link IoHandlerExecutor} of default type (
83       * {@link OrderedHandlerExecutor})
84       * 
85       * @param sessionConfig The configuration to use for this server
86       */
87      public NioUdpServer(UdpSessionConfig config) {
88          this(config, new NioSelectorLoop("accept", 0), null);
89      }
90  
91      /**
92       * Create an UDP server with provided selector loops pool
93       * 
94       * @param acceptSelectorLoop the selector loop for handling accept events (connection of new session)
95       * @param readWriteSelectorLoop the pool of selector loop for handling read/write events of connected sessions
96       * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
97       *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
98       *        operations.
99       */
100     public NioUdpServer(SelectorLoop readSelectorLoop, IoHandlerExecutor handlerExecutor) {
101         super(handlerExecutor);
102         this.readSelectorLoop = readSelectorLoop;
103     }
104 
105     /**
106      * Create an UDP server with provided selector loops pool
107      * 
108      * @param sessionConfig The configuration to use for this server
109      * @param acceptSelectorLoop the selector loop for handling accept events (connection of new session)
110      * @param ioHandlerExecutor used for executing IoHandler event in another pool of thread (not in the low level I/O
111      *        one). Use <code>null</code> if you don't want one. Be careful, the IoHandler processing will block the I/O
112      *        operations.
113      */
114     public NioUdpServer(UdpSessionConfig config, SelectorLoop readSelectorLoop, IoHandlerExecutor handlerExecutor) {
115         super(config, handlerExecutor);
116         this.readSelectorLoop = readSelectorLoop;
117     }
118 
119     /**
120      * Get the inner datagram channel for read and write operations. To be called by the {@link NioSelectorProcessor}
121      * 
122      * @return the datagram channel bound to this {@link NioUdpServer}.
123      */
124     public DatagramChannel getDatagramChannel() {
125         return datagramChannel;
126     }
127 
128     /**
129      * {@inheritDoc}
130      */
131     @Override
132     public SocketAddress getBoundAddress() {
133         return address;
134     }
135 
136     /**
137      * {@inheritDoc}
138      */
139     @Override
140     public void bind(final int port) {
141         bind(new InetSocketAddress(port));
142     }
143 
144     /**
145      * {@inheritDoc}
146      */
147     @Override
148     public void bind(final SocketAddress localAddress) {
149         if (localAddress == null) {
150             // We should at least have one address to bind on
151             throw new IllegalArgumentException("LocalAdress cannot be null");
152         }
153 
154         // check if the address is already bound
155         if (this.address != null) {
156             throw new IllegalStateException("address " + address + " already bound");
157         }
158         address = localAddress;
159 
160         LOG.info("binding address {}", localAddress);
161 
162         try {
163             datagramChannel = DatagramChannel.open();
164             datagramChannel.socket().setReuseAddress(isReuseAddress());
165             datagramChannel.socket().bind(address);
166             datagramChannel.configureBlocking(false);
167         } catch (IOException e) {
168             throw new MinaRuntimeException("can't open the address " + address, e);
169         }
170 
171         readSelectorLoop.register(false, false, true, false, this, datagramChannel, null);
172 
173         // it's the first address bound, let's fire the event
174         this.fireServiceActivated();
175     }
176 
177     @Override
178     public IoFuture<IoSession> connect(SocketAddress remoteAddress) {
179         throw new IllegalStateException("not supported");
180     }
181 
182     /**
183      * {@inheritDoc}
184      */
185     @Override
186     public void unbind() {
187         LOG.info("unbinding {}", address);
188         if (this.address == null) {
189             throw new IllegalStateException("server not bound");
190         }
191 
192         readSelectorLoop.unregister(this, datagramChannel);
193         datagramChannel.socket().close();
194         try {
195             datagramChannel.close();
196         } catch (IOException e) {
197             throw new MinaRuntimeException("can't close the datagram socket", e);
198         }
199 
200         this.address = null;
201         this.fireServiceInactivated();
202     }
203 
204     /**
205      * @return the readKey
206      */
207     public SelectionKey getReadKey() {
208         return readKey;
209     }
210 
211     /**
212      * @param readKey the readKey to set
213      */
214     public void setReadKey(final SelectionKey readKey) {
215         this.readKey = readKey;
216     }
217 
218     /**
219      * {@inheritDoc}
220      */
221     @Override
222     public void ready(final boolean accept, boolean connect, final boolean read, final ByteBuffer readBuffer,
223             final boolean write) {
224         // Process the reads first
225         try {
226             // System.err.println("remaining : " + readBuffer.remaining());
227             final SocketAddress source = datagramChannel.receive(readBuffer);
228             NioUdpSession session = null;
229 
230             // let's find the corresponding session
231             if (source != null) {
232                 session = sessions.get(source);
233 
234                 if (session == null) {
235                     session = createSession(source, datagramChannel);
236                 }
237                 if (read) {
238                     if (IS_DEBUG) {
239                         LOG.debug("readable datagram for UDP service : {}", this);
240                     }
241 
242                     readBuffer.flip();
243 
244                     if (IS_DEBUG) {
245                         LOG.debug("read {} bytes form {}", readBuffer.remaining(), source);
246                     }
247 
248                     session.receivedDatagram(readBuffer);
249 
250                 }
251 
252                 // Now, process the writes
253                 if (write) {
254                     session.processWrite(readSelectorLoop);
255                 }
256             } else {
257                 if (IS_DEBUG) {
258                     LOG.debug("Do data to read");
259                 }
260             }
261 
262         } catch (final IOException ex) {
263             LOG.error("IOException while reading the socket", ex);
264         }
265     }
266 
267     private NioUdpSession createSession(SocketAddress remoteAddress, DatagramChannel datagramChannel)
268             throws IOException {
269         LOG.debug("create session");
270         UdpSessionConfig config = getSessionConfig();
271         SocketAddress localAddress = new InetSocketAddress(datagramChannel.socket().getLocalAddress(), datagramChannel
272                 .socket().getLocalPort());
273         final NioUdpSession session = new NioUdpSession(this, idleChecker, datagramChannel, localAddress, remoteAddress);
274 
275         // apply idle configuration
276         session.getConfig().setIdleTimeInMillis(IdleStatus.READ_IDLE, config.getIdleTimeInMillis(IdleStatus.READ_IDLE));
277         session.getConfig().setIdleTimeInMillis(IdleStatus.WRITE_IDLE,
278                 config.getIdleTimeInMillis(IdleStatus.WRITE_IDLE));
279 
280         // apply the default service socket configuration
281 
282         Boolean reuseAddress = config.isReuseAddress();
283 
284         if (reuseAddress != null) {
285             session.getConfig().setReuseAddress(reuseAddress);
286         }
287 
288         Integer readBufferSize = config.getReadBufferSize();
289 
290         if (readBufferSize != null) {
291             session.getConfig().setReadBufferSize(readBufferSize);
292         }
293 
294         Integer sendBufferSize = config.getSendBufferSize();
295 
296         if (sendBufferSize != null) {
297             session.getConfig().setSendBufferSize(sendBufferSize);
298         }
299 
300         Integer trafficClass = config.getTrafficClass();
301 
302         if (trafficClass != null) {
303             session.getConfig().setTrafficClass(trafficClass);
304         }
305 
306         // Manage the Idle status
307         idleChecker.sessionRead(session, System.currentTimeMillis());
308         idleChecker.sessionWritten(session, System.currentTimeMillis());
309 
310         sessions.put(remoteAddress, session);
311 
312         // Inform the handler that the session has been created
313         session.setConnected();
314 
315         return session;
316     }
317 }