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.Socket;
25  import java.nio.ByteBuffer;
26  import java.nio.channels.SelectionKey;
27  import java.nio.channels.SocketChannel;
28  
29  import org.apache.mina.api.IoService;
30  import org.apache.mina.service.idlechecker.IdleChecker;
31  import org.apache.mina.session.WriteRequest;
32  import org.apache.mina.transport.ConnectFuture;
33  import org.apache.mina.transport.tcp.ProxyTcpSessionConfig;
34  import org.apache.mina.transport.tcp.TcpSessionConfig;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  /**
39   * A NIO based TCP session, should be used by {@link NioTcpServer} and {@link NioTcpClient}. A TCP session is a
40   * connection between a our server/client and the remote end-point.
41   * 
42   * @author <a href="http://mina.apache.org">Apache MINA Project</a>
43   * 
44   */
45  public class NioTcpSession extends AbstractNioSession implements SelectorListener {
46  
47      private static final Logger LOG = LoggerFactory.getLogger(NioTcpSession.class);
48  
49      /** the selector loop in charge of generating read/write events for this session */
50      private final SelectorLoop selectorLoop;
51  
52      /** the socket configuration */
53      private final TcpSessionConfig configuration;
54  
55      /** the future representing this session connection operation (client only) */
56      private ConnectFuture connectFuture;
57  
58      /** The associated selectionKey */
59      private SelectionKey selectionKey;
60  
61      /** The Direct Buffer used to send data */
62      private ByteBuffer sendBuffer;
63  
64      /** The size of the buffer configured in the socket to send data */
65      private int sendBufferSize;
66  
67      /* No qualifier */NioTcpSession(final IoService service, final SocketChannel channel,
68              final SelectorLoop selectorLoop, final IdleChecker idleChecker) {
69          super(service, channel, idleChecker);
70          this.selectorLoop = selectorLoop;
71          this.configuration = new ProxyTcpSessionConfig(channel.socket());
72          sendBufferSize = configuration.getSendBufferSize();
73          sendBuffer = ByteBuffer.allocateDirect(sendBufferSize);
74      }
75  
76      void setConnectFuture(ConnectFuture connectFuture) {
77          this.connectFuture = connectFuture;
78      }
79  
80      /**
81       * Get the underlying {@link SocketChannel} of this session
82       * 
83       * @return the socket channel used by this session
84       */
85      SocketChannel getSocketChannel() {
86          return (SocketChannel) channel;
87      }
88  
89      /**
90       * {@inheritDoc}
91       */
92      @Override
93      public InetSocketAddress getRemoteAddress() {
94          if (channel == null) {
95              return null;
96          }
97          final Socket socket = ((SocketChannel) channel).socket();
98  
99          if (socket == null) {
100             return null;
101         }
102 
103         return (InetSocketAddress) socket.getRemoteSocketAddress();
104     }
105 
106     /**
107      * {@inheritDoc}
108      */
109     @Override
110     public InetSocketAddress getLocalAddress() {
111         if (channel == null) {
112             return null;
113         }
114 
115         final Socket socket = ((SocketChannel) channel).socket();
116 
117         if (socket == null) {
118             return null;
119         }
120 
121         return (InetSocketAddress) socket.getLocalSocketAddress();
122     }
123 
124     /**
125      * {@inheritDoc}
126      */
127     @Override
128     public void suspendRead() {
129         // TODO
130         throw new RuntimeException("Not implemented");
131     }
132 
133     /**
134      * {@inheritDoc}
135      */
136     @Override
137     public void suspendWrite() {
138         // TODO
139         throw new RuntimeException("Not implemented");
140     }
141 
142     /**
143      * {@inheritDoc}
144      */
145     @Override
146     protected int writeDirect(Object message) {
147         try {
148             // Check that we can write into the channel
149             if (!isRegisteredForWrite()) {
150                 // We don't have pending writes
151                 return ((SocketChannel) channel).write((ByteBuffer) message);
152             } else {
153                 return -1;
154             }
155         } catch (final IOException e) {
156             LOG.error("Exception while reading : ", e);
157             processException(e);
158 
159             return -1;
160         }
161     }
162 
163     /**
164      * {@inheritDoc}
165      */
166     @Override
167     protected ByteBuffer convertToDirectBuffer(WriteRequest writeRequest, boolean createNew) {
168         ByteBuffer message = (ByteBuffer) writeRequest.getMessage();
169 
170         if (!message.isDirect()) {
171             int remaining = message.remaining();
172 
173             if ((remaining > sendBufferSize) || createNew) {
174                 ByteBuffer directBuffer = ByteBuffer.allocateDirect(remaining);
175                 directBuffer.put(message);
176                 directBuffer.flip();
177                 writeRequest.setMessage(directBuffer);
178 
179                 return directBuffer;
180             } else {
181                 sendBuffer.clear();
182                 sendBuffer.put(message);
183                 sendBuffer.flip();
184                 writeRequest.setMessage(sendBuffer);
185 
186                 return sendBuffer;
187             }
188         }
189 
190         return message;
191     }
192 
193     /**
194      * {@inheritDoc}
195      */
196     @Override
197     public void resumeRead() {
198         // TODO
199         throw new RuntimeException("Not implemented");
200     }
201 
202     /**
203      * {@inheritDoc}
204      */
205     @Override
206     public void resumeWrite() {
207         // TODO
208         throw new RuntimeException("Not implemented");
209     }
210 
211     /**
212      * {@inheritDoc}
213      */
214     @Override
215     public boolean isReadSuspended() {
216         // TODO
217         return false;
218     }
219 
220     /**
221      * {@inheritDoc}
222      */
223     @Override
224     public boolean isWriteSuspended() {
225         // TODO
226         throw new RuntimeException("Not implemented");
227     }
228 
229     /**
230      * {@inheritDoc}
231      */
232     @Override
233     public TcpSessionConfig getConfig() {
234         return configuration;
235     }
236 
237     /**
238      * Set this session status as connected. To be called by the processor selecting/polling this session.
239      */
240     void setConnected() {
241         if (!isCreated()) {
242             throw new RuntimeException("Trying to open a non created session");
243         }
244 
245         state = SessionState.CONNECTED;
246 
247         if (connectFuture != null) {
248             connectFuture.complete(this);
249             // free some memory
250             connectFuture = null;
251         }
252 
253         processSessionOpen();
254     }
255 
256     /**
257      * {@inheritDoc}
258      */
259     @Override
260     protected void channelClose() {
261         try {
262             selectorLoop.unregister(this, channel);
263             channel.close();
264         } catch (final IOException e) {
265             LOG.error("Exception while closing the channel : ", e);
266             processException(e);
267         }
268     }
269 
270     /**
271      * {@inheritDoc}
272      */
273     @Override
274     public void flushWriteQueue() {
275         // register for write
276         selectorLoop.modifyRegistration(false, !isReadSuspended(), true, this, channel, true);
277     }
278 
279     /**
280      * Process a read operation : read the data from the channel and push them to the chain.
281      * 
282      * @param readBuffer The buffer that will contain the read data
283      */
284     private void processRead(final ByteBuffer readBuffer) {
285         try {
286             LOG.debug("readable session : {}", this);
287 
288             // Read everything we can up to the buffer size
289             final int readCount = ((SocketChannel) channel).read(readBuffer);
290 
291             LOG.debug("read {} bytes", readCount);
292 
293             if (readCount < 0) {
294                 // session closed by the remote peer
295                 LOG.debug("session closed by the remote peer");
296                 close(true);
297             } else if (readCount > 0) {
298                 // we have read some data
299                 // limit at the current position & rewind buffer back to start &
300                 // push to the chain
301                 readBuffer.flip();
302 
303                 if (isSecured()) {
304                     // We are reading data over a SSL/TLS encrypted connection.
305                     // Redirect the processing to the SslHelper class.
306                     final SslHelper sslHelper = getAttribute(SSL_HELPER, null);
307 
308                     if (sslHelper == null) {
309                         throw new IllegalStateException();
310                     }
311 
312                     sslHelper.processRead(this, readBuffer);
313 
314                     // We don't clear the buffer. It has been done by the sslHelper
315                 } else {
316                     // Plain message, not encrypted : go directly to the chain
317                     processMessageReceived(readBuffer);
318 
319                     // And now, clear the buffer
320                     readBuffer.clear();
321                 }
322 
323                 // Update the session idle status
324                 idleChecker.sessionRead(this, System.currentTimeMillis());
325             }
326         } catch (final IOException e) {
327             LOG.error("Exception while reading : ", e);
328             processException(e);
329         }
330     }
331 
332     /**
333      * {@inheritDoc}
334      */
335     @Override
336     public void ready(final boolean accept, boolean connect, final boolean read, final ByteBuffer readBuffer,
337             final boolean write) {
338         if (LOG.isDebugEnabled()) {
339             LOG.debug("session {} ready for accept={}, connect={}, read={}, write={}", new Object[] { this, accept,
340                                     connect, read, write });
341         }
342         if (connect) {
343             try {
344 
345                 boolean isConnected = ((SocketChannel) channel).finishConnect();
346 
347                 if (!isConnected) {
348                     LOG.error("unable to connect session {}", this);
349                 } else {
350                     // cancel current registration for connection
351                     selectionKey.cancel();
352                     selectionKey = null;
353 
354                     // Register for reading
355                     selectorLoop.register(false, false, true, false, this, channel, new RegistrationCallback() {
356 
357                         @Override
358                         public void done(SelectionKey selectionKey) {
359                             setConnected();
360                         }
361                     });
362                 }
363             } catch (IOException e) {
364                 LOG.debug("Connection error, we cancel the future", e);
365                 if (connectFuture != null) {
366                     connectFuture.error(e);
367                 }
368             }
369         }
370 
371         if (read) {
372             processRead(readBuffer);
373         }
374 
375         if (write) {
376             processWrite(selectorLoop);
377         }
378         if (accept) {
379             throw new IllegalStateException("accept event should never occur on NioTcpSession");
380         }
381     }
382 
383     void setSelectionKey(SelectionKey key) {
384         this.selectionKey = key;
385     }
386 }