All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.tangosol.net.TcpDatagramSocket Maven / Gradle / Ivy

There is a newer version: 24.03
Show newest version
/*
 * Copyright (c) 2000, 2020, Oracle and/or its affiliates.
 *
 * Licensed under the Universal Permissive License v 1.0 as shown at
 * http://oss.oracle.com/licenses/upl.
 */

package com.tangosol.net;


import com.tangosol.coherence.config.Config;

import com.tangosol.util.Base;

import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.DatagramSocketImpl;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import java.nio.ByteBuffer;

import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

import java.nio.channels.spi.SelectorProvider;

import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import com.oracle.coherence.common.base.Blocking;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.net.ssl.SSLException;

import com.oracle.coherence.common.net.SocketProvider;


/**
* TCP based datagram socket implementation.
*
* @author mf  2009.12.03
*/
public class TcpDatagramSocket
    extends DatagramSocket
    {
    // ----- constructors ---------------------------------------------------

    /**
    * Create a new TcpDatagramSocket that with a wildcard address bound to an
    * ephemeral port.
    *
    * @throws SocketException if any error happens during the bind, or if the
    *         port is unavailable
    */
    public TcpDatagramSocket()
            throws SocketException
        {
        this(new InetSocketAddress(0));
        }

    /**
    * Creates a new TcpDatagramSocket which will be bound to the specified
    * {@link SocketAddress address}.
    *
    * @param addr  the {@link SocketAddress address} to bind
    *
    * @throws SocketException if any error happens during the bind,
    *         or if the port is unavailable
    */
    public TcpDatagramSocket(SocketAddress addr)
            throws SocketException
        {
        this(new Impl());
        if (addr != null)
            {
            bind(addr);
            }
        }

    /**
    * Creates a new TcpDatagramSocket using the wildcard address and  the
    * specified port.
    * 

* The port number should be between 0 and 65535. Zero means that the system * will pick an ephemeral port during the bind operation. * * @param nPort the port to bind to * * @throws SocketException if any error happens during the bind, * or if the port is unavailable */ public TcpDatagramSocket(int nPort) throws SocketException { this(nPort, null); } /** * Creates a new TcpDatagramSocket using an {@link InetAddress address} and * a port number. *

* If null is specified as the address assigned will be the * wildcard address. *

* The port number should be between 0 and 65535. Zero means that the system * will pick an ephemeral port during the bind operation. * * @param nPort the port number * @param addr the IP address * * @throws SocketException if any error happens during the bind, * or if the port is unavailable */ public TcpDatagramSocket(int nPort, InetAddress addr) throws SocketException { this(new InetSocketAddress(addr, nPort)); } /** * Creates a new TcpDatagramSocket using the {@link SocketProvider provider}. * * @param provider the {@link SocketProvider provider} to be used * * @throws SocketException if any error happens during the bind, * or if the port is unavailable */ public TcpDatagramSocket(SocketProvider provider) throws SocketException { this(new Impl(provider)); } /** * Creates a new TcpDatagramSocket using an {@link TcpDatagramSocket.Impl}. * * @param impl a {@link TcpDatagramSocket.Impl} */ protected TcpDatagramSocket(Impl impl) { super(impl); m_impl = impl; } // ----- TcpDatagramSocket methods -------------------------------------- /** * Specify SocketOptions to be used to configure each of the underlying * TCP sockets. These options will be added to any previously specified * options. * * @param options the SocketOptions * * @throws SocketException if the options fail to be set */ public void setSocketOptions(java.net.SocketOptions options) throws SocketException { m_impl.m_options.copyOptions(options); } /** * Specify the listen backlog for the server socket. * * @param n the depth of the backlog, or <=0 for the OS default. * * @throws IOException if the port is unavailable */ public void setListenBacklog(int n) throws IOException { if (isBound()) { throw new IOException("already bound"); } m_impl.m_nBacklog = n; } /** * Specify the packet header which is included at the start of every * packet. Because this implementation is TCP based these headers can * be stripped off, and replaced on the far side without consuming any * network resources. * * @param nMagic the packet header * @param nMask the packet header bitmask identifying the bits used * * @throws IOException if the port is unavailable */ public void setPacketMagic(int nMagic, int nMask) throws IOException { if (isBound()) { throw new IOException("already bound"); } m_impl.setPacketMagic(nMagic, nMask); } /** * Specify the frequency at which the DatagramSocket will advance over * the sub-sockets during receive. A higher value will optimize for * throughput as well as latency when communicating with a small number of * peers. A low value will optimize for latency when communicating with * a large number of peers, but is likely to hurt overall throughput. * * @param nAdvance the packet frequency at which to advance between peers */ public void setAdvanceFrequency(int nAdvance) { m_impl.m_nAdvanceFrequency = Math.max(1, nAdvance); } // ----- DatagramSocket methods ----------------------------------------- /** * {@inheritDoc} */ public void bind(SocketAddress addr) throws SocketException { m_impl.bind(addr); } /** * {@inheritDoc} */ public boolean isBound() { return m_impl.f_socket.socket().isBound(); } /** * {@inheritDoc} */ public void send(DatagramPacket p) throws IOException { // bypass the DatagramSocket security manager because: // - it is called once per packet // - we are going to go through the Socket security manager anyway m_impl.send(p); } /** * {@inheritDoc} */ public void receive(DatagramPacket p) throws IOException { // bypass the DatagramSocket security manager because: // - it is called once per packet // - we are going to go through the Socket security manager anyway // - we don't implement the peek methods which are required by the // security manager m_impl.receive(p); } // ----- Object methods ------------------------------------------------- /** * {@inheritDoc} */ public String toString() { return m_impl.toString(); } // ----- inner class: Impl ---------------------------------------------- /** * A specialized version of {@link DatagramSocketImpl}. */ public static class Impl extends DatagramSocketImpl { // ----- constructors ------------------------------------------- /** * Create a new new Impl. * * @throws SocketException if any error happens during the bind, or if * the port is unavailable */ public Impl() throws SocketException { this(SocketProviderFactory.DEFAULT_SOCKET_PROVIDER); } /** * Create a new Impl using a {@link SocketProvider provider}. * * @param provider the {@link SocketProvider provider} used to create * internal sockets * * @throws SocketException if any error happens during the bind, or if * the port is unavailable */ public Impl(SocketProvider provider) throws SocketException { m_provider = provider; m_mapConnectionsOut = new ConcurrentHashMap(); m_mapOptions = new ConcurrentHashMap(); try { // set our default TCP specific options SocketOptions options = m_options; options.setOption(SocketOptions.TCP_NODELAY, true); options.setOption(SocketOptions.SO_LINGER, 0); ServerSocketChannel socket = f_socket = provider .openServerSocketChannel(); socket.configureBlocking(false); f_selector = socket.provider().openSelector(); } catch (IOException e) { throw ensureSocketException(e); } } // ----- DatagramSocketImpl methods ----------------------------- /** * Return the SelectorProvider associated with this socket. * * @return the SelectorProvider */ public SelectorProvider provider() { return f_selector.provider(); } /** * Specify the packet header which is included at the start of every * packet. Because this implementation is TCP based these headers can * be stripped off, and replaced on the far side without consuming any * network resources. * * @param nMagic the packet header * @param nMask the packet header bitmask identifying the bits used * the mask must be in byte increments */ public void setPacketMagic(int nMagic, int nMask) { m_nPacketMagic = nMagic & nMask; m_nPacketMagicMask = nMask; } /** * {@inheritDoc} */ protected void create() throws SocketException { // handled in constructor } /** * {@inheritDoc} */ protected void bind(int nPort, InetAddress addr) throws SocketException { bind(new InetSocketAddress(addr, nPort)); } /** * {@inheritDoc} */ protected int getLocalPort() { return f_socket == null ? 0 : f_socket.socket().getLocalPort(); } /** * {@inheritDoc} */ protected void send(DatagramPacket packet) throws IOException { int cb = packet.getLength(); OutputStream out; Connection conn; try { conn = ensureConnection(packet.getSocketAddress()); out = conn.m_out; } catch (IOException e) { logException(packet.getSocketAddress(), e); return; } synchronized (out) { try { byte[] abPacket = packet.getData(); switch (m_nPacketMagicMask) { case 0xFFFF0000: // short if (cb > 0xFFFF) { throw new IOException("packet length exceeds 2^16"); } abPacket[0] = (byte) (cb >>> 8); abPacket[1] = (byte) cb; break; case 0xFFFFFFF0: // trint and counter abPacket[3] = (byte) ((conn.m_cTxPacket << 4) | (abPacket[3] & 0x0F)); // fall through case 0xFFFFFF00: // trint if (cb > 0xFFFFFF) { throw new IOException("packet length exceeds 2^24"); } abPacket[0] = (byte) (cb >>> 16); abPacket[1] = (byte) (cb >>> 8); abPacket[2] = (byte) cb; break; case 0xFFFFFFFF: // int abPacket[0] = (byte) (cb >>> 24); abPacket[1] = (byte) (cb >>> 16); abPacket[2] = (byte) (cb >>> 8); abPacket[3] = (byte) cb; break; default: out.write(cb >>> 24); out.write(cb >>> 16); out.write(cb >>> 8); out.write(cb); break; } out.write(packet.getData(), packet.getOffset(), cb); out.flush(); ++conn.m_cTxPacket; } catch (IOException e) { closeOutbound(packet.getSocketAddress()); } } } /** * {@inheritDoc} */ protected int peek(InetAddress addr) throws IOException { return 0; } /** * {@inheritDoc} */ protected int peekData(DatagramPacket packet) throws IOException { return 0; } /** * {@inheritDoc} */ protected void receive(DatagramPacket packet) throws IOException { ByteBuffer buffPacket = ByteBuffer.wrap(packet.getData(), packet.getOffset(), packet.getLength()); // optimized common path for hot-spot friendliness SelectionKey key = m_keyCurrent; if (key == null || m_cKeyUses++ > m_nAdvanceFrequency || !onRead(key, buffPacket)) { try { key = null; // in case nextKey throws key = nextKey(buffPacket); } finally { m_keyCurrent = key; m_cKeyUses = 1; } } // we have a packet ConnectionStatus status = (ConnectionStatus) key.attachment(); packet.setLength(status.m_cbBody); packet.setSocketAddress(status.m_addr); packet.setPort(status.m_addr.getPort()); } /** * Perform a blocking read, waiting for a complete packet. * * @param buffPacket the packet buffer * * @return the corresponding SelectionKey * * @throws SocketTimeoutException if SO_TIMEOUT is exceeded */ protected SelectionKey nextKey(ByteBuffer buffPacket) throws IOException { Selector selector = f_selector; Map mapSched = m_mapRegScheduled; long cMillisSoTimeout = m_cMillisSoTimeout; synchronized (selector) { Iterator iterPending = m_iterKeysPending; Set setPending = selector.isOpen() ? selector.selectedKeys() : null; while (!m_fClosing) { try { if (iterPending == null || !iterPending.hasNext()) { try { m_iterKeysPending = iterPending = null; // will be invalidated by select call Blocking.select(selector, cMillisSoTimeout); if (!mapSched.isEmpty()) { processRegistrations(); continue; // re-select } if (setPending.isEmpty()) { // lazily ensure binding; this is the only // way out of here if we are not yet bound ensureBound(); throw new SocketTimeoutException(); } m_iterKeysPending = iterPending = selector. selectedKeys().iterator(); } catch (ClosedChannelException e) { throw new SocketException("closed socket"); } } SelectionKey key = iterPending.next(); iterPending.remove(); if (key == null || !key.isValid()) { // continue; } else if (key.isReadable()) { if (onRead(key, buffPacket)) { // we have a packet return key; } } else if (key.isAcceptable()) { onAccept(key); } else if (key.readyOps() == 0) { // compensate for Java NIO bug: // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933 closeInbound((ConnectionStatus) key.attachment(), (SocketChannel) key.channel()); } } catch (ClosedSelectorException e) { throw new SocketException("closed socket"); } catch (CancelledKeyException e) {} } } throw new SocketException("closed socket"); } /** * Called when a channel is identified as have a new connection to * accept. * * @param key the associated SelectionKey */ protected void onAccept(SelectionKey key) { SocketChannel chan = null; try { // accept the connection and register it with the selector chan = ((ServerSocketChannel) key.channel()).accept(); if (chan != null) { chan.configureBlocking(false); configureSocket(chan.socket()); chan.register(f_selector, SelectionKey.OP_READ, new ConnectionStatus()); } } catch (IOException e) { logException(chan == null ? null : chan.socket().getRemoteSocketAddress(), e); } } /** * Called when a channel is detected as readable. * * @param key the associated SelectionKey * @param buffPacket a buffer in which to place any available full * packet * @return true iff a packet has been read */ protected boolean onRead(SelectionKey key, ByteBuffer buffPacket) { SocketChannel chan; ConnectionStatus status; try { chan = (SocketChannel) key.channel(); status = (ConnectionStatus) key.attachment(); } catch (CancelledKeyException e) { return false; } Socket socket = chan.socket(); boolean fEOS = false; int nLimitRestore = -1; int nPosRestore = -1; int nPacketMagic = m_nPacketMagic; int nPacketMask = m_nPacketMagicMask; int nHead = 0; ByteBuffer buff; try { switch (status.m_state) { case ConnectionStatus.WAIT_MAGIC: case ConnectionStatus.WAIT_PORT: fEOS = onConnectionHeader(status, chan); if (status.m_state != ConnectionStatus.WAIT_HEAD) { break; } // fall through; try to read packet header case ConnectionStatus.WAIT_HEAD: // process packet header buff = status.m_head; // TODO: optimize packet read so that it can complete // in a single chan.read, rather then at least two fEOS = chan.read(buff) < 0; if (buff.hasRemaining()) { break; // work on other channels } buff.flip(); nHead = buff.getInt(); // decode the size from the first int based on the // magic mask switch (nPacketMask) { case 0xFFFF0000: // short status.m_cbBody = (nHead >>> 16); break; case 0xFFFFFFF0: // trint and counter if ((status.m_cRxPacket & 0x0F) != ((nHead & 0x0F0) >>> 4)) { // indicates that we have a corrupted stream CacheFactory.log("Recovering corrupted " + "packet stream from " + status.m_addr + " detected at packet " + status.m_cRxPacket + ", last packet" + " length " + status.m_cbBody, CacheFactory.LOG_INFO); throw new IOException(); } // fall through case 0xFFFFFF00: // trint status.m_cbBody = (nHead >>> 8); break; case 0xFFFFFFFF: // int default: status.m_cbBody = nHead; break; } ++status.m_cRxPacket; buff.clear(); status.m_state = ConnectionStatus.WAIT_BODY; // fall through; try to read the body case ConnectionStatus.WAIT_BODY: // process packet body if (status.m_body.position() == 0) { // we haven't started reading the body yet if (buffPacket.remaining() >= status.m_cbBody) { // use the packet directly; record positions buff = buffPacket; nPosRestore = buff.position(); nLimitRestore = buff.limit(); buff.limit(nPosRestore + status.m_cbBody); } else if (status.m_body.remaining() >= status.m_cbBody) { // use the temp buffer buff = status.m_body; buff.limit(status.m_cbBody); } else { // temp buffer isn't big enough buff = status.m_body = ByteBuffer.allocate(status.m_cbBody); } // replace the magic if necessary switch (nPacketMask) { case 0xFFFF0000: // short case 0xFFFFFF00: // trint case 0xFFFFFFF0: // trint and counter case 0xFFFFFFFF: // int if (status.m_cbBody < 4) { CacheFactory.log("Recovering corrupted" + " packet stream from " + status.m_addr + " detected at" + " packet " + status.m_cRxPacket + ", with packet length " + status.m_cbBody + ", buffer " + "capacity " + status.m_body.capacity(), CacheFactory.LOG_INFO); throw new IOException(); } buff.putInt(nPacketMagic | (~nPacketMask & nHead)); break; default: // magic was not stripped from packet break; } } else { // we have a partial body buff = status.m_body; } fEOS = chan.read(buff) < 0; if (!buff.hasRemaining()) { // full packet has been read if (buff == status.m_body) { // transfer into buffPacket buff.rewind(); status.m_cbBody = transferBytes(buff, buffPacket); status.m_body.clear(); } // else; buffPacket holds the entire packet status.m_state = ConnectionStatus.WAIT_HEAD; return true; // we have a packet } // didn't complete the read if (buff == buffPacket) { // we read directly into the packet; transfer read // bytes into status.body if (status.m_body.capacity() < status.m_cbBody) { status.m_body = ByteBuffer.allocate(status.m_cbBody); } else { status.m_body.limit(status.m_cbBody); } buffPacket.limit(buffPacket.position()); buffPacket.position(nPosRestore); transferBytes(buffPacket, status.m_body); } // else; was reading into status.body nothing to do break; default: throw new IllegalStateException(); } } catch (IOException e) { logException(status.m_addr == null ? socket.getRemoteSocketAddress() : status.m_addr, e); closeInbound(status, chan); } finally { if (nLimitRestore != -1) { // restore original packet position and limit if they were // updated buffPacket.limit(nLimitRestore); buffPacket.position(nPosRestore); } if (fEOS) { closeInbound(status, chan); } } return false; // try another socket } /** * Process a pending connection header. * * @param status the associated ConnectionStatus * @param chan the associated channel * * @return true if EOS has been reached * * @throws IOException if an I/O error occurs */ protected boolean onConnectionHeader(ConnectionStatus status, SocketChannel chan) throws IOException { Socket socket = chan.socket(); ByteBuffer buff = status.m_head; boolean fEOS = false; switch (status.m_state) { case ConnectionStatus.WAIT_MAGIC: fEOS = chan.read(buff) < 0; if (buff.hasRemaining()) { break; // work on other channels } buff.flip(); int nMagic = buff.getInt(); buff.clear(); if (nMagic != PROTOCOL_MAGIC) { if (nMagic == 0) { // special case; if nothing other then zeros are // sent, then we stay in a holding pattern waiting // for the magic. The purpose of this is to all // monitoring of the channel by plain TCP // connections, i.e. TCPRing break; // work on other channels } fEOS = true; logProtocolWarning(socket.getRemoteSocketAddress(), status, nMagic); break; // work on other channels } status.m_state = ConnectionStatus.WAIT_PACKET_MAGIC; // fall through case ConnectionStatus.WAIT_PACKET_MAGIC: fEOS = chan.read(buff) < 0; if (buff.hasRemaining()) { break; // work on other channels } buff.flip(); int nPacketMagic = buff.getInt(); buff.clear(); if (nPacketMagic != m_nPacketMagic) { fEOS = true; logProtocolWarning(socket.getRemoteSocketAddress(), status, nPacketMagic); break; // work on other channels } status.m_state = ConnectionStatus.WAIT_PACKET_MAGIC_MASK; // fall through case ConnectionStatus.WAIT_PACKET_MAGIC_MASK: fEOS = chan.read(buff) < 0; if (buff.hasRemaining()) { break; // work on other channels } buff.flip(); int nPacketMagicMask = buff.getInt(); buff.clear(); if (nPacketMagicMask != m_nPacketMagicMask) { fEOS = true; logProtocolWarning(socket.getRemoteSocketAddress(), status, nPacketMagicMask); break; // work on other channels } status.m_state = ConnectionStatus.WAIT_PORT; // fall through case ConnectionStatus.WAIT_PORT: fEOS = chan.read(buff) < 0; if (buff.hasRemaining()) { break; // work on other channels } // create new buffer for the body buff.flip(); status.m_addr = new InetSocketAddress(chan.socket(). getInetAddress(), buff.getInt()); buff.clear(); status.m_state = ConnectionStatus.WAIT_HEAD; break; default: throw new IllegalStateException(); } return fEOS; } /** * {@inheritDoc} */ protected void close() { Map map = m_mapConnectionsOut; synchronized (map) { for (Connection conn : map.values()) { if (conn != null) { try { conn.m_socket.close(); } catch (IOException e) { logException(conn.m_socket.getRemoteSocketAddress(), e); } } } } Selector selector = f_selector; m_fClosing = true; selector.wakeup(); // terminate any existing calls to receive synchronized (selector) // wait for existing receive call to complete { for (SelectionKey key : selector.keys()) { if (key != null && key.channel() instanceof SocketChannel) { closeInbound((ConnectionStatus) key.attachment(), (SocketChannel) key.channel()); } } try { selector.close(); } catch (IOException e) {} } // don't allow acceptor port to be reused until we've cleaned up // all TCP sockets try { f_socket.close(); } catch (IOException e) { logException(f_socket.socket().getLocalSocketAddress(), e); } } /** * {@inheritDoc} */ public void setOption(int nId, Object oValue) throws SocketException { m_mapOptions.put(nId, oValue); if (nId == java.net.SocketOptions.SO_TIMEOUT) { m_cMillisSoTimeout = ((Number) oValue).longValue(); } // The only other setting which could map through from UDP to TCP // would be the send and receive space sizes. Though we could push // these settings down it would be unfair, the application had // requested that we use X bytes for buffering but we would use // X*N where N is the number of peers we end up conneted to. // Instead we just rely on the system defaults. // TODO: we could try to balance the buffer setting across all // sockets over time, perhaps only updating the sockets at most // once a minute, and only if the total memory allocation was // off by more tehn 10% of the configured limit? } /** * {@inheritDoc} */ public Object getOption(int nId) throws SocketException { switch (nId) { case java.net.SocketOptions.SO_BINDADDR: return f_socket == null ? null : f_socket.socket().getInetAddress(); default: return m_mapOptions.get(nId); } } // ----- unsupported operations --------------------------------- /** * {@inheritDoc} */ protected void setTTL(byte ttl) throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected byte getTTL() throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected void setTimeToLive(int ttl) throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected int getTimeToLive() throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected void join(InetAddress inetaddr) throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected void leave(InetAddress inetaddr) throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected void joinGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException { throw new UnsupportedOperationException(); } /** * {@inheritDoc} */ protected void leaveGroup(SocketAddress mcastaddr, NetworkInterface netIf) throws IOException { throw new UnsupportedOperationException(); } // ----- helper methods ----------------------------------------- /** * Bind the socket to the specified address. * * @param addr the address to bind to * * @throws SocketException if an I/O error occurs */ protected void bind(SocketAddress addr) throws SocketException { try { // custom server config ServerSocketChannel socket = f_socket; m_options.apply(socket.socket()); socket.socket().bind(addr, m_nBacklog); socket.register(f_selector, SelectionKey.OP_ACCEPT); } catch (IOException e) { throw ensureSocketException(e); } } /** * Apply socket options to a socket. * * @param socket the socket to configure * * @throws SocketException on configuration error */ void configureSocket(Socket socket) throws SocketException { // apply socket configuration m_options.apply(socket); } /** * Ensure that the socket is bound. * * @throws SocketException if an I/O error occurs */ void ensureBound() throws SocketException { ServerSocketChannel socket = f_socket; if (!socket.socket().isBound()) { try { bind(null); } catch (IOException e) { if (!socket.socket().isBound()) { throw ensureSocketException(e); } } } } /** * Convert an IOException into a SocketException. * * @param e the IOExcepotion * * @return the SocketException */ protected static SocketException ensureSocketException(IOException e) { if (e instanceof SocketException) { return (SocketException) e; } SocketException es = new SocketException(e.getMessage()); es.initCause(e); return es; } /** * Produce a new Connection for the specified destination address. * * @param addr the destination address * * @return the corresponding connection * * @throws IOException if an I/O error occurs */ protected Connection makeConnection(SocketAddress addr) throws IOException { Connection conn = new Connection(addr, m_provider, m_options); OutputStream out = conn.m_out; // start the connection by sending magic int nMagic = PROTOCOL_MAGIC; out.write(nMagic >>> 24); out.write(nMagic >>> 16); out.write(nMagic >>> 8); out.write(nMagic); // then the packet magic int nPacketMagic = m_nPacketMagic; out.write(nPacketMagic >>> 24); out.write(nPacketMagic >>> 16); out.write(nPacketMagic >>> 8); out.write(nPacketMagic); // then the packet magic mask int nPacketMagicMask = m_nPacketMagicMask; out.write(nPacketMagicMask >>> 24); out.write(nPacketMagicMask >>> 16); out.write(nPacketMagicMask >>> 8); out.write(nPacketMagicMask); // and then our acceptor port this allows the receiving side to // re-build the UDP like source address int nPort = f_socket.socket().getLocalPort(); out.write(nPort >>> 24); out.write(nPort >>> 16); out.write(nPort >>> 8); out.write(nPort); return conn; } /** * Obtain a Connection for the specified address. * * @param addr the destination address * * @return the corresponding connection * * @throws IOException if an I/O error occurs */ protected Connection ensureConnection(SocketAddress addr) throws IOException { ConcurrentMap map = m_mapConnectionsOut; Connection conn = map.get(addr); if (conn == null) { if (f_socket.socket().isClosed()) { throw new SocketException("Socket is closed"); } ensureBound(); conn = makeConnection(addr); if (map.putIfAbsent(addr, conn) == null) { return conn; } else { map.get(addr); } } return conn; } /** * Close the inbound {@link SocketChannel channel}. * * @param status the {@link TcpDatagramSocket.Impl.ConnectionStatus * ConnectionStatus} corresponding to the channel * @param chan the channel to close */ protected void closeInbound(ConnectionStatus status, SocketChannel chan) { if (status.m_connection != null && status.m_addr != null) { m_mapConnectionsOut.remove(status.m_addr, status.m_connection); } try { chan.close(); } catch (IOException e) { logException(status.m_addr == null ? chan.socket().getRemoteSocketAddress() : status.m_addr, e); } } /** * Close the outbound socket. * * @param addr the {@link SocketAddress address} of the outbound socket */ protected void closeOutbound(SocketAddress addr) { Connection conn = m_mapConnectionsOut.remove(addr); if (conn != null) { if (conn.m_out != null) { try { conn.m_out.flush(); } catch (IOException e) { logException(addr, e); } } try { SocketChannel chan = conn.m_socket.getChannel(); if (chan == null) { conn.m_socket.close(); } else { chan.close(); } } catch (IOException e) { logException(addr, e); } } } /** * Schedule a registration with the selector, and wake it up. * * @param chan the channel to scheduled registration for * @param status the associated ConnectionStatus to register */ protected void scheduleRegistration(SocketChannel chan, ConnectionStatus status) { m_mapRegScheduled.put(chan, status); f_selector.wakeup(); } /** * Process any scheduled selector registrations. */ protected void processRegistrations() { Selector selector = f_selector; Map mapReg = m_mapRegScheduled; synchronized (mapReg) { for (Iterator> iter = m_mapRegScheduled.entrySet().iterator(); iter.hasNext(); ) { Map.Entry entry = iter.next(); if (entry != null) { iter.remove(); SocketChannel chan = entry.getKey(); ConnectionStatus status = entry.getValue(); try { chan.register(selector, SelectionKey.OP_READ, status); } catch (ClosedChannelException e) { closeInbound(status, chan); } } } } } /** * Transfer bytes from the source to the destination buffer based on * their limits. * * @param buffSrc the source buffer * @param buffDst the destination buffer * * @return the number of bytes transfered */ protected int transferBytes(ByteBuffer buffSrc, ByteBuffer buffDst) { int ofSrc = buffSrc.position(); int cbSrc = buffSrc.remaining(); int cbDst = buffDst.remaining(); if (cbSrc > cbDst) { // dst can't hold entire src, copy as much as possible buffDst.put(buffSrc.array(), buffSrc.arrayOffset() + ofSrc, cbDst); buffSrc.position(ofSrc + cbDst); return cbDst; } else // cbSrc <= cbDst { // transfer the remaining contents buffDst.put(buffSrc); return cbSrc; } } /** * Log an exception which is handled internally by the TcpDatagramSocket. * * @param addr the associated address * @param e the exception */ protected void logException(SocketAddress addr, IOException e) { int nLevel = e instanceof SSLException ? CacheFactory.LOG_WARN // all security exceptions are logged : IO_EXCEPTIONS_LOG_LEVEL; if (nLevel >= 0 && CacheFactory.isLogEnabled(nLevel)) { CacheFactory.log(this + ", exception regarding peer " + addr + ", " + Base.getDeepMessage(e, "; "), nLevel); if (IO_EXCEPTIONS_LOG_LEVEL >= 0 && CacheFactory.isLogEnabled(IO_EXCEPTIONS_LOG_LEVEL)) { // full stack traces are only logged if explictly requested CacheFactory.err(e); } } } /** * Periodically log a warning when connections are made using an * unrecognized protocol. * * @param addr the source address of the connection * @param status the connection status * @param nMagic the "magic" header they sent */ protected void logProtocolWarning(SocketAddress addr, ConnectionStatus status, int nMagic) { long ldtNow = Base.getSafeTimeMillis(); if (ldtNow - m_ldtLastWarn > 10000) { CacheFactory.log("Unexpected protocol header " + nMagic + " in state " + status.m_state + " received from " + addr + " dropping connection", CacheFactory.LOG_WARN); m_ldtLastWarn = ldtNow; } } /** * {@inheritDoc} */ public String toString() { return "TcpDatagramSocket{bind=" + f_socket.socket() + "}"; } // ----- inner class: Connection -------------------------------- /** * A representation of a outbound connection. */ static class Connection { /** * Creates a new Connection. The {@link SocketProvider provider} is * used to create the underlying socket and the {@link SocketOptions * options} will be applied to the socket before it is connected to * {@link SocketAddress addr}. The outbound strem will then be * wrapped in a {@link BufferedOutputStream}. * * @param addr the {@link SocketAddress address} to connect to * @param provider the {@link SocketProvider provider} to use to * create the underlying socket * @param options the {@link SocketOptions options} to apply to the * underlying socket * * @throws IOException if an I/O error occurs when creating the * output stream or if the socket is not connected. */ public Connection(SocketAddress addr, SocketProvider provider, SocketOptions options) throws IOException { m_socket = provider.openSocket(); options.apply(m_socket); m_socket.connect(addr); m_out = new BufferedOutputStream(m_socket.getOutputStream()); } /** * Creates a new Connection using the {@link Socket socket} as the * underlying socket. Will set m_out to null. * * @param socket the underlying socket */ protected Connection(Socket socket) { m_out = null; m_socket = socket; } // ----- data members ----------------------------------------- /** * The underlying {@link Socket socket}. */ final Socket m_socket; /** * The {@link OutputStream stream} used to send. */ final OutputStream m_out; /** * The number of 'packets' sent. */ int m_cTxPacket; } // ----- inner class: ConnectionStatus -------------------------- /** * ConnectionStatus. */ static class ConnectionStatus { // associated outbound connection, if any public Connection m_connection; public int m_state = WAIT_MAGIC; public InetSocketAddress m_addr; public ByteBuffer m_head = ByteBuffer.allocate(HEADER_SIZE); public ByteBuffer m_body = ByteBuffer.allocate(2048); public int m_cbBody; public int m_cRxPacket; public static final int WAIT_MAGIC = 0; public static final int WAIT_PACKET_MAGIC = 1; public static final int WAIT_PACKET_MAGIC_MASK = 2; public static final int WAIT_PORT = 3; public static final int WAIT_HEAD = 4; public static final int WAIT_BODY = 5; } // ----- data members ----------------------------------------- /** * SocketProvider to use in creating internal sockets. */ final SocketProvider m_provider; /** * Configuration for the underlying sockets. */ final SocketOptions m_options = new SocketOptions(); /** * The TCP listen backlog for the server socket. */ int m_nBacklog; /** * A map of pending selector registrations, key is chan, value is attachment. */ final Map m_mapRegScheduled = new ConcurrentHashMap(); /** * Selector. */ final Selector f_selector; /** * The pending set of selector keys. */ Iterator m_iterKeysPending; /** * The current key to read from. */ SelectionKey m_keyCurrent; /** * The number of times the current key has been consequitively ready from. */ int m_cKeyUses; /** * True iff the socket is closing. */ boolean m_fClosing; /** * The maximum number of times to consequitively read from a key. */ int m_nAdvanceFrequency = 32; /** * Acceptor socket. */ final ServerSocketChannel f_socket; /** * Map of InetSocketAddress to corresponding outbound Socket connections. */ ConcurrentMap m_mapConnectionsOut; /** * Socket options. */ Map m_mapOptions; /** * SO_TIMEOUT. */ long m_cMillisSoTimeout; /** * The timestamp of the last warning message. */ long m_ldtLastWarn; /** * The header included in every packet. */ int m_nPacketMagic; /** * The part of m_nPacketMagic which defines the header. */ int m_nPacketMagicMask; // ----- constants ---------------------------------------------- /** * The fixed header size for packets. */ public static final int HEADER_SIZE = 4; /** * Protcol identifier used to identify that peers are also * TcpDatagramSockets. This is necessary so that we don't try to act * upon garbage in this class, for instance trying to allocate a * negative or gigabit sized packet. */ public static final int PROTOCOL_MAGIC = 0x0DDF00DA; } // ----- constants ------------------------------------------------------ /** * Debbuging flag for logging an IO exceptions which occur, set to a negative * to disable the logging. */ public static final int IO_EXCEPTIONS_LOG_LEVEL = Config.getInteger("coherence.tcpdatagram.log.level", -1); // ----- data members --------------------------------------------------- protected Impl m_impl; }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy