/* * SocketTransport.java February 2007 * * Copyright (C) 2007, Niall Gallagher <niallg@users.sf.net> * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package org.simpleframework.transport; import static org.simpleframework.transport.TransportEvent.READ; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.util.Map; import javax.net.ssl.SSLEngine; import org.simpleframework.transport.reactor.Reactor; import org.simpleframework.transport.trace.Trace; /** * The <code>SocketTransport</code> object offers a transport that can send and * receive bytes in a non-blocking manner. The contract of the * <code>Transport</code> is that it must either write the data it is asked to * write or it must queue that data for delivery. For the vast majority of cases * data is written directly to the socket without any need for queuing or * selection for write ready events. * <p> * In the event that the client TCP window is full and writing would block this * makes use of a queue of buffers which can be used to append data to. The * buffers are lazily instantiated so the memory required is created only in the * rare case that they are needed. Once a buffer is full it is queued to an * asynchronous thread where the buffer queue is drained and sent to the client * when the TCP window of the client is capable of accepting it. * <p> * In order to improve the network performance of this transport the default * packet size sent to the TCP stack is four kilobytes. This ensures that the * fragments of response delivered to the TCP layer are sufficiently large for * optimum network performance. * * @author Niall Gallagher */ public class SocketTransport implements Transport { /** * This creates packets with increasing sequence numbers. */ private PacketBuilder builder; /** * This is the underlying byte channel used to send the data. */ private SocketChannel channel; /** * This is the writer that is used to flush the buffer queue. */ private Controller writer; /** * This is the socket that this transport is representing. */ private Socket socket; /** * This is the trace used to monitor all transport events. */ private Trace trace; /** * This is used to determine if the transport has been closed. */ private boolean closed; /** * Constructor for the <code>SocketTransport</code> object. This requires a * reactor to perform asynchronous writes and also the pipeline which is * used to read and write data. This transport will use a queue of buffers * which are lazily initialized so as to only allocate the memory on demand. * * @param socket * this is used to read and write the data * @param reactor * this is used to perform asynchronous writes */ public SocketTransport(Socket socket, Reactor reactor) throws IOException { this(socket, reactor, 20480); } /** * Constructor for the <code>SocketTransport</code> object. This requires a * reactor to perform asynchronous writes and also the pipeline which is * used to read and write data. This transport will use a queue of buffers * which are lazily initialized so as to only allocate the memory on demand. * * @param socket * this is used to read and write the data * @param reactor * this is used to perform asynchronous writes * @param limit * this is the threshold for asynchronous buffers */ public SocketTransport(Socket socket, Reactor reactor, int limit) throws IOException { this(socket, reactor, limit, 3); } /** * Constructor for the <code>SocketTransport</code> object. This requires a * reactor to perform asynchronous writes and also the pipeline which is * used to read and write data. This transport will use a queue of buffers * which are lazily initialized so as to only allocate the memory on demand. * * @param socket * this is used to read and write the data * @param reactor * this is used to perform asynchronous writes * @param limit * this is the threshold for asynchronous buffers * @param queue * this is the queue size for asynchronous writes */ public SocketTransport(Socket socket, Reactor reactor, int limit, int queue) throws IOException { this.writer = new SocketController(socket, reactor, limit); this.builder = new PacketBuilder(queue); this.channel = socket.getChannel(); this.trace = socket.getTrace(); this.socket = socket; } /** * This is used to acquire the trace object that is associated with the * socket. A trace object is used to collection details on what operations * are being performed on the socket. For instance it may contain * information relating to I/O events or more application specific events * such as errors. * * @return this returns the trace associated with this socket */ @Override public Trace getTrace() { return this.trace; } /** * This method is used to get the <code>Map</code> of attributes by this * pipeline. The attributes map is used to maintain details about the * connection. Information such as security credentials to client details * can be placed within the attribute map. * * @return this returns the map of attributes for this pipeline */ @Override public Map getAttributes() { return this.socket.getAttributes(); } /** * This is used to acquire the SSL engine used for https. If the pipeline is * connected to an SSL transport this returns an SSL engine which can be * used to establish the secure connection and send and receive content over * that connection. If this is null then the pipeline represents a normal * transport. * * @return the SSL engine used to establish a secure transport */ @Override public SSLEngine getEngine() { return this.socket.getEngine(); } /** * This method is used to acquire the <code>SocketChannel</code> for the * connection. This allows the server to acquire the input and output * streams with which to communicate. It can also be used to configure the * connection and perform various network operations that could otherwise * not be performed. * * @return this returns the socket used by this HTTP pipeline */ @Override public SocketChannel getChannel() { return this.socket.getChannel(); } /** * This is used to perform a non-blocking read on the transport. If there * are no bytes available on the input buffers then this method will return * zero and the buffer will remain the same. If there is data and the buffer * can be filled then this will return the number of bytes read. Finally if * the socket is closed this will return a -1 value. * * @param data * this is the buffer to append the bytes to * * @return this returns the number of bytes that were read */ @Override public int read(ByteBuffer data) throws IOException { if (this.closed) throw new TransportException("Transport is closed"); int count = this.channel.read(data); if (this.trace != null) { this.trace.trace(READ, count); } return count; } /** * This method is used to deliver the provided buffer of bytes to the * underlying transport. Depending on the connection type the array may be * encoded for SSL transport or send directly. This will buffer the bytes * within the internal buffer to ensure that the response fragments are * sufficiently large for the network. Smaller packets result poorer * performance. * * @param data * this is the array of bytes to send to the client */ @Override public void write(ByteBuffer data) throws IOException { if (this.closed) throw new TransportException("Transport is closed"); Packet packet = this.builder.build(data); while (packet != null) { if (!this.closed) { this.writer.write(packet); } packet = this.builder.build(data); } } /** * This is used to flush the internal buffer to the underlying socket. * Flushing with this method is always non-blocking, so if the socket is not * write ready and the buffer can be queued it will be queued and the * calling thread will return. */ @Override public void flush() throws IOException { if (this.closed) throw new TransportException("Transport is closed"); Packet packet = this.builder.build(); if (packet != null) { this.writer.write(packet); } } /** * This method is used to flush the internal buffer and close the underlying * socket. This method will not complete until all buffered data is written * and the underlying socket is closed at which point this can be disposed * of. */ @Override public void close() throws IOException { if (!this.closed) { Packet packet = this.builder.build(); if (packet != null) { this.writer.write(packet); } this.writer.close(); this.closed = true; } } }