From abfa6da4408ff02d4f039a02e4cb827549611e4b Mon Sep 17 00:00:00 2001 From: pocsajimiklos Date: Tue, 10 Feb 2015 11:13:45 +0100 Subject: [PATCH 01/28] Removed executable flag from pom.xml. Added necessary gitignores --- .gitignore | 5 +++++ pom.xml | 0 2 files changed, 5 insertions(+) mode change 100755 => 100644 pom.xml diff --git a/.gitignore b/.gitignore index 14c34b0..7d67421 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,8 @@ target /.project /ManagementTest_sctp.xml /sctptest.log + +# Ignore AppNGIN-generated build.xml and ivy.xml +build.xml +ivy.xml + diff --git a/pom.xml b/pom.xml old mode 100755 new mode 100644 From 083ebdcf5abba577e4c74304728ec7102fb96f3d Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 10 Feb 2015 18:52:33 +0100 Subject: [PATCH 02/28] initial version of the OneToMany client SCTP stack --- .../multiclient/MultiAssociationHandler.java | 122 +++ .../sctp/multiclient/MultiChangeRequest.java | 94 +++ .../multiclient/MultiChannelController.java | 86 ++ .../sctp/multiclient/MultiManagementImpl.java | 735 +++++++++++++++++ .../sctp/multiclient/MultiSctpXMLBinding.java | 70 ++ .../sctp/multiclient/MultiSelectorThread.java | 253 ++++++ .../sctp/multiclient/MultiWorker.java | 64 ++ .../OneToManyAssocMultiplexer.java | 324 ++++++++ .../OneToManyAssociationHandler.java | 170 ++++ .../multiclient/OneToManyAssociationImpl.java | 780 ++++++++++++++++++ 10 files changed, 2698 insertions(+) create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java new file mode 100644 index 0000000..5ff938d --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -0,0 +1,122 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual + * contributors as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a full listing + * of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License, v. 2.0. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * v. 2.0 along with this distribution; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.net.SocketAddress; + + + +import org.apache.log4j.Logger; + +import com.sun.nio.sctp.Notification; +import com.sun.nio.sctp.AbstractNotificationHandler; +import com.sun.nio.sctp.AssociationChangeNotification; +import com.sun.nio.sctp.HandlerResult; +import com.sun.nio.sctp.PeerAddressChangeNotification; +import com.sun.nio.sctp.SendFailedNotification; +import com.sun.nio.sctp.ShutdownNotification; + +/** + * Implements NotificationHandler for the OneToManyAssocMultiplexer class. Its main responsibility is to delegate the notification to the NotificationHandler of the corresponding + * OneToManyAssociationImpl. Each handler method calls the resolveAssociatonImpl method to find the corresponding Association. + * + * @author alerant appngin + * + */ +class MultiAssociationHandler extends AbstractNotificationHandler { + + private static final Logger logger = Logger.getLogger(MultiAssociationHandler.class); + + public MultiAssociationHandler() { + + } + + /** + * The delegateNotificationHandling method resolves the OneToManyAssociationImpl instance which belongs to the given Notification and calls the handleNotification method of the resolved Association. + * In case the association instance can not be resolved the method returns the value of the defaultResult parameter. + * + * @param not - Notification that need to be delegated + * @param defaultResult - Default return value used when Association instance can not be resolved + * @param multiplexer - The OneToManyAssocMultiplexer that is attached to this MultiAssociationHandler instance + * @return - If delegation is successful it returns the return result of the handler method of the corresponding OneToManyAssociationImpl instance otherwise ű + * returns the value of the defaultResult parameter. + */ + private HandlerResult delegateNotificationHandling(Notification not, HandlerResult defaultResult, OneToManyAssocMultiplexer multiplexer) { + OneToManyAssociationImpl association = multiplexer.resolveAssociationImpl(not.association()); + if (association == null) { + return defaultResult; + } + if (logger.isDebugEnabled()) { + logger.debug("Notification: "+not+" is being delagated to associationHandler: "+association.associationHandler); + } + return association.associationHandler.handleNotification(not, association); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (not.association() == null) { + logger.error("Cannot handle AssociationChangeNotification: association method of AssociationChangeNotification: "+not+" returns null value, handler returns CONTINUE"); + return HandlerResult.CONTINUE; + } + return delegateNotificationHandling(not, HandlerResult.CONTINUE, multiplexer); + } + + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (not.association() == null) { + logger.error("Cannot handle ShutdownNotification: assoction method of ShutdownNotification: "+not+" returns null value, handler returns RETURN"); + return HandlerResult.RETURN; + } + return delegateNotificationHandling(not, HandlerResult.RETURN, multiplexer); + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (notification.association() == null) { + logger.error("Cannot handle SendFailedNotification: assoction method of SendFailedNotification: "+notification+" returns null value, handler returns RETURN"); + return HandlerResult.RETURN; + } + return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (notification.association() == null) { + logger.error("Cannot handle PeerAddressChangeNotification: assoction method of PeerAddressChangeNotification: "+notification+" returns null value, handler returns CONTINUE"); + return HandlerResult.RETURN; + } + return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java new file mode 100644 index 0000000..e349d06 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java @@ -0,0 +1,94 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual + * contributors as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a full listing + * of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License, v. 2.0. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * v. 2.0 along with this distribution; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.mobicents.protocols.sctp.multiclient; + +import java.nio.channels.spi.AbstractSelectableChannel; + +/** + * @author amit bhayani + * @author alerant appngin + * + */ +public final class MultiChangeRequest { + public static final int REGISTER = 1; + public static final int CHANGEOPS = 2; + public static final int CONNECT = 3; + public static final int CLOSE = 4; + public static final int ADD_OPS = 5; + + private int type; + private int ops; + private AbstractSelectableChannel socketChannel; + private OneToManyAssocMultiplexer assocMultiplexer; + + private long executionTime; + + protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, int type, int ops) { + this.type = type; + this.ops = ops; + this.socketChannel = socketChannel; + this.assocMultiplexer = assocMultiplexer; + if (socketChannel == null && assocMultiplexer != null) { + this.socketChannel = assocMultiplexer.getSocketMultiChannel(); + } + } + + protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, int type, long executionTime) { + this(null, assocMultiplexer, type, -1); + this.executionTime = executionTime; + } + + /** + * @return the type + */ + protected int getType() { + return type; + } + + /** + * @return the ops + */ + protected int getOps() { + return ops; + } + + /** + * @return the socketChannel + */ + protected AbstractSelectableChannel getSocketChannel() { + return socketChannel; + } + + /** + * @return the association + */ + protected OneToManyAssocMultiplexer getAssocMultiplexer() { + return assocMultiplexer; + } + + /** + * @return the executionTime + */ + protected long getExecutionTime() { + return executionTime; + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java new file mode 100644 index 0000000..a114d37 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -0,0 +1,86 @@ +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; + +import org.apache.log4j.Logger; + +/** + * Stores and manages of the OneToManyAssocMultiplexer instances of a MultiManagementImpl (SCTP stack) + * + * @author alerant appngin + * + */ +public class MultiChannelController { + private static final Logger logger = Logger.getLogger(MultiChannelController.class); + + private MultiManagementImpl management; + + private final HashMap> multiplexers = new HashMap>(); + + public MultiChannelController(MultiManagementImpl management) { + super(); + this.management = management; + } + + + private OneToManyAssocMultiplexer findMultiplexerByHostAddrInfo(OneToManyAssociationImpl.HostAddressInfo hostAddressInfo) { + OneToManyAssocMultiplexer ret = null; + if (logger.isDebugEnabled()) { + logger.debug("Number of multiplexers: "+multiplexers.size()); + } + ArrayList mList = multiplexers.get(hostAddressInfo.getHostPort()); + if (mList == null || mList.isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("No multiplexers found for local port: "+hostAddressInfo.getHostPort()); + } + } else { + for (OneToManyAssocMultiplexer am: mList) { + if (am.getHostAddressInfo().matches(hostAddressInfo)) { + ret = am; + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("findMultiplexerByHostAddr: "+hostAddressInfo+" returns: "+ret); + } + return ret; + } + + private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrInfo, OneToManyAssocMultiplexer multiplexer) { + ArrayList mList = multiplexers.get(hostAddrInfo.getHostPort()); + if (mList == null) { + mList = new ArrayList(); + multiplexers.put(hostAddrInfo.getHostPort(), mList); + } + mList.add(multiplexer); + } + + /** + * Using the host address information of the given OneToManyAssociationImpl finds the appropriate multiplexer instance and register it. + * If the multiplexer instance does not exists it is created by the method. + * @param assocImpl - OneToManyAssociation instance need to be registered to the appropriate OneToManyAssocMultiplexer + * @return - the OneToManyAssocMultiplexer that is associated to the OneToManyAssociationImpl assocImpl + * @throws IOException + */ + protected OneToManyAssocMultiplexer register(OneToManyAssociationImpl assocImpl) throws IOException { + if (assocImpl == null || assocImpl.getAssocInfo() == null || assocImpl.getAssocInfo().getHostInfo() == null) { + return null; + } + if (logger.isDebugEnabled()) { + logger.debug("register: "+assocImpl); + } + OneToManyAssocMultiplexer ret = null; + synchronized (multiplexers) { + ret = findMultiplexerByHostAddrInfo(assocImpl.getAssocInfo().getHostInfo()); + if (ret == null) { + ret = new OneToManyAssocMultiplexer(assocImpl.getAssocInfo().getHostInfo(), management); + storeMultiplexer(assocImpl.getAssocInfo().getHostInfo(), ret); + } + ret.registerAssociation(assocImpl); + } + return ret; + } + +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java new file mode 100644 index 0000000..fec803b --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -0,0 +1,735 @@ +/* + * TeleStax, Open Source Cloud Communications Copyright 2012. + * and individual contributors + * by the @authors tag. See the copyright.txt in the distribution for a + * full listing of individual contributors. + * + * This is free software; you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this software; if not, write to the Free + * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA, or see the FSF site: http://www.fsf.org. + */ + +package org.mobicents.protocols.sctp.multiclient; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.Selector; +import java.nio.channels.spi.SelectorProvider; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import javolution.text.TextBuilder; +import javolution.util.FastList; +import javolution.util.FastMap; +import javolution.xml.XMLObjectReader; +import javolution.xml.XMLObjectWriter; +import javolution.xml.stream.XMLStreamException; + +import org.apache.log4j.Logger; +import org.mobicents.protocols.api.Association; +import org.mobicents.protocols.api.IpChannelType; +import org.mobicents.protocols.api.Management; +import org.mobicents.protocols.api.ManagementEventListener; +import org.mobicents.protocols.api.Server; +import org.mobicents.protocols.api.ServerListener; +import org.mobicents.protocols.sctp.AssociationMap; + +/** + * This class is a partial implementation of the Management interface of the sctp-api. + * It is partial because it does not support the whole functionality of the interface instead + * it extends the capabilities of the implementition provided by org.mobicents.protocols.sctp package + * with the capability to use One-To-Many type SCTP client associations. + * + * Therefore the following functionality is not supported by this class: + * server type associations + * TCP ipChannelType + * + * @author amit bhayani + * @author alerant appngin + * + * MultiManagementImpl is a limited implemention OneToMany client associations. + * + */ +public class MultiManagementImpl implements Management { + + private static final Logger logger = Logger.getLogger(MultiManagementImpl.class); + + private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; + private static final String USER_DIR_KEY = "user.dir"; + private static final String PERSIST_FILE_NAME = "sctp.xml"; + private static final String ASSOCIATIONS = "associations"; + + private static final String CONNECT_DELAY_PROP = "connectdelay"; + private static final String SINGLE_THREAD_PROP = "singlethread"; + private static final String WORKER_THREADS_PROP = "workerthreads"; + + private final TextBuilder persistFile = TextBuilder.newInstance(); + + protected static final MultiSctpXMLBinding binding = new MultiSctpXMLBinding(); + protected static final String TAB_INDENT = "\t"; + private static final String CLASS_ATTRIBUTE = "type"; + + private final String name; + + protected String persistDir = null; + + protected AssociationMap associations = new AssociationMap(); + + private FastList pendingChanges = new FastList(); + + // Create a new selector + private Selector socketSelector = null; + + private MultiSelectorThread selectorThread = null; + + static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2; + + private int workerThreads = DEFAULT_IO_THREADS; + + private boolean singleThread = true; + + private int workerThreadCount = 0; + + // Maximum IO Errors tolerated by Socket. After this the Socket will be + // closed and attempt will be made to open again + private int maxIOErrors = 3; + + private int connectDelay = 5000; + + private ExecutorService[] executorServices = null; + + private FastList managementEventListeners = new FastList(); + + private volatile boolean started = false; + + private final MultiChannelController multiChannelController = new MultiChannelController(this); + + public MultiManagementImpl(String name) throws IOException { + this.name = name; + binding.setClassAttribute(CLASS_ATTRIBUTE); + binding.setAlias(OneToManyAssociationImpl.class, "association"); + binding.setAlias(String.class, "string"); + this.socketSelector = SelectorProvider.provider().openSelector(); + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + public String getPersistDir() { + return persistDir; + } + + public void setPersistDir(String persistDir) { + this.persistDir = persistDir; + } + + /** + * @return the connectDelay + */ + public int getConnectDelay() { + return connectDelay; + } + + /** + * @param connectDelay + * the connectDelay to set + */ + public void setConnectDelay(int connectDelay) { + this.connectDelay = connectDelay; + + this.store(); + } + + /** + * @return the workerThreads + */ + public int getWorkerThreads() { + return workerThreads; + } + + /** + * @param workerThreads + * the workerThreads to set + */ + public void setWorkerThreads(int workerThreads) { + if (workerThreads < 1) { + workerThreads = DEFAULT_IO_THREADS; + } + this.workerThreads = workerThreads; + + this.store(); + } + + /** + * @return the maxIOErrors + */ + public int getMaxIOErrors() { + return maxIOErrors; + } + + /** + * @param maxIOErrors + * the maxIOErrors to set + */ + public void setMaxIOErrors(int maxIOErrors) { + if (maxIOErrors < 1) { + maxIOErrors = 1; + } + this.maxIOErrors = maxIOErrors; + } + + /** + * @return the singleThread + */ + public boolean isSingleThread() { + return singleThread; + } + + /** + * @param singleThread + * the singleThread to set + */ + public void setSingleThread(boolean singleThread) { + this.singleThread = singleThread; + + this.store(); + } + + protected FastList getManagementEventListeners() { + return managementEventListeners; + } + + public void addManagementEventListener(ManagementEventListener listener) { + synchronized (this) { + if (this.managementEventListeners.contains(listener)) + return; + + FastList newManagementEventListeners = new FastList(); + newManagementEventListeners.addAll(this.managementEventListeners); + newManagementEventListeners.add(listener); + this.managementEventListeners = newManagementEventListeners; + } + } + + public void removeManagementEventListener(ManagementEventListener listener) { + synchronized (this) { + if (!this.managementEventListeners.contains(listener)) + return; + + FastList newManagementEventListeners = new FastList(); + newManagementEventListeners.addAll(this.managementEventListeners); + newManagementEventListeners.remove(listener); + this.managementEventListeners = newManagementEventListeners; + } + } + + protected MultiChannelController getMultiChannelController() { + return multiChannelController; + } + + public void start() throws Exception { + + if (this.started) { + logger.warn(String.format("management=%s is already started", this.name)); + return; + } + + synchronized (this) { + this.persistFile.clear(); + + if (persistDir != null) { + this.persistFile.append(persistDir).append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME); + } else { + persistFile.append(System.getProperty(SCTP_PERSIST_DIR_KEY, System.getProperty(USER_DIR_KEY))).append(File.separator).append(this.name) + .append("_").append(PERSIST_FILE_NAME); + } + + logger.info(String.format("SCTP configuration file path %s", persistFile.toString())); + + try { + this.load(); + } catch (FileNotFoundException e) { + logger.warn(String.format("Failed to load the SCTP configuration file. \n%s", e.getMessage())); + } + + if (!this.singleThread) { + // If not single thread model we create worker threads + this.executorServices = new ExecutorService[this.workerThreads]; + for (int i = 0; i < this.workerThreads; i++) { + this.executorServices[i] = Executors.newSingleThreadExecutor(); + } + } + this.selectorThread = new MultiSelectorThread(this.socketSelector, this); + this.selectorThread.setStarted(true); + + (new Thread(this.selectorThread)).start(); + + this.started = true; + + if (logger.isInfoEnabled()) { + logger.info(String.format("Started SCTP Management=%s WorkerThreads=%d SingleThread=%s", this.name, + (this.singleThread ? 0 : this.workerThreads), this.singleThread)); + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onServiceStarted(); + } catch (Throwable ee) { + logger.error("Exception while invoking onServiceStarted", ee); + } + } + } + } + + public void stop() throws Exception { + + if (!this.started) { + logger.warn(String.format("management=%s is already stopped", this.name)); + return; + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onServiceStopped(); + } catch (Throwable ee) { + logger.error("Exception while invoking onServiceStopped", ee); + } + } + + // We store the original state first + this.store(); + + // Stop all associations + FastMap associationsTemp = this.associations; + for (FastMap.Entry n = associationsTemp.head(), end = associationsTemp.tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + if (associationTemp.isStarted()) { + ((OneToManyAssociationImpl) associationTemp).stop(); + } + } + + if (this.executorServices != null) { + for (int i = 0; i < this.executorServices.length; i++) { + this.executorServices[i].shutdown(); + } + } + + this.selectorThread.setStarted(false); + this.socketSelector.wakeup(); // Wakeup selector so SelectorThread dies + + // waiting till stopping associations + for (int i1 = 0; i1 < 20; i1++) { + boolean assConnected = false; + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + if (associationTemp.isConnected()) { + assConnected = true; + break; + } + } + if (!assConnected) + break; + Thread.sleep(100); + } + + // Graceful shutdown for each of Executors + if (this.executorServices != null) { + for (int i = 0; i < this.executorServices.length; i++) { + if (!this.executorServices[i].isTerminated()) { + if (logger.isInfoEnabled()) { + logger.info("Waiting for worker thread to die gracefully ...."); + } + try { + this.executorServices[i].awaitTermination(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Do we care? + } + } + } + } + + this.started = false; + } + + public boolean isStarted(){ + return this.started; + } + + @SuppressWarnings("unchecked") + public void load() throws FileNotFoundException { + XMLObjectReader reader = null; + try { + reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); + reader.setBinding(binding); + + try { + this.connectDelay = reader.read(CONNECT_DELAY_PROP, Integer.class); + this.workerThreads = reader.read(WORKER_THREADS_PROP, Integer.class); + this.singleThread = reader.read(SINGLE_THREAD_PROP, Boolean.class); + } catch (java.lang.NullPointerException npe) { + // ignore. + // For backward compatibility we can ignore if these values are not defined + } + + + this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + OneToManyAssociationImpl associationTemp = (OneToManyAssociationImpl) n.getValue(); + associationTemp.setManagement(this); + } + + } catch (XMLStreamException ex) { + // this.logger.info( + // "Error while re-creating Linksets from persisted file", ex); + } + } + + public void store() { + try { + XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString())); + writer.setBinding(binding); + // Enables cross-references. + // writer.setReferenceResolver(new XMLReferenceResolver()); + writer.setIndentation(TAB_INDENT); + + writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class); + writer.write(this.workerThreads, WORKER_THREADS_PROP, Integer.class); + writer.write(this.singleThread, SINGLE_THREAD_PROP, Boolean.class); + + writer.write(this.associations, ASSOCIATIONS, AssociationMap.class); + + writer.close(); + } catch (Exception e) { + logger.error("Error while persisting the Rule state in file", e); + } + } + + public void removeAllResourses() throws Exception { + + synchronized (this) { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (this.associations.size() == 0) + // no resources allocated - nothing to do + return; + + if (logger.isInfoEnabled()) { + logger.info(String.format("Removing allocated resources: Associations=%d", this.associations.size())); + } + + synchronized (this) { + // Remove all associations + ArrayList lst = new ArrayList(); + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + lst.add(n.getKey()); + } + for (String n : lst) { + this.stopAssociation(n); + this.removeAssociation(n); + } + + // We store the cleared state + this.store(); + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onRemoveAllResources(); + } catch (Throwable ee) { + logger.error("Exception while invoking onRemoveAllResources", ee); + } + } + } + } + + + public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName) throws Exception { + return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, null); + } + + public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, IpChannelType ipChannelType, + String[] extraHostAddresses) throws Exception { + + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (hostAddress == null) { + throw new Exception("Host address cannot be null"); + } + + if (hostPort < 0) { + throw new Exception("Host port cannot be less than 0"); + } + + if (peerAddress == null) { + throw new Exception("Peer address cannot be null"); + } + + if (peerPort < 1) { + throw new Exception("Peer port cannot be less than 1"); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + synchronized (this) { + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + + if (assocName.equals(associationTemp.getName())) { + throw new Exception(String.format("Already has association=%s", associationTemp.getName())); + } +/* TODO: We should need a new condition + if (peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) { + throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d", associationTemp.getName(), + peerAddress, peerPort)); + } + + if (hostAddress.equals(associationTemp.getHostAddress()) && associationTemp.getHostPort() == hostPort) { + throw new Exception(String.format("Already has association=%s with same host address=%s and port=%d", associationTemp.getName(), + hostAddress, hostPort)); + } +*/ + } + + OneToManyAssociationImpl association = new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + association.setManagement(this); + + AssociationMap newAssociations = new AssociationMap(); + newAssociations.putAll(this.associations); + newAssociations.put(assocName, association); + this.associations = newAssociations; + // associations.put(assocName, association); + + this.store(); + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onAssociationAdded(association); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationAdded", ee); + } + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("Added Associoation=%s of type=%s", association.getName(), association.getAssociationType())); + } + + return association; + } + } + + public Association getAssociation(String assocName) throws Exception { + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + Association associationTemp = this.associations.get(assocName); + + if (associationTemp == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + return associationTemp; + } + + /** + * @return the associations + */ + public Map getAssociations() { + Map routeTmp = new HashMap(); + routeTmp.putAll(this.associations); + return routeTmp; + } + + public void startAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + Association associationTemp = this.associations.get(assocName); + + if (associationTemp == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + if (associationTemp.isStarted()) { + throw new Exception(String.format("Association=%s is already started", assocName)); + } + + ((OneToManyAssociationImpl) associationTemp).start(); + this.store(); + } + + public void stopAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + Association association = this.associations.get(assocName); + + if (association == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + ((OneToManyAssociationImpl) association).stop(); + this.store(); + } + + public void removeAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + synchronized (this) { + Association association = this.associations.get(assocName); + + if (association == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + if (association.isStarted()) { + throw new Exception(String.format("Association name=%s is started. Stop before removing", assocName)); + } + + AssociationMap newAssociations = new AssociationMap(); + newAssociations.putAll(this.associations); + newAssociations.remove(assocName); + this.associations = newAssociations; + // this.associations.remove(assocName); + + this.store(); + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onAssociationRemoved(association); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationRemoved", ee); + } + } + } + } + + /** + * @return the pendingChanges + */ + protected FastList getPendingChanges() { + return pendingChanges; + } + + /** + * @return the socketSelector + */ + protected Selector getSocketSelector() { + return socketSelector; + } + + protected void populateWorkerThread(int workerThreadTable[]) { + for (int count = 0; count < workerThreadTable.length; count++) { + if (this.workerThreadCount == this.workerThreads) { + this.workerThreadCount = 0; + } + + workerThreadTable[count] = this.workerThreadCount; + this.workerThreadCount++; + } + } + + protected ExecutorService getExecutorService(int index) { + return this.executorServices[index]; + } + + /*unimplemented management methods*/ + @Override + public Server addServer(String serverName, String hostAddress, int port) + throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + + @Override + public Server addServer(String serverName, String hostAddress, int port, + IpChannelType ipChannelType, boolean acceptAnonymousConnections, + int maxConcurrentConnectionsCount, String[] extraHostAddresses) + throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public Server addServer(String serverName, String hostAddress, int port, + IpChannelType ipChannelType, String[] extraHostAddresses) + throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public Association addServerAssociation(String peerAddress, int peerPort, + String serverName, String assocName) throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public Association addServerAssociation(String peerAddress, int peerPort, + String serverName, String assocName, IpChannelType ipChannelType) + throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not support server type associations!"); + } + @Override + public ServerListener getServerListener() { + return null; + } + @Override + public List getServers() { + return Collections.emptyList(); + } + @Override + public void removeServer(String serverName) throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public void setServerListener(ServerListener serverListener) { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public void startServer(String serverName) throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } + @Override + public void stopServer(String serverName) throws Exception { + throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java new file mode 100644 index 0000000..35d1555 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java @@ -0,0 +1,70 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual + * contributors as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a full listing + * of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License, v. 2.0. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * v. 2.0 along with this distribution; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.mobicents.protocols.sctp.multiclient; + +import java.util.Iterator; +import java.util.Map; + +import javolution.xml.XMLBinding; +import javolution.xml.XMLFormat; +import javolution.xml.stream.XMLStreamException; + +import org.mobicents.protocols.sctp.AssociationMap; + +/** + * @author amit bhayani + * @author alerant appngin + */ +public class MultiSctpXMLBinding extends XMLBinding { + + protected static final XMLFormat ASSOCIATION_MAP = new XMLFormat(AssociationMap.class) { + + @Override + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { + final Map map = (Map) obj; + + for (Iterator it = map.entrySet().iterator(); it.hasNext();) { + Map.Entry entry = (Map.Entry) it.next(); + + xml.add((String) entry.getKey(), "name", String.class); + xml.add((OneToManyAssociationImpl) entry.getValue(), "association", OneToManyAssociationImpl.class); + } + } + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { + while (xml.hasNext()) { + String key = xml.get("name", String.class); + OneToManyAssociationImpl association = xml.get("association", OneToManyAssociationImpl.class); + obj.put(key, association); + } + } + + }; + + protected XMLFormat getFormat(Class forClass) throws XMLStreamException { + if (AssociationMap.class.equals(forClass)) { + return ASSOCIATION_MAP; + } + return super.getFormat(forClass); + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java new file mode 100644 index 0000000..9a0dd3d --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -0,0 +1,253 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual + * contributors as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a full listing + * of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License, v. 2.0. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * v. 2.0 along with this distribution; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.util.Iterator; + +import javolution.util.FastList; + +import org.apache.log4j.Logger; + +import com.sun.nio.sctp.AssociationChangeNotification; +import com.sun.nio.sctp.AssociationChangeNotification.AssocChangeEvent; +import com.sun.nio.sctp.SctpChannel; + +/** + * @author amit bhayani + * @author alerant appngin + * + */ +public class MultiSelectorThread implements Runnable { + + protected static final Logger logger = Logger.getLogger(MultiSelectorThread.class); + + protected Selector selector; + + protected MultiManagementImpl management = null; + + protected volatile boolean started = true; + + /** + * Creates the MultiSelector instance for the given MultiManagementImpl (SCTP stack) and Selector + * + * @param selector + * @param management + */ + protected MultiSelectorThread(Selector selector, MultiManagementImpl management) { + super(); + this.selector = selector; + this.management = management; + } + + /** + * @param started + * the started to set + */ + protected void setStarted(boolean started) { + this.started = started; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + if (logger.isInfoEnabled()) { + logger.info(String.format("SelectorThread for Management=%s started.", this.management.getName())); + } + while (this.started) { + try { + FastList pendingChanges = this.management.getPendingChanges(); + + // Process any pending changes + synchronized (pendingChanges) { + Iterator changes = pendingChanges.iterator(); + while (changes.hasNext()) { + MultiChangeRequest change = changes.next(); + SelectionKey key = change.getSocketChannel().keyFor(this.selector); + switch (change.getType()) { + case MultiChangeRequest.CHANGEOPS: + pendingChanges.remove(change); + key.interestOps(change.getOps()); + break; + case MultiChangeRequest.ADD_OPS : + pendingChanges.remove(change); + key.interestOps(key.interestOps() | change.getOps()); + break; + case MultiChangeRequest.REGISTER: + pendingChanges.remove(change); + SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); + key1.attach(change.getAssocMultiplexer()); + AssocChangeEvent ace = AssocChangeEvent.COMM_UP; + AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); + change.getAssocMultiplexer().associationHandler.handleNotification(acn, change.getAssocMultiplexer()); + break; + case MultiChangeRequest.CONNECT: + pendingChanges.remove(change); + /*TODO + if (!change.getAssociation().isStarted()) { + // if Association is stopped - remove pending connection requests + pendingChanges.remove(change); + } else { + if (change.getExecutionTime() <= System.currentTimeMillis()) { + pendingChanges.remove(change); + change.getAssociation().initiateConnection(); + } + }*/ + break; + case MultiChangeRequest.CLOSE: + pendingChanges.remove(change); + //TODO change.getAssociation().close(); + } + }// end of while + } + + // Wait for an event one of the registered channels + this.selector.select(500); + + //logger.debug("Done selecting, selected keys size: " + this.selector.selectedKeys().size()); + + // Iterate over the set of keys for which events are available + Iterator selectedKeys = this.selector.selectedKeys().iterator(); + + while (selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + + if (!key.isValid()) { + continue; + } + + // Check what event is available and deal with it + if (key.isConnectable()) { + logger.error("Illegal selectionKey state: connectable"); + } + if (key.isAcceptable()) { + logger.error("Illegal selectionKey state: acceptable"); + } + if (key.isReadable()) { + this.read(key); + } + if (key.isWritable()) { + this.write(key); + } + } + + } catch (Exception e) { + logger.error("Error while selecting the ready keys", e); + e.printStackTrace(); + } + } + + try { + this.selector.close(); + } catch (IOException e) { + logger.error(String.format("Error while closing Selector for SCTP Management=%s", this.management.getName())); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("SelectorThread for Management=%s stopped.", this.management.getName())); + } + } +/* + private void finishConnection(SelectionKey key) throws IOException{ + this.finishConnectionMultiSctp(key); + } +/* + private void finishConnectionMultiSctp(SelectionKey key) throws IOException { + OneToManyAssociationImpl association = (OneToManyAssociationImpl) key.attachment(); + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s connected to=%s", association.getName(), "TODO")); + } + this.read(key); + // Register an interest in writing on this channel + key.interestOps(SelectionKey.OP_READ); + + + AssocChangeEvent ace = AssocChangeEvent.COMM_UP; + AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); + association.associationHandler.handleNotification(acn, association); + } +/* + private void finishConnectionSctp(SelectionKey key) throws IOException { + + OneToManyAssociationImpl association = (OneToManyAssociationImpl) key.attachment(); + try { + + SctpChannel socketChannel = (SctpChannel) key.channel(); + + if (socketChannel.isConnectionPending()) { + + // TODO Loop? Or may be sleep for while? + while (socketChannel.isConnectionPending()) { + socketChannel.finishConnect(); + } + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s connected to=%s", association.getName(), socketChannel.getRemoteAddresses())); + } + + // Register an interest in writing on this channel + key.interestOps(SelectionKey.OP_READ); + } catch (Exception e) { + logger.error(String.format("Exception while finishing connection for Association=%s", association.getName()), e); + association.scheduleConnect(); + } + }*/ + + private void read(SelectionKey key) throws IOException { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.read(); + } + + private void write(SelectionKey key) throws IOException { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.write(key); + } + + class AssociationChangeNotification2 extends AssociationChangeNotification { + + private AssocChangeEvent assocChangeEvent; + + public AssociationChangeNotification2(AssocChangeEvent assocChangeEvent) { + this.assocChangeEvent = assocChangeEvent; + } + + @Override + public com.sun.nio.sctp.Association association() { + return null; + } + + @Override + public AssocChangeEvent event() { + return this.assocChangeEvent; + } + } +} + diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java new file mode 100644 index 0000000..dc81489 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java @@ -0,0 +1,64 @@ +/* + * JBoss, Home of Professional Open Source + * Copyright 2011, Red Hat, Inc. and/or its affiliates, and individual + * contributors as indicated by the @authors tag. All rights reserved. + * See the copyright.txt in the distribution for a full listing + * of individual contributors. + * + * This copyrighted material is made available to anyone wishing to use, + * modify, copy, or redistribute it subject to the terms and conditions + * of the GNU General Public License, v. 2.0. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License, + * v. 2.0 along with this distribution; if not, write to the Free + * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ +package org.mobicents.protocols.sctp.multiclient; + +import org.mobicents.protocols.api.Association; +import org.mobicents.protocols.api.AssociationListener; +import org.mobicents.protocols.api.PayloadData; + +/** + * @author amit bhayani + * + */ +public class MultiWorker implements Runnable { + + private Association association; + private AssociationListener associationListener = null; + private PayloadData payloadData = null; + + /** + * @param association + * @param associationListener + * @param payloadData + */ + protected MultiWorker(Association association, AssociationListener associationListener, PayloadData payloadData) { + super(); + this.association = association; + this.associationListener = associationListener; + this.payloadData = payloadData; + } + + /* + * (non-Javadoc) + * + * @see java.lang.Runnable#run() + */ + @Override + public void run() { + try { + this.associationListener.onPayload(this.association, this.payloadData); + } catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java new file mode 100644 index 0000000..d84613c --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -0,0 +1,324 @@ +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +import javolution.util.FastList; + +import org.apache.log4j.Logger; +import org.mobicents.protocols.api.PayloadData; +import org.mobicents.protocols.sctp.multiclient.OneToManyAssociationImpl.HostAddressInfo; + +import com.sun.nio.sctp.MessageInfo; +import com.sun.nio.sctp.SctpMultiChannel; + +public class OneToManyAssocMultiplexer { + private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); + + private HostAddressInfo hostAddressInfo; + private SctpMultiChannel socketMultiChannel; + private MultiManagementImpl management; + // The buffer into which we'll read data when it's available + private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); + + + // Queue holds payloads to be transmitted + private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); + + private ArrayList pendingAssocs = new ArrayList(); + private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); + + protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); + + /* + * Support fast and save queue operations like: + * + */ + static class ConcurrentLinkedQueueSwapper { + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private ConcurrentLinkedQueue queue; + + public ConcurrentLinkedQueueSwapper(ConcurrentLinkedQueue queue) { + this.queue = queue; + } + + public void add(T e) { + lock.readLock().lock(); + queue.add(e); + lock.readLock().unlock(); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + public ConcurrentLinkedQueue swap(ConcurrentLinkedQueue newQueue) { + if (newQueue == null) { + throw new NullPointerException(this.getClass()+".swap(ConcurrentLinkedQueue newQueue): newQueue parameter can not be null!"); + } + ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newQueue); + lock.writeLock().lock(); + ConcurrentLinkedQueue oldQueue = this.queue; + this.queue = newQueueCopy; + lock.writeLock().unlock(); + return oldQueue; + } + + public void concatAsHead(ConcurrentLinkedQueue newHead) { + if (newHead == null) { + throw new NullPointerException(this.getClass()+".concatAsHead(ConcurrentLinkedQueue newHead): newHead parameter can not be null!"); + } + ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newHead); + lock.writeLock().lock(); + for (T e: this.queue) { + newQueueCopy.add(e); + } + this.queue = newQueueCopy; + lock.writeLock().unlock(); + } + + } + public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagementImpl management) throws IOException { + super(); + if (hostAddressInfo == null || management == null) { + throw new IllegalArgumentException("Constructor OneToManyAssocMultiplexer: hostAddressInfo and management parameters can not be null!"); + } + this.hostAddressInfo = hostAddressInfo; + this.management = management; + // clean receiver buffer + this.rxBuffer.clear(); + this.rxBuffer.rewind(); + this.rxBuffer.flip(); + initMultiChannel(); + } + + protected void registerAssociation(OneToManyAssociationImpl association) { + synchronized (pendingAssocs) { + pendingAssocs.add(association); + } + } + + protected void assignSctpAssocIdToAssociation(Integer id, OneToManyAssociationImpl association) { + if (id == null || association == null) { + return; + } + connectedAssocs.put(id, association); + association.assignSctpAssociationId(id); + } + + protected OneToManyAssociationImpl findConnectedAssociation(Integer sctpAssocId) { + return connectedAssocs.get(sctpAssocId); + } + + private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation) { + String peerAddresses = ""; + try { + for (SocketAddress sa : getSocketMultiChannel().getRemoteAddresses(sctpAssociation)) { + peerAddresses += ", "+sa.toString(); + } + } catch (IOException e) { } + return peerAddresses; + } + + protected OneToManyAssociationImpl findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { + String peerAddresses = extractPeerAddresses(sctpAssociation); + if (logger.isDebugEnabled()) { + peerAddresses = peerAddresses.isEmpty() ? peerAddresses : peerAddresses.substring(2); + logger.debug("Association("+sctpAssociation.associationID()+") connected to "+peerAddresses); + } + OneToManyAssociationImpl ret=null; + for (OneToManyAssociationImpl assocImpl : pendingAssocs) { + if (assocImpl.isConnectedToPeerAddresses(peerAddresses)) { + ret = assocImpl; + break; + } + } + return ret; + } + + private void initMultiChannel() throws IOException { + socketMultiChannel = SctpMultiChannel.open(); + socketMultiChannel.configureBlocking(false); + socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); + + if (logger.isDebugEnabled()) { + logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); + } + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + } + } + + public HostAddressInfo getHostAddressInfo() { + return hostAddressInfo; + } + public SctpMultiChannel getSocketMultiChannel() { + return socketMultiChannel; + } + + private OneToManyAssociationImpl getAssociationByMessageInfo(MessageInfo msgInfo) { + OneToManyAssociationImpl ret = null; + //find connected assoc + if (msgInfo.association() != null) { + ret = findConnectedAssociation(msgInfo.association().associationID()); + } + //find in pending assoc + if (ret == null) { + ret = findPendingAssociation(msgInfo.association()); + } + return ret; + } + + protected void send(PayloadData payloadData, MessageInfo messageInfo, OneToManyAssociationImpl sender) throws IOException { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(this.getSocketMultiChannel(), this, MultiChangeRequest.ADD_OPS, + SelectionKey.OP_WRITE)); + + this.txQueueSwapper.add(new SctpMessage(payloadData, messageInfo, sender)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + + protected void write(SelectionKey key) { + ConcurrentLinkedQueue txQueueTmp = txQueueSwapper.swap(new ConcurrentLinkedQueue()); + HashSet skipList = new HashSet(); + ConcurrentLinkedQueue retransmitQueue = new ConcurrentLinkedQueue(); + + if (txQueueTmp.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + if (logger.isDebugEnabled()) { + logger.debug("write: txQueue was empty"); + } + return; + } + + while (!txQueueTmp.isEmpty()) { + SctpMessage msg = txQueueTmp.poll(); + if (skipList.contains(msg.getSenderAssoc().getName())) { + retransmitQueue.add(msg); + } else { + if (!msg.getSenderAssoc().write(msg.getPayloadData())) { + skipList.add(msg.getSenderAssoc().getName()); + retransmitQueue.add(msg); + } + } + } + + if (!retransmitQueue.isEmpty()) { + txQueueSwapper.concatAsHead(retransmitQueue); + } + + //TODO see dev notes + if (txQueueTmp.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + } + } + + + private void doReadSctp() throws IOException { + + rxBuffer.clear(); + MessageInfo messageInfo = null; + messageInfo = this.socketMultiChannel.receive(rxBuffer, this, this.associationHandler); + + if (messageInfo == null) { + if (logger.isDebugEnabled()) { + logger.debug(String.format(" messageInfo is null for AssociationMultiplexer=%s", this)); + } + return; + } + + int len = messageInfo.bytes(); + if (len == -1) { + logger.error(String.format("Rx -1 while trying to read from underlying socket for AssociationMultiplexer=%s ", + this)); + return; + } + + rxBuffer.flip(); + byte[] data = new byte[len]; + rxBuffer.get(data); + rxBuffer.clear(); + + PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), + messageInfo.payloadProtocolID(), messageInfo.streamNumber()); + + OneToManyAssociationImpl assoc = getAssociationByMessageInfo(messageInfo); + if (assoc != null) { + assoc.read(payload); + } + + } + + + protected void read() { + try { + doReadSctp(); + } catch (IOException e) { + logger.error("Unable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, e); + } + } + + protected OneToManyAssociationImpl resolveAssociationImpl(com.sun.nio.sctp.Association sctpAssociation) { + OneToManyAssociationImpl association = findConnectedAssociation(sctpAssociation.associationID()); + if (association == null) { + association = findPendingAssociation(sctpAssociation); + assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); + } + if (logger.isDebugEnabled()) { + logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); + } + return association; + } + + static class SctpMessage { + private PayloadData payloadData; + private MessageInfo messageInfo; + private OneToManyAssociationImpl senderAssoc; + private SctpMessage(PayloadData payloadData, MessageInfo messageInfo, + OneToManyAssociationImpl senderAssoc) { + super(); + this.payloadData = payloadData; + this.messageInfo = messageInfo; + this.senderAssoc = senderAssoc; + } + private PayloadData getPayloadData() { + return payloadData; + } + private MessageInfo getMessageInfo() { + return messageInfo; + } + private OneToManyAssociationImpl getSenderAssoc() { + return senderAssoc; + } + @Override + public String toString() { + return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" + + messageInfo + ", senderAssoc=" + senderAssoc + "]"; + } + } + +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java new file mode 100644 index 0000000..9f46f80 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -0,0 +1,170 @@ +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.net.SocketAddress; + +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +import com.sun.nio.sctp.AbstractNotificationHandler; +import com.sun.nio.sctp.AssociationChangeNotification; +import com.sun.nio.sctp.HandlerResult; +import com.sun.nio.sctp.Notification; +import com.sun.nio.sctp.PeerAddressChangeNotification; +import com.sun.nio.sctp.SendFailedNotification; +import com.sun.nio.sctp.ShutdownNotification; + +public class OneToManyAssociationHandler extends AbstractNotificationHandler { + + private static final Logger logger = Logger.getLogger(OneToManyAssociationHandler.class); + + //Default value is 1 for TCP TODO what is it used for? + private volatile int maxInboundStreams = 1; + private volatile int maxOutboundStreams = 1; + + /** + * @param asscoitaion + */ + public OneToManyAssociationHandler() { + + } + + /** + * @return the maxInboundStreams + */ + public int getMaxInboundStreams() { + return maxInboundStreams; + } + + /** + * @return the maxOutboundStreams + */ + public int getMaxOutboundStreams() { + return maxOutboundStreams; + } + + @Override + public HandlerResult handleNotification(Notification arg0, OneToManyAssociationImpl arg1) { + if (arg0 instanceof AssociationChangeNotification) { + return handleNotification((AssociationChangeNotification) arg0, arg1); + } + if (arg0 instanceof ShutdownNotification) { + return handleNotification((ShutdownNotification) arg0, arg1); + } + if (arg0 instanceof SendFailedNotification) { + return handleNotification((SendFailedNotification) arg0, arg1); + } + if (arg0 instanceof PeerAddressChangeNotification) { + return handleNotification((PeerAddressChangeNotification) arg0, arg1); + } + logger.warn("Polymorphism failure: "+arg0+" arg1: "+arg1); + return super.handleNotification(arg0, arg1); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssociationImpl associtaion) { + + switch (not.event()) { + case COMM_UP: + if (not.association() != null) { + this.maxOutboundStreams = not.association().maxOutboundStreams(); + this.maxInboundStreams = not.association().maxInboundStreams(); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", + associtaion.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); + } + + associtaion.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); + + // TODO assign Thread's ? + try { + associtaion.markAssociationUp(); + associtaion.getAssociationListener().onCommunicationUp(associtaion, this.maxInboundStreams, this.maxOutboundStreams); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", associtaion.getName()), e); + } + return HandlerResult.CONTINUE; + + case CANT_START: + logger.error(String.format("Can't start for Association=%s", associtaion.getName())); + return HandlerResult.CONTINUE; + case COMM_LOST: + logger.warn(String.format("Communication lost for Association=%s", associtaion.getName())); + + // Close the Socket + /*TODO mark for delete + * associtaion.close(); + + associtaion.scheduleConnect();*/ + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationLost(associtaion); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", associtaion.getName()), e); + } + return HandlerResult.RETURN; + case RESTART: + logger.warn(String.format("Restart for Association=%s", associtaion.getName())); + try { + associtaion.getAssociationListener().onCommunicationRestart(associtaion); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", associtaion.getName()), + e); + } + return HandlerResult.CONTINUE; + case SHUTDOWN: + if (logger.isInfoEnabled()) { + logger.info(String.format("Shutdown for Association=%s", associtaion.getName())); + } + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), + e); + } + return HandlerResult.RETURN; + default: + logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), associtaion.getName())); + break; + } + + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssociationImpl associtaion) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); + } + + // TODO assign Thread's ? + + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), e); + } + + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssociationImpl associtaion) { +// logger.error(String.format("Association=%s SendFailedNotification", associtaion.getName())); + logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssociationImpl associtaion) { + //associtaion.peerSocketAddress = notification.address(); + if(logger.isEnabledFor(Priority.WARN)){ + logger.warn(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); + } + return HandlerResult.CONTINUE; + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java new file mode 100644 index 0000000..e782da5 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -0,0 +1,780 @@ +package org.mobicents.protocols.sctp.multiclient; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.spi.AbstractSelectableChannel; +import java.util.Arrays; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javolution.util.FastList; +import javolution.xml.XMLFormat; +import javolution.xml.stream.XMLStreamException; + +import org.apache.log4j.Logger; +import org.mobicents.protocols.api.Association; +import org.mobicents.protocols.api.AssociationListener; +import org.mobicents.protocols.api.AssociationType; +import org.mobicents.protocols.api.IpChannelType; +import org.mobicents.protocols.api.ManagementEventListener; +import org.mobicents.protocols.api.PayloadData; + +import com.sun.nio.sctp.MessageInfo; +import com.sun.nio.sctp.SctpMultiChannel; + +/* + * This Association implementation is limited to ONE-TO-MANY TYPE CLIENT SCTP association + */ + +public class OneToManyAssociationImpl implements Association { + + protected static final Logger logger = Logger.getLogger(OneToManyAssociationImpl.class); + + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String ASSOCIATION_TYPE = "assoctype"; + private static final String IPCHANNEL_TYPE = "ipChannelType"; + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + + private String hostAddress; + private int hostPort; + private String peerAddress; + private int peerPort; + private String name; + private String[] extraHostAddresses; + + private AssociationListener associationListener = null; + + //TODO see dev notes + private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); + + protected final OneToManyAssociationHandler associationHandler = new OneToManyAssociationHandler(); + + /** + * This is used only for SCTP This is the socket address for peer which will + * be null initially. If the Association has multihome support and if peer + * address changes, this variable is set to new value so new messages are + * now sent to changed peer address + */ + protected volatile SocketAddress peerSocketAddress = null; + + // Is the Association been started by management? + private AtomicBoolean started = new AtomicBoolean(false); + // Is the Association up (connection is established) + protected AtomicBoolean up = new AtomicBoolean(false); + + private int workerThreadTable[] = null; + + private MultiManagementImpl management; + + private volatile MessageInfo msgInfo; + + private volatile com.sun.nio.sctp.Association sctpAssociation; + private final IpChannelType ipChannelType = IpChannelType.SCTP; + + private AssociationInfo assocInfo; + private OneToManyAssocMultiplexer multiplexer; + + /** + * Count of number of IO Errors occured. If this exceeds the maxIOErrors set + * in Management, socket will be closed and request to reopen the cosket + * will be initiated + */ + //TODO see dev notes + private volatile int ioErrors = 0; + + static class PeerAddressInfo { + protected SocketAddress peerSocketAddress; + protected int sctpAssocId; + + public PeerAddressInfo(SocketAddress peerSocketAddress) { + super(); + this.peerSocketAddress = peerSocketAddress; + } + + public SocketAddress getPeerSocketAddress() { + return peerSocketAddress; + } + + public int getSctpAssocId() { + return sctpAssocId; + } + + protected void setPeerSocketAddress(SocketAddress peerSocketAddress) { + this.peerSocketAddress = peerSocketAddress; + } + + protected void setSctpAssocId(int sctpAssocId) { + this.sctpAssocId = sctpAssocId; + } + + @Override + public String toString() { + return "PeerAddressInfo [peerSocketAddress=" + peerSocketAddress + + ", sctpAssocId=" + sctpAssocId + "]"; + } + } + + static class HostAddressInfo { + private final String primaryHostAddress; + private final String secondaryHostAddress; + private final int hostPort; + + + public HostAddressInfo(String primaryHostAddress, + String secondaryHostAddress, int hostPort) { + super(); + if (primaryHostAddress == null || primaryHostAddress.isEmpty()) { + throw new IllegalArgumentException("Constructor HostAddressInfo: primaryHostAddress can not be null!"); + } + this.primaryHostAddress = primaryHostAddress; + this.secondaryHostAddress = secondaryHostAddress; + this.hostPort = hostPort; + } + public String getPrimaryHostAddress() { + return primaryHostAddress; + } + + public String getSecondaryHostAddress() { + return secondaryHostAddress; + } + + public int getHostPort() { + return hostPort; + } + + public boolean matches(HostAddressInfo hostAddressInfo) { + if (hostAddressInfo == null) { + return false; + } + if (this.hostPort != hostAddressInfo.getHostPort()) { + return false; + } + if (this.equals(hostAddressInfo)) { + return true; + } + if (this.getPrimaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getPrimaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { + if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "HostAddressInfo [primaryHostAddress=" + primaryHostAddress + + ", secondaryHostAddress=" + secondaryHostAddress + + ", hostPort=" + hostPort + "]"; + } + + } + static class AssociationInfo { + protected PeerAddressInfo peerInfo; + protected HostAddressInfo hostInfo; + public PeerAddressInfo getPeerInfo() { + return peerInfo; + } + public HostAddressInfo getHostInfo() { + return hostInfo; + } + @Override + public String toString() { + return "AssociationInfo [peerInfo=" + peerInfo + ", hostInfo=" + + hostInfo + "]"; + } + public AssociationInfo(PeerAddressInfo peerInfo, + HostAddressInfo hostInfo) { + super(); + this.peerInfo = peerInfo; + this.hostInfo = hostInfo; + } + protected void setPeerInfo(PeerAddressInfo peerInfo) { + this.peerInfo = peerInfo; + } + protected void setHostInfo(HostAddressInfo hostInfo) { + this.hostInfo = hostInfo; + } + + } + + protected OneToManyAssociationImpl() { + super(); + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + } + + /** + * Creating a CLIENT Association + * + * @param hostAddress + * @param hostPort + * @param peerAddress + * @param peerPort + * @param assocName + * @param ipChannelType + * @param extraHostAddresses + * @throws IOException + */ + public OneToManyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses) throws IOException { + this(); + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.name = assocName; + this.extraHostAddresses = extraHostAddresses; + this.peerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); + String secondaryHostAddress = null; + if (extraHostAddresses != null && extraHostAddresses.length >= 1) { + secondaryHostAddress = extraHostAddresses[0]; + } + this.assocInfo = new AssociationInfo(new PeerAddressInfo(peerSocketAddress), + new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); + } + + public AssociationInfo getAssocInfo() { + return assocInfo; + } + + public void setAssocInfo(AssociationInfo assocInfo) { + this.assocInfo = assocInfo; + } + + protected void assignSctpAssociationId(int id) { + this.assocInfo.getPeerInfo().setSctpAssocId(id); + } + + protected boolean isConnectedToPeerAddresses(String peerAddresses) { + if (logger.isDebugEnabled()) { + logger.debug("OneToManyAssociationImpl.isConnectedToPeerAddresses - ownPeerAddress: "+getAssocInfo().getPeerInfo().getPeerSocketAddress().toString() + + "parameter peerAddresses: "+peerAddresses); + } + return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); + } + + protected void start() throws Exception { + if (started.getAndSet(true)) { + logger.warn("Association: "+this+" has been already STARTED"); + return; + } + + if (this.associationListener == null) { + throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); + } + + doInitiateConnectionSctp(); + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStarted(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStarted", ee); + } + } + } + + /** + * Stops this Association. If the underlying SctpChannel is open, marks the + * channel for close + */ + protected void stop() throws Exception { + if (!started.getAndSet(false)) { + logger.warn("Association: "+this+" has been already STOPPED"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStopped(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStopped", ee); + } + } +/*TODO lifecycle management + if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + // Indicate we want the interest ops set changed + pendingChanges.add(new ChangeRequest(socketMultiChannel, this, ChangeRequest.CLOSE, -1)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + */ + + } + + public IpChannelType getIpChannelType() { + return IpChannelType.SCTP; + } + + /** + * @return the associationListener + */ + public AssociationListener getAssociationListener() { + return associationListener; + } + + /** + * @param associationListener + * the associationListener to set + */ + public void setAssociationListener(AssociationListener associationListener) { + this.associationListener = associationListener; + } + + /** + * @return the assocName + */ + public String getName() { + return name; + } + + /** + * @return the associationType + */ + public AssociationType getAssociationType() { + return AssociationType.CLIENT; + } + + /** + * @return the started + */ + @Override + public boolean isStarted() { + return started.get(); + } + + @Override + public boolean isConnected() { + return started.get() && up.get(); + } + + @Override + public boolean isUp() { + return up.get(); + } + + protected void markAssociationUp() { + if (up.getAndSet(true)) { + logger.debug("Association: "+this+" has been already marked UP"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationUp(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationUp", ee); + } + } + } + + protected void markAssociationDown() { + if (!up.getAndSet(false)) { + logger.debug("Association: "+this+" has been already marked DOWN"); + return; + } + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationDown(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationDown", ee); + } + } + } + + /** + * @return the hostAddress + */ + public String getHostAddress() { + return hostAddress; + } + + /** + * @return the hostPort + */ + public int getHostPort() { + return hostPort; + } + + /** + * @return the peerAddress + */ + public String getPeerAddress() { + return peerAddress; + } + + /** + * @return the peerPort + */ + public int getPeerPort() { + return peerPort; + } + + /** + * @return the serverName + */ + public String getServerName() { + return null; + } + + @Override + public String[] getExtraHostAddresses() { + return extraHostAddresses; + } + + /** + * @param management + * the management to set + */ + protected void setManagement(MultiManagementImpl management) { + this.management = management; + } + + + /** + * @param socketChannel + * the socketChannel to set + */ + protected void setSocketChannel(AbstractSelectableChannel socketChannel) { + // + } + + public void read(PayloadData payload) { + if (payload == null) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + // If single thread model the listener should be called in the + // selector thread itself + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, + payload), e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + + ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload + .getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } + + public void send(PayloadData payloadData) throws Exception { + if (!started.get()) { + throw new Exception("send failed: Association is not started"); + } + multiplexer.send(payloadData, this.msgInfo, this); + } + + protected boolean write(PayloadData payloadData) { + try { + + if (txBuffer.hasRemaining()) { + // All data wasn't sent in last doWrite. Try to send it now + this.doSend(); + } + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txBuffer.hasRemaining()) { + txBuffer.clear(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + logger.warn(e); + } + txBuffer.clear(); + txBuffer.flip(); + return false; + } + + if (this.sctpAssociation != null) { + msgInfo =MessageInfo.createOutgoing(sctpAssociation, peerSocketAddress, seqControl); + } else { + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + } + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + + logger.debug("write() - msgInfo: "+msgInfo); + txBuffer.flip(); + + this.doSend(); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return true; + } + return true; + } + return false; + } catch (IOException e) { + this.ioErrors++; + logger.error(String.format( + "IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), e); + return false; + } + } + + private int doSend() throws IOException { + return multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + } + + + private void checkSocketIsOpen() throws Exception { + if (!started.get()) { + throw new Exception(String.format( + "Association is not open (started) Association=%s", this.name)); + } + } + + + + //TODO proper lifecycle management of multiplexed associations + protected void close() {/* + /* + if (this.sctpAssociation != null && this.socketMultiChannel !=null) { + try { + this.socketMultiChannel.shutdown(this.sctpAssociation); + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + } + + try { + this.markAssociationDown(); + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + this.name), e); + } + + // Finally clear the txQueue + if (this.txQueue.size() > 0) { + logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", + this.name, this.txQueue.size())); + } + this.txQueue.clear(); + */ + } +/* + protected void scheduleConnect() { + if (this.getAssociationType() == AssociationType.CLIENT && !useOneToManyConnection) { + // If Associtaion is of Client type, reinitiate the connection + // procedure + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this, MultiChangeRequest.CONNECT, System.currentTimeMillis() + + this.management.getConnectDelay())); + } + } else if (this.getAssociationType() == AssociationType.CLIENT && useOneToManyConnection) { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, MultiChangeRequest.REGISTER, SelectionKey.OP_WRITE)); + } + } + }*/ + + protected void initiateConnection() throws IOException { + + /* // If Association is stopped, don't try to initiate connect + if (!this.started) { + return; + } + + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + } catch (Exception e) { + logger.error( + String.format( + "Exception while trying to close existing sctp socket and initiate new socket for Association=%s", + this.name), e); + } + } + + try { + if (this.ipChannelType == IpChannelType.SCTP) + this.doInitiateConnectionSctp(); + else + this.doInitiateConnectionTcp(); + } catch (Exception e) { + logger.error("Error while initiating a connection", e); + this.scheduleConnect(); + return; + } + + // reset the ioErrors + this.ioErrors = 0; + + // Queue a channel registration since the caller is not the + // selecting thread. As part of the registration we'll register + // an interest in connection events. These are raised when a channel + // is ready to complete connection establishment. + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), this, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE)); + } + + sendInit(); + this.connecting = true; + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); +*/ + } + + + private void doInitiateConnectionSctp() throws IOException { + this.multiplexer = management.getMultiChannelController().register(this); + //send init msg + byte[] spaceTrash = new byte[1]; + PayloadData payloadData = new PayloadData(spaceTrash.length, spaceTrash, true, false, 0, 0); + this.multiplexer.send(payloadData, null, this); + } + + protected void createworkerThreadTable(int maximumBooundStream) { + this.workerThreadTable = new int[maximumBooundStream]; + this.management.populateWorkerThread(this.workerThreadTable); + } + + @Override + public String toString() { + return "OneToManyAssociationImpl [hostAddress=" + hostAddress + + ", hostPort=" + hostPort + ", peerAddress=" + peerAddress + + ", peerPort=" + peerPort + ", name=" + name + + ", extraHostAddresses=" + Arrays.toString(extraHostAddresses) + + ", type=" + AssociationType.CLIENT + ", started=" + started + ", up=" + up + + ", management=" + management + ", msgInfo=" + msgInfo + + ", sctpAssociation=" + sctpAssociation + ", ipChannelType=" + + ipChannelType + ", assocInfo=" + assocInfo + ", multiplexer=" + + multiplexer + ", ioErrors=" + ioErrors + "]"; + } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + OneToManyAssociationImpl.class) { + + @SuppressWarnings("unchecked") + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociationImpl association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + //association.type = AssociationType.getAssociationType(xml.getAttribute(ASSOCIATION_TYPE, "")); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + + //association.serverName = xml.getAttribute(SERVER_NAME, ""); + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + + } + + @Override + public void write(OneToManyAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + xml.setAttribute(ASSOCIATION_TYPE, AssociationType.CLIENT.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME, null); + xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode()); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; + + @Override + public void acceptAnonymousAssociation( + AssociationListener associationListener) throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void rejectAnonymousAssociation() { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void stopAnonymousAssociation() throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } +} From 8d788c2d26e288a3b303dcf56c4f83f695230bba Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Thu, 26 Feb 2015 13:08:20 +0100 Subject: [PATCH 03/28] -D "switch" to disable the default configuration persistance - multiplexers (and underlying multichannels) can be stopped by Management --- .../protocols/sctp/ManagementImpl.java | 12 +++++ .../multiclient/MultiChannelController.java | 14 ++++++ .../sctp/multiclient/MultiManagementImpl.java | 24 ++++++++-- .../OneToManyAssocMultiplexer.java | 48 ++++++++++++++++++- .../multiclient/OneToManyAssociationImpl.java | 2 + 5 files changed, 94 insertions(+), 6 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java index cc405cd..9103bdc 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java @@ -59,6 +59,7 @@ public class ManagementImpl implements Management { private static final Logger logger = Logger.getLogger(ManagementImpl.class); + private static final String DISABLE_CONFIG_PERSISTANCE_KEY = "ss7.disableDefaultConfigPersistance"; private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; private static final String USER_DIR_KEY = "user.dir"; private static final String PERSIST_FILE_NAME = "sctp.xml"; @@ -384,8 +385,16 @@ public boolean isStarted(){ return this.started; } + private boolean isConfigPersistanceDisabled() { + String disableConfigPersistanceString = System.getProperty(DISABLE_CONFIG_PERSISTANCE_KEY, "false"); + return Boolean.valueOf(disableConfigPersistanceString); + } + @SuppressWarnings("unchecked") public void load() throws FileNotFoundException { + if (isConfigPersistanceDisabled()) { + return; + } XMLObjectReader reader = null; try { reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); @@ -427,6 +436,9 @@ public void load() throws FileNotFoundException { } public void store() { + if (isConfigPersistanceDisabled()) { + return; + } try { XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString())); writer.setBinding(binding); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java index a114d37..b98b6a9 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -3,6 +3,7 @@ import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import org.apache.log4j.Logger; @@ -83,4 +84,17 @@ protected OneToManyAssocMultiplexer register(OneToManyAssociationImpl assocImpl) return ret; } + protected void stopAllMultiplexers() { + for (List mList: multiplexers.values()) { + for (OneToManyAssocMultiplexer multiplexer: mList) { + try { + multiplexer.stop(); + } catch (IOException e) { + logger.warn(e);; + } + } + } + multiplexers.clear(); + } + } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index fec803b..cf9d506 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -74,6 +74,7 @@ public class MultiManagementImpl implements Management { private static final Logger logger = Logger.getLogger(MultiManagementImpl.class); + private static final String DISABLE_CONFIG_PERSISTANCE_KEY = "ss7.disableDefaultConfigPersistance"; private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; private static final String USER_DIR_KEY = "user.dir"; private static final String PERSIST_FILE_NAME = "sctp.xml"; @@ -323,14 +324,15 @@ public void stop() throws Exception { // We store the original state first this.store(); + multiChannelController.stopAllMultiplexers(); // Stop all associations - FastMap associationsTemp = this.associations; + /*FastMap associationsTemp = this.associations; for (FastMap.Entry n = associationsTemp.head(), end = associationsTemp.tail(); (n = n.getNext()) != end;) { Association associationTemp = n.getValue(); if (associationTemp.isStarted()) { ((OneToManyAssociationImpl) associationTemp).stop(); } - } + }*/ if (this.executorServices != null) { for (int i = 0; i < this.executorServices.length; i++) { @@ -378,9 +380,16 @@ public void stop() throws Exception { public boolean isStarted(){ return this.started; } - + private boolean isConfigPersistanceDisabled() { + String disableConfigPersistanceString = System.getProperty(DISABLE_CONFIG_PERSISTANCE_KEY, "false"); + return Boolean.valueOf(disableConfigPersistanceString); + } + @SuppressWarnings("unchecked") public void load() throws FileNotFoundException { + if (isConfigPersistanceDisabled()) { + return; + } XMLObjectReader reader = null; try { reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); @@ -409,6 +418,9 @@ public void load() throws FileNotFoundException { } public void store() { + if (isConfigPersistanceDisabled()) { + return; + } try { XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString())); writer.setBinding(binding); @@ -444,6 +456,7 @@ public void removeAllResourses() throws Exception { } synchronized (this) { + // Remove all associations ArrayList lst = new ArrayList(); for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { @@ -592,7 +605,8 @@ public void startAssociation(String assocName) throws Exception { } public void stopAssociation(String assocName) throws Exception { - if (!this.started) { + throw new UnsupportedOperationException("MultiManagement.stopAssociation is not a supported feature!"); + /*if (!this.started) { throw new Exception(String.format("Management=%s not started", this.name)); } @@ -607,7 +621,7 @@ public void stopAssociation(String assocName) throws Exception { } ((OneToManyAssociationImpl) association).stop(); - this.store(); + this.store();*/ } public void removeAssociation(String assocName) throws Exception { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index d84613c..4bd9e93 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -9,6 +9,7 @@ import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -29,6 +30,9 @@ public class OneToManyAssocMultiplexer { private MultiManagementImpl management; // The buffer into which we'll read data when it's available private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); + + // Is the multiplexer been started by management? + private AtomicBoolean started = new AtomicBoolean(false); // Queue holds payloads to be transmitted @@ -102,12 +106,18 @@ public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagemen } protected void registerAssociation(OneToManyAssociationImpl association) { + if (!started.get()) { + throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); + } synchronized (pendingAssocs) { pendingAssocs.add(association); } } protected void assignSctpAssocIdToAssociation(Integer id, OneToManyAssociationImpl association) { + if (!started.get()) { + throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); + } if (id == null || association == null) { return; } @@ -149,7 +159,7 @@ private void initMultiChannel() throws IOException { socketMultiChannel = SctpMultiChannel.open(); socketMultiChannel.configureBlocking(false); socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); - + started.set(true); if (logger.isDebugEnabled()) { logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); } @@ -181,6 +191,9 @@ private OneToManyAssociationImpl getAssociationByMessageInfo(MessageInfo msgInfo } protected void send(PayloadData payloadData, MessageInfo messageInfo, OneToManyAssociationImpl sender) throws IOException { + if (!started.get()) { + return; + } FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { @@ -197,6 +210,9 @@ protected void send(PayloadData payloadData, MessageInfo messageInfo, OneToManyA } protected void write(SelectionKey key) { + if (!started.get()) { + return; + } ConcurrentLinkedQueue txQueueTmp = txQueueSwapper.swap(new ConcurrentLinkedQueue()); HashSet skipList = new HashSet(); ConcurrentLinkedQueue retransmitQueue = new ConcurrentLinkedQueue(); @@ -275,6 +291,9 @@ private void doReadSctp() throws IOException { protected void read() { + if (!started.get()) { + return; + } try { doReadSctp(); } catch (IOException e) { @@ -283,6 +302,9 @@ protected void read() { } protected OneToManyAssociationImpl resolveAssociationImpl(com.sun.nio.sctp.Association sctpAssociation) { + if (!started.get()) { + return null; + } OneToManyAssociationImpl association = findConnectedAssociation(sctpAssociation.associationID()); if (association == null) { association = findPendingAssociation(sctpAssociation); @@ -294,6 +316,30 @@ protected OneToManyAssociationImpl resolveAssociationImpl(com.sun.nio.sctp.Assoc return association; } + protected void stop() throws IOException { + if (!started.compareAndSet(true, false)) { + return; + } + + for (OneToManyAssociationImpl assocImpl: connectedAssocs.values()) { + try { + assocImpl.stop(); + } catch (Exception ex) { + logger.warn(ex); + } + } + connectedAssocs.clear(); + for (OneToManyAssociationImpl assocImpl: pendingAssocs) { + try { + assocImpl.stop(); + } catch (Exception e) { + logger.warn(e);; + } + } + pendingAssocs.clear(); + this.socketMultiChannel.close(); + } + static class SctpMessage { private PayloadData payloadData; private MessageInfo messageInfo; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index e782da5..98718d6 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -311,6 +311,8 @@ protected void stop() throws Exception { logger.error("Exception while invoking onAssociationStopped", ee); } } + + /*TODO lifecycle management if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) { FastList pendingChanges = this.management.getPendingChanges(); From 34ed59765c6ed137a449f28ab5434c251170fa5e Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 3 Mar 2015 16:55:14 +0100 Subject: [PATCH 04/28] fixing multihome local address binding --- .../protocols/sctp/multiclient/OneToManyAssocMultiplexer.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 4bd9e93..533fc7f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -1,6 +1,7 @@ package org.mobicents.protocols.sctp.multiclient; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; @@ -159,6 +160,9 @@ private void initMultiChannel() throws IOException { socketMultiChannel = SctpMultiChannel.open(); socketMultiChannel.configureBlocking(false); socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); + if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { + socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); + } started.set(true); if (logger.isDebugEnabled()) { logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); From ca803f9f136f3311f2e51bb4cb220e5e574924f8 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 21 Apr 2015 18:36:04 +0200 Subject: [PATCH 05/28] using technical DAUD msg for one-to-many association initiation --- .../protocols/sctp/multiclient/OneToManyAssociationImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 98718d6..a0f1419 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -688,7 +688,7 @@ protected void initiateConnection() throws IOException { private void doInitiateConnectionSctp() throws IOException { this.multiplexer = management.getMultiChannelController().register(this); //send init msg - byte[] spaceTrash = new byte[1]; + byte[] spaceTrash = new byte[]{0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x12, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00}; PayloadData payloadData = new PayloadData(spaceTrash.length, spaceTrash, true, false, 0, 0); this.multiplexer.send(payloadData, null, this); } From e82d22143dc601a4d38950a09fbf8310f81e2f96 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Fri, 24 Apr 2015 15:39:39 +0200 Subject: [PATCH 06/28] one-to-one client association initial implementation (in progress) --- .../sctp/multiclient/MultiChangeRequest.java | 59 +- .../sctp/multiclient/MultiManagementImpl.java | 8 +- .../sctp/multiclient/MultiSelectorThread.java | 42 +- .../OneToManyAssocMultiplexer.java | 67 +- .../OneToManyAssociationHandler.java | 4 - .../multiclient/OneToManyAssociationImpl.java | 38 +- .../OneToOneAssociationHandler.java | 161 ++++ .../multiclient/OneToOneAssociationImpl.java | 839 ++++++++++++++++++ 8 files changed, 1147 insertions(+), 71 deletions(-) create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java index e349d06..726068c 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java @@ -35,25 +35,49 @@ public final class MultiChangeRequest { public static final int CLOSE = 4; public static final int ADD_OPS = 5; - private int type; - private int ops; - private AbstractSelectableChannel socketChannel; - private OneToManyAssocMultiplexer assocMultiplexer; + private final int type; + private final int ops; + private final AbstractSelectableChannel socketChannel; + private final OneToManyAssocMultiplexer assocMultiplexer; + private final OneToOneAssociationImpl oneToOneAssoc; + + private final boolean multiAssocRequest; private long executionTime; - protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, int type, int ops) { + protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, OneToOneAssociationImpl oneToOneAssoc, int type, int ops) { + if (assocMultiplexer != null && oneToOneAssoc != null) { + throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: both assocMultiplexer and oneToOneAssoc are specified!"); + } + if (assocMultiplexer == null && oneToOneAssoc == null) { + throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: nor assocMultiplexer nor oneToOneAssocc are specified!"); + } this.type = type; this.ops = ops; - this.socketChannel = socketChannel; - this.assocMultiplexer = assocMultiplexer; - if (socketChannel == null && assocMultiplexer != null) { - this.socketChannel = assocMultiplexer.getSocketMultiChannel(); + + if (assocMultiplexer != null) { + this.assocMultiplexer = assocMultiplexer; + this.oneToOneAssoc = null; + this.multiAssocRequest = true; + if (socketChannel == null) { + this.socketChannel = assocMultiplexer.getSocketMultiChannel(); + } else { + this.socketChannel = socketChannel; + } + } else { + this.oneToOneAssoc = oneToOneAssoc; + this.assocMultiplexer = null; + this.multiAssocRequest = false; + if (socketChannel == null) { + this.socketChannel = oneToOneAssoc.getSocketChannel(); + } else { + this.socketChannel = socketChannel; + } } } - protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, int type, long executionTime) { - this(null, assocMultiplexer, type, -1); + protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, OneToOneAssociationImpl oneToOneAssoc, int type, long executionTime) { + this(null, assocMultiplexer, oneToOneAssoc, type, -1); this.executionTime = executionTime; } @@ -79,12 +103,23 @@ protected AbstractSelectableChannel getSocketChannel() { } /** - * @return the association + * @return the one-to-many multiplexer instance */ protected OneToManyAssocMultiplexer getAssocMultiplexer() { return assocMultiplexer; } + /** + * @return the one-to-one association + */ + protected OneToOneAssociationImpl getOneToOneAssociation() { + return oneToOneAssoc; + } + + protected boolean isMultiAssocRequest() { + return multiAssocRequest; + } + /** * @return the executionTime */ diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index cf9d506..2427544 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -605,8 +605,7 @@ public void startAssociation(String assocName) throws Exception { } public void stopAssociation(String assocName) throws Exception { - throw new UnsupportedOperationException("MultiManagement.stopAssociation is not a supported feature!"); - /*if (!this.started) { + if (!this.started) { throw new Exception(String.format("Management=%s not started", this.name)); } @@ -621,7 +620,7 @@ public void stopAssociation(String assocName) throws Exception { } ((OneToManyAssociationImpl) association).stop(); - this.store();*/ + this.store(); } public void removeAssociation(String assocName) throws Exception { @@ -648,8 +647,7 @@ public void removeAssociation(String assocName) throws Exception { newAssociations.putAll(this.associations); newAssociations.remove(assocName); this.associations = newAssociations; - // this.associations.remove(assocName); - + this.store(); for (ManagementEventListener lstr : managementEventListeners) { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 9a0dd3d..83792aa 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -102,27 +102,29 @@ public void run() { case MultiChangeRequest.REGISTER: pendingChanges.remove(change); SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); - key1.attach(change.getAssocMultiplexer()); + if (change.isMultiAssocRequest()) { + key1.attach(change.getAssocMultiplexer()); + } else { + key1.attach(change.getOneToOneAssociation()); + } AssocChangeEvent ace = AssocChangeEvent.COMM_UP; AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); change.getAssocMultiplexer().associationHandler.handleNotification(acn, change.getAssocMultiplexer()); break; case MultiChangeRequest.CONNECT: pendingChanges.remove(change); - /*TODO - if (!change.getAssociation().isStarted()) { - // if Association is stopped - remove pending connection requests - pendingChanges.remove(change); - } else { - if (change.getExecutionTime() <= System.currentTimeMillis()) { - pendingChanges.remove(change); - change.getAssociation().initiateConnection(); + if (!change.isMultiAssocRequest()) { + if (change.getOneToOneAssociation().isStarted() + && change.getExecutionTime() <= System.currentTimeMillis()) { + change.getOneToOneAssociation().initiateConnection(); } - }*/ + } break; case MultiChangeRequest.CLOSE: pendingChanges.remove(change); - //TODO change.getAssociation().close(); + if (!change.isMultiAssocRequest()) { + change.getOneToOneAssociation().close(); + } } }// end of while } @@ -222,13 +224,23 @@ private void finishConnectionSctp(SelectionKey key) throws IOException { }*/ private void read(SelectionKey key) throws IOException { - OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); - multiplexer.read(); + if (key.attachment() instanceof OneToManyAssocMultiplexer) { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.read(); + } else if (key.attachment() instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); + association.read(); + } } private void write(SelectionKey key) throws IOException { - OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); - multiplexer.write(key); + if (key.attachment() instanceof OneToManyAssocMultiplexer) { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.write(key); + } else if (key.attachment() instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); + association.write(key); + } } class AssociationChangeNotification2 extends AssociationChangeNotification { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 533fc7f..190ce16 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -6,10 +6,10 @@ import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; -import java.util.ArrayList; import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; @@ -39,7 +39,7 @@ public class OneToManyAssocMultiplexer { // Queue holds payloads to be transmitted private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); - private ArrayList pendingAssocs = new ArrayList(); + private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); @@ -110,9 +110,28 @@ protected void registerAssociation(OneToManyAssociationImpl association) { if (!started.get()) { throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); } - synchronized (pendingAssocs) { - pendingAssocs.add(association); - } + + pendingAssocs.add(association); + } + + protected void start() throws IOException { + if (!started.compareAndSet(false, true)) { + return; + } + socketMultiChannel = SctpMultiChannel.open(); + socketMultiChannel.configureBlocking(false); + socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); + if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { + socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); + } + if (logger.isDebugEnabled()) { + logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); + } + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, null, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + } } protected void assignSctpAssocIdToAssociation(Integer id, OneToManyAssociationImpl association) { @@ -122,7 +141,10 @@ protected void assignSctpAssocIdToAssociation(Integer id, OneToManyAssociationIm if (id == null || association == null) { return; } + logger.debug("BUG_TRACE - assignSctpAssocIdToAssociation - 1: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); connectedAssocs.put(id, association); + pendingAssocs.remove(association); + logger.debug("BUG_TRACE - assignSctpAssocIdToAssociation - 2: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); association.assignSctpAssociationId(id); } @@ -169,7 +191,7 @@ private void initMultiChannel() throws IOException { } FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, MultiChangeRequest.REGISTER, + pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, null, MultiChangeRequest.REGISTER, SelectionKey.OP_WRITE|SelectionKey.OP_READ)); } } @@ -202,7 +224,7 @@ protected void send(PayloadData payloadData, MessageInfo messageInfo, OneToManyA synchronized (pendingChanges) { // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(this.getSocketMultiChannel(), this, MultiChangeRequest.ADD_OPS, + pendingChanges.add(new MultiChangeRequest(this.getSocketMultiChannel(), this, null, MultiChangeRequest.ADD_OPS, SelectionKey.OP_WRITE)); this.txQueueSwapper.add(new SctpMessage(payloadData, messageInfo, sender)); @@ -302,6 +324,8 @@ protected void read() { doReadSctp(); } catch (IOException e) { logger.error("Unable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, e); + } catch (Exception ex) { + logger.error("Unexpected exception: unnable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, ex); } } @@ -344,10 +368,33 @@ protected void stop() throws IOException { this.socketMultiChannel.close(); } + protected synchronized void stopAssociation(OneToManyAssociationImpl assocImpl) throws IOException { + logger.debug("BUG_TRACE 1"); + if (!started.get()) { + logger.debug("BUG_TRACE 2_1"); + return; + } + logger.debug("BUG_TRACE 2_2"); + if (connectedAssocs.remove(assocImpl.getAssocInfo().getPeerInfo().getSctpAssocId()) == null) { + logger.debug("BUG_TRACE 3_1"); + pendingAssocs.remove(assocImpl); + } else { + logger.debug("BUG_TRACE 3_2"); + } + + logger.debug("BUG_TRACE 4: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); + + if (pendingAssocs.isEmpty() && connectedAssocs.isEmpty()) { + started.set(false); + logger.debug("All associations of the multiplexer instance is stopped"); + this.socketMultiChannel.close(); + } + } + static class SctpMessage { - private PayloadData payloadData; - private MessageInfo messageInfo; - private OneToManyAssociationImpl senderAssoc; + private final PayloadData payloadData; + private final MessageInfo messageInfo; + private final OneToManyAssociationImpl senderAssoc; private SctpMessage(PayloadData payloadData, MessageInfo messageInfo, OneToManyAssociationImpl senderAssoc) { super(); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 9f46f80..0c419d7 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -18,13 +18,9 @@ public class OneToManyAssociationHandler extends AbstractNotificationHandler pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - // Indicate we want the interest ops set changed - pendingChanges.add(new ChangeRequest(socketMultiChannel, this, ChangeRequest.CLOSE, -1)); - } - - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); - } - */ - } public IpChannelType getIpChannelType() { @@ -549,7 +533,7 @@ protected boolean write(PayloadData payloadData) { } msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); + msgInfo.unordered(payloadData.isUnordered()); logger.debug("write() - msgInfo: "+msgInfo); txBuffer.flip(); @@ -571,6 +555,10 @@ protected boolean write(PayloadData payloadData) { "IOException while trying to write to underlying socket for Association=%s IOError count=%d", this.name, this.ioErrors), e); return false; + } catch (Exception ex) { + logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", + this.name, ex.getMessage()), ex); + return false; } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java new file mode 100644 index 0000000..22dff15 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -0,0 +1,161 @@ +package org.mobicents.protocols.sctp.multiclient; + +import org.apache.log4j.Logger; +import org.apache.log4j.Priority; + +import com.sun.nio.sctp.AbstractNotificationHandler; +import com.sun.nio.sctp.AssociationChangeNotification; +import com.sun.nio.sctp.HandlerResult; +import com.sun.nio.sctp.Notification; +import com.sun.nio.sctp.PeerAddressChangeNotification; +import com.sun.nio.sctp.SendFailedNotification; +import com.sun.nio.sctp.ShutdownNotification; + +public class OneToOneAssociationHandler extends AbstractNotificationHandler { + + private static final Logger logger = Logger.getLogger(OneToOneAssociationHandler.class); + + private volatile int maxInboundStreams = 1; + private volatile int maxOutboundStreams = 1; + + public OneToOneAssociationHandler() { + + } + + /** + * @return the maxInboundStreams + */ + public int getMaxInboundStreams() { + return maxInboundStreams; + } + + /** + * @return the maxOutboundStreams + */ + public int getMaxOutboundStreams() { + return maxOutboundStreams; + } + + @Override + public HandlerResult handleNotification(Notification arg0, OneToOneAssociationImpl arg1) { + if (arg0 instanceof AssociationChangeNotification) { + return handleNotification((AssociationChangeNotification) arg0, arg1); + } + if (arg0 instanceof ShutdownNotification) { + return handleNotification((ShutdownNotification) arg0, arg1); + } + if (arg0 instanceof SendFailedNotification) { + return handleNotification((SendFailedNotification) arg0, arg1); + } + if (arg0 instanceof PeerAddressChangeNotification) { + return handleNotification((PeerAddressChangeNotification) arg0, arg1); + } + logger.warn("Polymorphism failure: "+arg0+" arg1: "+arg1); + return super.handleNotification(arg0, arg1); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToOneAssociationImpl association) { + + switch (not.event()) { + case COMM_UP: + if (not.association() != null) { + this.maxOutboundStreams = not.association().maxOutboundStreams(); + this.maxInboundStreams = not.association().maxInboundStreams(); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", + association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); + } + + association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); + + // TODO assign Thread's ? + try { + association.markAssociationUp(); + association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", association.getName()), e); + } + return HandlerResult.CONTINUE; + + case CANT_START: + logger.error(String.format("Can't start for Association=%s", association.getName())); + return HandlerResult.CONTINUE; + case COMM_LOST: + logger.warn(String.format("Communication lost for Association=%s", association.getName())); + + // Close the Socket + association.close(); + + association.scheduleConnect(); + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationLost(association); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", association.getName()), e); + } + return HandlerResult.RETURN; + case RESTART: + logger.warn(String.format("Restart for Association=%s", association.getName())); + try { + association.getAssociationListener().onCommunicationRestart(association); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", association.getName()), + e); + } + return HandlerResult.CONTINUE; + case SHUTDOWN: + if (logger.isInfoEnabled()) { + logger.info(String.format("Shutdown for Association=%s", association.getName())); + } + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationShutdown(association); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), + e); + } + return HandlerResult.RETURN; + default: + logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); + break; + } + + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToOneAssociationImpl associtaion) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); + } + + // TODO assign Thread's ? + + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + } catch (Exception e) { + logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), e); + } + + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToOneAssociationImpl associtaion) { + logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToOneAssociationImpl associtaion) { + if(logger.isEnabledFor(Priority.INFO)){ + logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); + } + return HandlerResult.CONTINUE; + } +} + diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java new file mode 100644 index 0000000..b480de8 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -0,0 +1,839 @@ +package org.mobicents.protocols.sctp.multiclient; + + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.nio.channels.spi.AbstractSelectableChannel; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javolution.util.FastList; +import javolution.xml.XMLFormat; +import javolution.xml.stream.XMLStreamException; + +import org.apache.log4j.Logger; +import org.mobicents.protocols.api.Association; +import org.mobicents.protocols.api.AssociationListener; +import org.mobicents.protocols.api.AssociationType; +import org.mobicents.protocols.api.IpChannelType; +import org.mobicents.protocols.api.ManagementEventListener; +import org.mobicents.protocols.api.PayloadData; +import org.mobicents.protocols.sctp.AssociationImpl; +import org.mobicents.protocols.sctp.ChangeRequest; +import org.mobicents.protocols.sctp.Worker; + +import com.sun.nio.sctp.MessageInfo; +import com.sun.nio.sctp.SctpChannel; + +/** + * @author amit bhayani + * @ + * + */ +public class OneToOneAssociationImpl implements Association { + + protected static final Logger logger = Logger.getLogger(OneToOneAssociationImpl.class.getName()); + + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String ASSOCIATION_TYPE = "assoctype"; + private static final String IPCHANNEL_TYPE = "ipChannelType"; + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + + private String hostAddress; + private int hostPort; + private String peerAddress; + private int peerPort; + private String serverName; + private String name; + private IpChannelType ipChannelType; + private String[] extraHostAddresses; + + private AssociationType type; + + private AssociationListener associationListener = null; + + protected final OneToOneAssociationHandler associationHandler = new OneToOneAssociationHandler(); + + /** + * This is used only for SCTP This is the socket address for peer which will + * be null initially. If the Association has multihome support and if peer + * address changes, this variable is set to new value so new messages are + * now sent to changed peer address + */ + protected volatile SocketAddress peerSocketAddress = null; + + // Is the Association been started by management? + private AtomicBoolean started = new AtomicBoolean(false); + // Is the Association up (connection is established) + protected AtomicBoolean up = new AtomicBoolean(false); + + private int workerThreadTable[] = null; + + private ConcurrentLinkedQueue txQueue = new ConcurrentLinkedQueue(); + + private MultiManagementImpl management; + + private SctpChannel socketChannelSctp; + private SocketChannel socketChannelTcp; + + // The buffer into which we'll read data when it's available + private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); + private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); + + private volatile MessageInfo msgInfo; + + /** + * Count of number of IO Errors occured. If this exceeds the maxIOErrors set + * in Management, socket will be closed and request to reopen the cosket + * will be initiated + */ + private volatile int ioErrors = 0; + + public OneToOneAssociationImpl() { + super(); + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + + // clean receiver buffer + rxBuffer.clear(); + rxBuffer.rewind(); + rxBuffer.flip(); + } + + /** + * Creating a CLIENT Association + * + * @param hostAddress + * @param hostPort + * @param peerAddress + * @param peerPort + * @param assocName + * @param ipChannelType + * @param extraHostAddresses + * @throws IOException + */ + public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + IpChannelType ipChannelType, String[] extraHostAddresses) throws IOException { + this(); + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.name = assocName; + this.ipChannelType = ipChannelType; + this.extraHostAddresses = extraHostAddresses; + + this.type = AssociationType.CLIENT; + + } + + /** + * Creating a SERVER Association + * + * @param peerAddress + * @param peerPort + * @param serverName + * @param assocName + * @param ipChannelType + */ + public OneToOneAssociationImpl(String peerAddress, int peerPort, String serverName, String assocName, + IpChannelType ipChannelType) { + this(); + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.serverName = serverName; + this.name = assocName; + this.ipChannelType = ipChannelType; + + this.type = AssociationType.SERVER; + + } + + protected void start() throws Exception { + + if (this.associationListener == null) { + throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); + } + + if (started.getAndSet(true)) { + logger.warn("Association: "+this+" has been already STARTED"); + return; + } + + + if (this.type == AssociationType.CLIENT) { + this.scheduleConnect(); + } + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStarted(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStarted", ee); + } + } + } + + /** + * Stops this Association. If the underlying SctpChannel is open, marks the + * channel for close + */ + protected void stop() throws Exception { + if (!started.getAndSet(false)) { + logger.warn("Association: "+this+" has been already STOPPED"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStopped(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStopped", ee); + } + } + + if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(getSocketChannel(), null, this, MultiChangeRequest.CLOSE, -1)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + } + + public IpChannelType getIpChannelType() { + return IpChannelType.SCTP; + } + + /** + * @return the associationListener + */ + public AssociationListener getAssociationListener() { + return associationListener; + } + + /** + * @param associationListener + * the associationListener to set + */ + public void setAssociationListener(AssociationListener associationListener) { + this.associationListener = associationListener; + } + + /** + * @return the assocName + */ + public String getName() { + return name; + } + + /** + * @return the associationType + */ + public AssociationType getAssociationType() { + return AssociationType.CLIENT; + } + + + /** + * @return the started + */ + @Override + public boolean isStarted() { + return started.get(); + } + + @Override + public boolean isConnected() { + return started.get() && up.get(); + } + + @Override + public boolean isUp() { + return up.get(); + } + + protected void markAssociationUp() { + if (up.getAndSet(true)) { + logger.debug("Association: "+this+" has been already marked UP"); + return; + } + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationUp(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationUp", ee); + } + } + } + + protected void markAssociationDown() { + if (!up.getAndSet(false)) { + logger.debug("Association: "+this+" has been already marked DOWN"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationDown(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationDown", ee); + } + } + } + + /** + * @return the hostAddress + */ + public String getHostAddress() { + return hostAddress; + } + + /** + * @return the hostPort + */ + public int getHostPort() { + return hostPort; + } + + /** + * @return the peerAddress + */ + public String getPeerAddress() { + return peerAddress; + } + + /** + * @return the peerPort + */ + public int getPeerPort() { + return peerPort; + } + + /** + * @return the serverName + */ + public String getServerName() { + return null; + } + + @Override + public String[] getExtraHostAddresses() { + return extraHostAddresses; + } + + /** + * @param management + * the management to set + */ + protected void setManagement(MultiManagementImpl management) { + this.management = management; + } + + protected AbstractSelectableChannel getSocketChannel() { + if (this.ipChannelType == IpChannelType.SCTP) + return this.socketChannelSctp; + else + return this.socketChannelTcp; + } + + /** + * @param socketChannel + * the socketChannel to set + */ + protected void setSocketChannel(AbstractSelectableChannel socketChannel) { + if (this.ipChannelType == IpChannelType.SCTP) + this.socketChannelSctp = (SctpChannel) socketChannel; + else + this.socketChannelTcp = (SocketChannel) socketChannel; + } + + public void send(PayloadData payloadData) throws Exception { + this.checkSocketIsOpen(); + + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.CHANGEOPS, + SelectionKey.OP_WRITE)); + + // And queue the data we want written + // TODO Do we need to synchronize ConcurrentLinkedQueue ? + // synchronized (this.txQueue) { + this.txQueue.add(payloadData); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + + private void checkSocketIsOpen() throws Exception { + if (this.ipChannelType == IpChannelType.SCTP) { + if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() + || this.socketChannelSctp.association() == null) + throw new Exception(String.format( + "Underlying sctp channel doesn't open or doesn't have association for Association=%s", + this.name)); + } else { + if (!started.get() || this.socketChannelTcp == null || !this.socketChannelTcp.isOpen() + || !this.socketChannelTcp.isConnected()) + throw new Exception(String.format("Underlying tcp channel doesn't open for Association=%s", this.name)); + } + } + + protected void read() { + + try { + PayloadData payload; + if (this.ipChannelType == IpChannelType.SCTP) + payload = this.doReadSctp(); + else + payload = this.doReadTcp(); + if (payload == null) + return; + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + // If single thread model the listener should be called in the + // selector thread itself + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, + payload), e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + + ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload + .getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } catch (IOException e) { + this.ioErrors++; + logger.error(String.format( + "IOException while trying to read from underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), e); + + if (this.ioErrors > this.management.getMaxIOErrors()) { + // Close this socket + this.close(); + + // retry to connect after delay + this.scheduleConnect(); + } + } + } + + private PayloadData doReadSctp() throws IOException { + + rxBuffer.clear(); + MessageInfo messageInfo = this.socketChannelSctp.receive(rxBuffer, this, this.associationHandler); + + if (messageInfo == null) { + if (logger.isDebugEnabled()) { + logger.debug(String.format(" messageInfo is null for Association=%s", this.name)); + } + return null; + } + + int len = messageInfo.bytes(); + if (len == -1) { + logger.error(String.format("Rx -1 while trying to read from underlying socket for Association=%s ", + this.name)); + this.close(); + this.scheduleConnect(); + return null; + } + + rxBuffer.flip(); + byte[] data = new byte[len]; + rxBuffer.get(data); + rxBuffer.clear(); + + PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), + messageInfo.payloadProtocolID(), messageInfo.streamNumber()); + + return payload; + } + + private PayloadData doReadTcp() throws IOException { + + rxBuffer.clear(); + int len = this.socketChannelTcp.read(rxBuffer); + if (len == -1) { + logger.warn(String.format("Rx -1 while trying to read from underlying socket for Association=%s ", + this.name)); + this.close(); + this.scheduleConnect(); + return null; + } + + rxBuffer.flip(); + byte[] data = new byte[len]; + rxBuffer.get(data); + rxBuffer.clear(); + + PayloadData payload = new PayloadData(len, data, true, false, 0, 0); + + return payload; + } + + protected void write(SelectionKey key) { + + try { + + if (txBuffer.hasRemaining()) { + // All data wasn't sent in last doWrite. Try to send it now + // this.socketChannel.send(txBuffer, msgInfo); + this.doSend(); + } + + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txQueue.isEmpty() && !txBuffer.hasRemaining()) { + while (!txQueue.isEmpty()) { + // Lets read all the messages in txQueue and send + + txBuffer.clear(); + PayloadData payloadData = txQueue.poll(); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + if (this.ipChannelType == IpChannelType.SCTP) { + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + + } + txBuffer.clear(); + txBuffer.flip(); + continue; + } + + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + } + + txBuffer.flip(); + + this.doSend(); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return; + } + + }// end of while + } + + if (txQueue.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + } + + } catch (IOException e) { + this.ioErrors++; + logger.error(String.format( + "IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), e); + + if (this.ioErrors > this.management.getMaxIOErrors()) { + // Close this socket + this.close(); + + // retry to connect after delay + this.scheduleConnect(); + } + }// try-catch + } + + private int doSend() throws IOException { + if (this.ipChannelType == IpChannelType.SCTP) + return this.doSendSctp(); + else + return this.doSendTcp(); + } + + private int doSendSctp() throws IOException { + return this.socketChannelSctp.send(txBuffer, msgInfo); + } + + private int doSendTcp() throws IOException { + return this.socketChannelTcp.write(txBuffer); + } + + protected void close() { + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + } + + try { + this.markAssociationDown(); + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + this.name), e); + } + + // Finally clear the txQueue + if (this.txQueue.size() > 0) { + logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", + this.name, this.txQueue.size())); + } + this.txQueue.clear(); + } + + protected void scheduleConnect() { + if (this.getAssociationType() == AssociationType.CLIENT) { + // If Associtaion is of Client type, reinitiate the connection + // procedure + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() + + this.management.getConnectDelay())); + } + } + } + + protected void initiateConnection() throws IOException { + + // If Association is stopped, don't try to initiate connect + if (!this.started.get()) { + return; + } + + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + } catch (Exception e) { + logger.error( + String.format( + "Exception while trying to close existing sctp socket and initiate new socket for Association=%s", + this.name), e); + } + } + + try { + if (this.ipChannelType == IpChannelType.SCTP) + this.doInitiateConnectionSctp(); + else + this.doInitiateConnectionTcp(); + } catch (Exception e) { + logger.error("Error while initiating a connection", e); + this.scheduleConnect(); + return; + } + + // reset the ioErrors + this.ioErrors = 0; + + // Queue a channel registration since the caller is not the + // selecting thread. As part of the registration we'll register + // an interest in connection events. These are raised when a channel + // is ready to complete connection establishment. + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.REGISTER, + SelectionKey.OP_CONNECT)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + + } + + private void doInitiateConnectionSctp() throws IOException { + // Create a non-blocking socket channel + this.socketChannelSctp = SctpChannel.open(); + this.socketChannelSctp.configureBlocking(false); + + // bind to host address:port + this.socketChannelSctp.bind(new InetSocketAddress(this.hostAddress, this.hostPort)); + if (this.extraHostAddresses != null) { + for (String s : extraHostAddresses) { + this.socketChannelSctp.bindAddress(InetAddress.getByName(s)); + } + } + + // Kick off connection establishment + this.socketChannelSctp.connect(new InetSocketAddress(this.peerAddress, this.peerPort), 32, 32); + } + + private void doInitiateConnectionTcp() throws IOException { + + // Create a non-blocking socket channel + this.socketChannelTcp = SocketChannel.open(); + this.socketChannelTcp.configureBlocking(false); + + // bind to host address:port + this.socketChannelTcp.bind(new InetSocketAddress(this.hostAddress, this.hostPort)); + + // Kick off connection establishment + this.socketChannelTcp.connect(new InetSocketAddress(this.peerAddress, this.peerPort)); + } + + protected void createworkerThreadTable(int maximumBooundStream) { + this.workerThreadTable = new int[maximumBooundStream]; + this.management.populateWorkerThread(this.workerThreadTable); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("Association [name=").append(this.name).append(", associationType=").append(this.type) + .append(", ipChannelType=").append(this.ipChannelType).append(", hostAddress=") + .append(this.hostAddress).append(", hostPort=").append(this.hostPort).append(", peerAddress=") + .append(this.peerAddress).append(", peerPort=").append(this.peerPort).append(", serverName=") + .append(this.serverName); + + sb.append(", extraHostAddress=["); + + if (this.extraHostAddresses != null) { + for (int i = 0; i < this.extraHostAddresses.length; i++) { + String extraHostAddress = this.extraHostAddresses[i]; + sb.append(extraHostAddress); + sb.append(", "); + } + } + + sb.append("]]"); + + return sb.toString(); + } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + OneToOneAssociationImpl.class) { + + @SuppressWarnings("unchecked") + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationImpl association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + association.type = AssociationType.getAssociationType(xml.getAttribute(ASSOCIATION_TYPE, "")); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + + association.serverName = xml.getAttribute(SERVER_NAME, ""); + association.ipChannelType = IpChannelType.getInstance(xml.getAttribute(IPCHANNEL_TYPE, + IpChannelType.SCTP.getCode())); + if (association.ipChannelType == null) + association.ipChannelType = IpChannelType.SCTP; + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + + } + + @Override + public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME, association.serverName); + xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode()); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; + + @Override + public void acceptAnonymousAssociation( + AssociationListener associationListener) throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void rejectAnonymousAssociation() { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void stopAnonymousAssociation() throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } +} From 2c1af42682ad0913f2827fd2df3b3737cb3cda68 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Fri, 24 Apr 2015 18:21:27 +0200 Subject: [PATCH 07/28] implementing one-to-one branched associations (in progress) --- .../multiclient/AssociationImplProxy.java | 202 ++++++++++++++++++ .../multiclient/ManageableAssociation.java | 9 + .../sctp/multiclient/MultiManagementImpl.java | 37 ++-- .../OneToManyAssocMultiplexer.java | 19 ++ .../multiclient/OneToManyAssociationImpl.java | 13 +- .../multiclient/OneToOneAssociationImpl.java | 15 +- 6 files changed, 261 insertions(+), 34 deletions(-) create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java new file mode 100644 index 0000000..3a4270a --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java @@ -0,0 +1,202 @@ +package org.mobicents.protocols.sctp.multiclient; + +import org.mobicents.protocols.api.AssociationListener; +import org.mobicents.protocols.api.AssociationType; +import org.mobicents.protocols.api.IpChannelType; +import org.mobicents.protocols.api.PayloadData; + +public class AssociationImplProxy extends ManageableAssociation { + private ManageableAssociation delagate; + + public AssociationImplProxy(ManageableAssociation delagate) { + this.delagate = delagate; + } + + /** + * @return the delagate + */ + protected ManageableAssociation getDelagate() { + return delagate; + } + + /** + * @param delagate the delagate to set + */ + protected void setDelagate(ManageableAssociation delagate) { + this.delagate = delagate; + } + + + /** + * @param management + * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#setManagement(org.mobicents.protocols.sctp.multiclient.MultiManagementImpl) + */ + protected void setManagement(MultiManagementImpl management) { + delagate.setManagement(management); + } + + + + /** + * @throws Exception + * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#start() + */ + protected void start() throws Exception { + delagate.start(); + } + + /** + * @throws Exception + * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#stop() + */ + protected void stop() throws Exception { + delagate.stop(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getIpChannelType() + */ + public IpChannelType getIpChannelType() { + return delagate.getIpChannelType(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getAssociationType() + */ + public AssociationType getAssociationType() { + return delagate.getAssociationType(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getName() + */ + public String getName() { + return delagate.getName(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#isStarted() + */ + public boolean isStarted() { + return delagate.isStarted(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#isConnected() + */ + public boolean isConnected() { + return delagate.isConnected(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#isUp() + */ + public boolean isUp() { + return delagate.isUp(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getAssociationListener() + */ + public AssociationListener getAssociationListener() { + return delagate.getAssociationListener(); + } + + /** + * @param associationListener + * @see org.mobicents.protocols.api.Association#setAssociationListener(org.mobicents.protocols.api.AssociationListener) + */ + public void setAssociationListener(AssociationListener associationListener) { + delagate.setAssociationListener(associationListener); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getHostAddress() + */ + public String getHostAddress() { + return delagate.getHostAddress(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getHostPort() + */ + public int getHostPort() { + return delagate.getHostPort(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getPeerAddress() + */ + public String getPeerAddress() { + return delagate.getPeerAddress(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getPeerPort() + */ + public int getPeerPort() { + return delagate.getPeerPort(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getServerName() + */ + public String getServerName() { + return delagate.getServerName(); + } + + /** + * @return + * @see org.mobicents.protocols.api.Association#getExtraHostAddresses() + */ + public String[] getExtraHostAddresses() { + return delagate.getExtraHostAddresses(); + } + + /** + * @param payloadData + * @throws Exception + * @see org.mobicents.protocols.api.Association#send(org.mobicents.protocols.api.PayloadData) + */ + public void send(PayloadData payloadData) throws Exception { + delagate.send(payloadData); + } + + /** + * @param associationListener + * @throws Exception + * @see org.mobicents.protocols.api.Association#acceptAnonymousAssociation(org.mobicents.protocols.api.AssociationListener) + */ + public void acceptAnonymousAssociation( + AssociationListener associationListener) throws Exception { + delagate.acceptAnonymousAssociation(associationListener); + } + + /** + * + * @see org.mobicents.protocols.api.Association#rejectAnonymousAssociation() + */ + public void rejectAnonymousAssociation() { + delagate.rejectAnonymousAssociation(); + } + + /** + * @throws Exception + * @see org.mobicents.protocols.api.Association#stopAnonymousAssociation() + */ + public void stopAnonymousAssociation() throws Exception { + delagate.stopAnonymousAssociation(); + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java new file mode 100644 index 0000000..94f298f --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -0,0 +1,9 @@ +package org.mobicents.protocols.sctp.multiclient; + +import org.mobicents.protocols.api.Association; + +public abstract class ManageableAssociation implements Association { + protected abstract void setManagement(MultiManagementImpl management); + protected abstract void start() throws Exception; + protected abstract void stop() throws Exception; +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index 2427544..367b454 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -94,7 +94,7 @@ public class MultiManagementImpl implements Management { protected String persistDir = null; - protected AssociationMap associations = new AssociationMap(); + protected AssociationMap associations = new AssociationMap(); private FastList pendingChanges = new FastList(); @@ -346,7 +346,7 @@ public void stop() throws Exception { // waiting till stopping associations for (int i1 = 0; i1 < 20; i1++) { boolean assConnected = false; - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { Association associationTemp = n.getValue(); if (associationTemp.isConnected()) { assConnected = true; @@ -406,8 +406,8 @@ public void load() throws FileNotFoundException { this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - OneToManyAssociationImpl associationTemp = (OneToManyAssociationImpl) n.getValue(); + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + AssociationImplProxy associationTemp = (AssociationImplProxy) n.getValue(); associationTemp.setManagement(this); } @@ -459,7 +459,7 @@ public void removeAllResourses() throws Exception { // Remove all associations ArrayList lst = new ArrayList(); - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { lst.add(n.getKey()); } for (String n : lst) { @@ -482,11 +482,11 @@ public void removeAllResourses() throws Exception { } - public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName) throws Exception { + public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName) throws Exception { return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, null); } - public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, IpChannelType ipChannelType, + public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception { if (!this.started) { @@ -514,7 +514,7 @@ public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, } synchronized (this) { - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { Association associationTemp = n.getValue(); if (assocName.equals(associationTemp.getName())) { @@ -533,15 +533,14 @@ public OneToManyAssociationImpl addAssociation(String hostAddress, int hostPort, */ } - OneToManyAssociationImpl association = new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + ManageableAssociation association = new AssociationImplProxy(new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses)); association.setManagement(this); - AssociationMap newAssociations = new AssociationMap(); + AssociationMap newAssociations = new AssociationMap(); newAssociations.putAll(this.associations); newAssociations.put(assocName, association); this.associations = newAssociations; - // associations.put(assocName, association); - + this.store(); for (ManagementEventListener lstr : managementEventListeners) { @@ -590,17 +589,17 @@ public void startAssociation(String assocName) throws Exception { throw new Exception("Association name cannot be null"); } - Association associationTemp = this.associations.get(assocName); + ManageableAssociation association = this.associations.get(assocName); - if (associationTemp == null) { + if (association == null) { throw new Exception(String.format("No Association found for name=%s", assocName)); } - if (associationTemp.isStarted()) { + if (association.isStarted()) { throw new Exception(String.format("Association=%s is already started", assocName)); } - ((OneToManyAssociationImpl) associationTemp).start(); + association.start(); this.store(); } @@ -613,13 +612,13 @@ public void stopAssociation(String assocName) throws Exception { throw new Exception("Association name cannot be null"); } - Association association = this.associations.get(assocName); + ManageableAssociation association = this.associations.get(assocName); if (association == null) { throw new Exception(String.format("No Association found for name=%s", assocName)); } - ((OneToManyAssociationImpl) association).stop(); + association.stop(); this.store(); } @@ -643,7 +642,7 @@ public void removeAssociation(String assocName) throws Exception { throw new Exception(String.format("Association name=%s is started. Stop before removing", assocName)); } - AssociationMap newAssociations = new AssociationMap(); + AssociationMap newAssociations = new AssociationMap(); newAssociations.putAll(this.associations); newAssociations.remove(assocName); this.associations = newAssociations; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 190ce16..60d59d9 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -22,6 +22,7 @@ import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpMultiChannel; +import com.sun.nio.sctp.SctpChannel; public class OneToManyAssocMultiplexer { private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); @@ -337,6 +338,24 @@ protected OneToManyAssociationImpl resolveAssociationImpl(com.sun.nio.sctp.Assoc if (association == null) { association = findPendingAssociation(sctpAssociation); assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); + //BRANCH + logger.info("BRANCH"); + try { + SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); + OneToOneAssociationImpl oneToOneAssoc = new OneToOneAssociationImpl( association.getHostAddress()//hostAddress + , association.getHostPort() //hostPort + , association.getPeerAddress()//peerAddress + , association.getPeerPort()//peerPort + , association.getName()//assocName + , association.getIpChannelType()//ipChannelType + , association.getExtraHostAddresses()//extraHostAddresses + ); + oneToOneAssoc.setBranchChannel(sctpChannel); + ((AssociationImplProxy)management.getAssociation(association.getName())).setDelagate(oneToOneAssoc); + } catch (Exception ex) { + logger.error(ex); + } + } if (logger.isDebugEnabled()) { logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 588ca0f..46cb14c 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -5,20 +5,16 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.ByteBuffer; -import java.nio.channels.SelectionKey; import java.nio.channels.spi.AbstractSelectableChannel; import java.util.Arrays; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; -import javolution.util.FastList; import javolution.xml.XMLFormat; import javolution.xml.stream.XMLStreamException; import org.apache.log4j.Logger; -import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.AssociationListener; import org.mobicents.protocols.api.AssociationType; import org.mobicents.protocols.api.IpChannelType; @@ -26,13 +22,12 @@ import org.mobicents.protocols.api.PayloadData; import com.sun.nio.sctp.MessageInfo; -import com.sun.nio.sctp.SctpMultiChannel; /* * This Association implementation is limited to ONE-TO-MANY TYPE CLIENT SCTP association */ -public class OneToManyAssociationImpl implements Association { +public class OneToManyAssociationImpl extends ManageableAssociation { protected static final Logger logger = Logger.getLogger(OneToManyAssociationImpl.class); @@ -274,7 +269,7 @@ protected boolean isConnectedToPeerAddresses(String peerAddresses) { return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); } - protected void start() throws Exception { + public void start() throws Exception { if (this.associationListener == null) { throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); @@ -296,7 +291,7 @@ protected void start() throws Exception { } } - protected void stop() throws Exception { + public void stop() throws Exception { if (!started.getAndSet(false)) { logger.warn("Association: "+this+" has been already STOPPED"); return; @@ -437,7 +432,7 @@ public String[] getExtraHostAddresses() { * @param management * the management to set */ - protected void setManagement(MultiManagementImpl management) { + public void setManagement(MultiManagementImpl management) { this.management = management; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index b480de8..e82c05a 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -19,15 +19,12 @@ import javolution.xml.stream.XMLStreamException; import org.apache.log4j.Logger; -import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.AssociationListener; import org.mobicents.protocols.api.AssociationType; import org.mobicents.protocols.api.IpChannelType; import org.mobicents.protocols.api.ManagementEventListener; import org.mobicents.protocols.api.PayloadData; -import org.mobicents.protocols.sctp.AssociationImpl; import org.mobicents.protocols.sctp.ChangeRequest; -import org.mobicents.protocols.sctp.Worker; import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; @@ -37,7 +34,7 @@ * @ * */ -public class OneToOneAssociationImpl implements Association { +public class OneToOneAssociationImpl extends ManageableAssociation { protected static final Logger logger = Logger.getLogger(OneToOneAssociationImpl.class.getName()); @@ -404,7 +401,7 @@ private void checkSocketIsOpen() throws Exception { } protected void read() { - + logger.debug("read - BUG_TRACE 1"); try { PayloadData payload; if (this.ipChannelType == IpChannelType.SCTP) @@ -513,7 +510,7 @@ private PayloadData doReadTcp() throws IOException { } protected void write(SelectionKey key) { - + logger.debug("write - BUG_TRACE 1"); try { if (txBuffer.hasRemaining()) { @@ -698,6 +695,12 @@ protected void initiateConnection() throws IOException { this.management.getSocketSelector().wakeup(); } + + protected void setBranchChannel(SctpChannel sctpChannel) { + this.started.set(true); + this.up.set(true); + this.socketChannelSctp = sctpChannel; + } private void doInitiateConnectionSctp() throws IOException { // Create a non-blocking socket channel From 2d031073be9fde3c8b6e9d2bc8499c5e36ad3035 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 28 Apr 2015 14:38:45 +0200 Subject: [PATCH 08/28] implementing sctp association branching --- .../multiclient/AssociationImplProxy.java | 55 ++++++++++--------- .../multiclient/ManageableAssociation.java | 2 + .../multiclient/MultiAssociationHandler.java | 24 ++++++-- .../sctp/multiclient/MultiChangeRequest.java | 14 +++++ .../sctp/multiclient/MultiManagementImpl.java | 5 ++ .../sctp/multiclient/MultiSelectorThread.java | 12 +++- .../OneToManyAssocMultiplexer.java | 30 +++++++++- .../multiclient/OneToManyAssociationImpl.java | 1 + .../multiclient/OneToOneAssociationImpl.java | 49 ++++++++++------- 9 files changed, 138 insertions(+), 54 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java index 3a4270a..89c329f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java @@ -6,24 +6,29 @@ import org.mobicents.protocols.api.PayloadData; public class AssociationImplProxy extends ManageableAssociation { - private ManageableAssociation delagate; + private ManageableAssociation delegate; public AssociationImplProxy(ManageableAssociation delagate) { - this.delagate = delagate; + this.delegate = delagate; + } + + public void hotSwapDelegate(ManageableAssociation newDelegate) { + newDelegate.setAssociationListener(delegate.getAssociationListener()); + this.delegate = newDelegate; } /** * @return the delagate */ protected ManageableAssociation getDelagate() { - return delagate; + return delegate; } /** * @param delagate the delagate to set */ protected void setDelagate(ManageableAssociation delagate) { - this.delagate = delagate; + this.delegate = delagate; } @@ -32,7 +37,7 @@ protected void setDelagate(ManageableAssociation delagate) { * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#setManagement(org.mobicents.protocols.sctp.multiclient.MultiManagementImpl) */ protected void setManagement(MultiManagementImpl management) { - delagate.setManagement(management); + delegate.setManagement(management); } @@ -42,7 +47,7 @@ protected void setManagement(MultiManagementImpl management) { * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#start() */ protected void start() throws Exception { - delagate.start(); + delegate.start(); } /** @@ -50,7 +55,7 @@ protected void start() throws Exception { * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#stop() */ protected void stop() throws Exception { - delagate.stop(); + delegate.stop(); } /** @@ -58,7 +63,7 @@ protected void stop() throws Exception { * @see org.mobicents.protocols.api.Association#getIpChannelType() */ public IpChannelType getIpChannelType() { - return delagate.getIpChannelType(); + return delegate.getIpChannelType(); } /** @@ -66,7 +71,7 @@ public IpChannelType getIpChannelType() { * @see org.mobicents.protocols.api.Association#getAssociationType() */ public AssociationType getAssociationType() { - return delagate.getAssociationType(); + return delegate.getAssociationType(); } /** @@ -74,7 +79,7 @@ public AssociationType getAssociationType() { * @see org.mobicents.protocols.api.Association#getName() */ public String getName() { - return delagate.getName(); + return delegate.getName(); } /** @@ -82,7 +87,7 @@ public String getName() { * @see org.mobicents.protocols.api.Association#isStarted() */ public boolean isStarted() { - return delagate.isStarted(); + return delegate.isStarted(); } /** @@ -90,7 +95,7 @@ public boolean isStarted() { * @see org.mobicents.protocols.api.Association#isConnected() */ public boolean isConnected() { - return delagate.isConnected(); + return delegate.isConnected(); } /** @@ -98,7 +103,7 @@ public boolean isConnected() { * @see org.mobicents.protocols.api.Association#isUp() */ public boolean isUp() { - return delagate.isUp(); + return delegate.isUp(); } /** @@ -106,7 +111,7 @@ public boolean isUp() { * @see org.mobicents.protocols.api.Association#getAssociationListener() */ public AssociationListener getAssociationListener() { - return delagate.getAssociationListener(); + return delegate.getAssociationListener(); } /** @@ -114,7 +119,7 @@ public AssociationListener getAssociationListener() { * @see org.mobicents.protocols.api.Association#setAssociationListener(org.mobicents.protocols.api.AssociationListener) */ public void setAssociationListener(AssociationListener associationListener) { - delagate.setAssociationListener(associationListener); + delegate.setAssociationListener(associationListener); } /** @@ -122,7 +127,7 @@ public void setAssociationListener(AssociationListener associationListener) { * @see org.mobicents.protocols.api.Association#getHostAddress() */ public String getHostAddress() { - return delagate.getHostAddress(); + return delegate.getHostAddress(); } /** @@ -130,7 +135,7 @@ public String getHostAddress() { * @see org.mobicents.protocols.api.Association#getHostPort() */ public int getHostPort() { - return delagate.getHostPort(); + return delegate.getHostPort(); } /** @@ -138,7 +143,7 @@ public int getHostPort() { * @see org.mobicents.protocols.api.Association#getPeerAddress() */ public String getPeerAddress() { - return delagate.getPeerAddress(); + return delegate.getPeerAddress(); } /** @@ -146,7 +151,7 @@ public String getPeerAddress() { * @see org.mobicents.protocols.api.Association#getPeerPort() */ public int getPeerPort() { - return delagate.getPeerPort(); + return delegate.getPeerPort(); } /** @@ -154,7 +159,7 @@ public int getPeerPort() { * @see org.mobicents.protocols.api.Association#getServerName() */ public String getServerName() { - return delagate.getServerName(); + return delegate.getServerName(); } /** @@ -162,7 +167,7 @@ public String getServerName() { * @see org.mobicents.protocols.api.Association#getExtraHostAddresses() */ public String[] getExtraHostAddresses() { - return delagate.getExtraHostAddresses(); + return delegate.getExtraHostAddresses(); } /** @@ -171,7 +176,7 @@ public String[] getExtraHostAddresses() { * @see org.mobicents.protocols.api.Association#send(org.mobicents.protocols.api.PayloadData) */ public void send(PayloadData payloadData) throws Exception { - delagate.send(payloadData); + delegate.send(payloadData); } /** @@ -181,7 +186,7 @@ public void send(PayloadData payloadData) throws Exception { */ public void acceptAnonymousAssociation( AssociationListener associationListener) throws Exception { - delagate.acceptAnonymousAssociation(associationListener); + delegate.acceptAnonymousAssociation(associationListener); } /** @@ -189,7 +194,7 @@ public void acceptAnonymousAssociation( * @see org.mobicents.protocols.api.Association#rejectAnonymousAssociation() */ public void rejectAnonymousAssociation() { - delagate.rejectAnonymousAssociation(); + delegate.rejectAnonymousAssociation(); } /** @@ -197,6 +202,6 @@ public void rejectAnonymousAssociation() { * @see org.mobicents.protocols.api.Association#stopAnonymousAssociation() */ public void stopAnonymousAssociation() throws Exception { - delagate.stopAnonymousAssociation(); + delegate.stopAnonymousAssociation(); } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index 94f298f..e1855c7 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -2,6 +2,8 @@ import org.mobicents.protocols.api.Association; +import com.sun.nio.sctp.AbstractNotificationHandler; + public abstract class ManageableAssociation implements Association { protected abstract void setManagement(MultiManagementImpl management); protected abstract void start() throws Exception; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index 5ff938d..f852c28 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -47,6 +47,7 @@ class MultiAssociationHandler extends AbstractNotificationHandler pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(sctpChannel, null, oneToOneAssoc, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + } + ((AssociationImplProxy)management.getAssociation(association.getName())).hotSwapDelegate(oneToOneAssoc); + if (logger.isDebugEnabled()) { + logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); + } + return oneToOneAssoc; } catch (Exception ex) { logger.error(ex); } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 46cb14c..3bfc8c4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -21,6 +21,7 @@ import org.mobicents.protocols.api.ManagementEventListener; import org.mobicents.protocols.api.PayloadData; +import com.sun.nio.sctp.AbstractNotificationHandler; import com.sun.nio.sctp.MessageInfo; /* diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index e82c05a..59015d0 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -26,6 +26,7 @@ import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.sctp.ChangeRequest; +import com.sun.nio.sctp.AbstractNotificationHandler; import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; @@ -366,33 +367,42 @@ protected void setSocketChannel(AbstractSelectableChannel socketChannel) { } public void send(PayloadData payloadData) throws Exception { - this.checkSocketIsOpen(); - - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - - // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.CHANGEOPS, - SelectionKey.OP_WRITE)); - - // And queue the data we want written - // TODO Do we need to synchronize ConcurrentLinkedQueue ? - // synchronized (this.txQueue) { - this.txQueue.add(payloadData); + logger.debug("send - BUG_TRACE 1 - txQueue.size=" + txQueue.size()); + try { + this.checkSocketIsOpen(); + + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.CHANGEOPS, + SelectionKey.OP_WRITE)); + logger.debug("send - BUG_TRACE 2"); + // And queue the data we want written + // TODO Do we need to synchronize ConcurrentLinkedQueue ? + // synchronized (this.txQueue) { + this.txQueue.add(payloadData); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } catch (Exception ex) { + logger.error("send - BUG_TRACE ex", ex); } - - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); } private void checkSocketIsOpen() throws Exception { if (this.ipChannelType == IpChannelType.SCTP) { if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() - || this.socketChannelSctp.association() == null) + || this.socketChannelSctp.association() == null) { + logger.warn(String.format( + "Underlying sctp channel doesn't open or doesn't have association for Association=%s", + this.name)); throw new Exception(String.format( "Underlying sctp channel doesn't open or doesn't have association for Association=%s", this.name)); + } } else { if (!started.get() || this.socketChannelTcp == null || !this.socketChannelTcp.isOpen() || !this.socketChannelTcp.isConnected()) @@ -456,7 +466,7 @@ protected void read() { } private PayloadData doReadSctp() throws IOException { - + logger.debug("doReadSctp - BUG_TRACE 1"); rxBuffer.clear(); MessageInfo messageInfo = this.socketChannelSctp.receive(rxBuffer, this, this.associationHandler); @@ -637,6 +647,7 @@ protected void close() { } protected void scheduleConnect() { + logger.debug("scheduleConnect - BUG_TRACE 1"); if (this.getAssociationType() == AssociationType.CLIENT) { // If Associtaion is of Client type, reinitiate the connection // procedure From 5d33bb18df7dfd82ac1db268ad494138b9570372 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 5 May 2015 17:10:24 +0200 Subject: [PATCH 09/28] implementing branching feature new system property: sctp.enableBranching=true/false: it controls if new sctp associations are branched of from multiChannel after communication is up or not. --- .../multiclient/AssociationImplProxy.java | 207 ---------- .../multiclient/ManageableAssociation.java | 218 +++++++++- .../multiclient/MultiAssociationHandler.java | 7 +- .../sctp/multiclient/MultiChangeRequest.java | 28 +- .../multiclient/MultiChannelController.java | 4 +- .../sctp/multiclient/MultiManagementImpl.java | 20 +- .../sctp/multiclient/MultiSelectorThread.java | 25 +- .../OneToManyAssocMultiplexer.java | 149 +++---- .../OneToManyAssociationHandler.java | 11 +- .../multiclient/OneToManyAssociationImpl.java | 243 ++--------- .../OneToOneAssociationHandler.java | 1 - .../multiclient/OneToOneAssociationImpl.java | 376 ++++++++---------- 12 files changed, 513 insertions(+), 776 deletions(-) delete mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java deleted file mode 100644 index 89c329f..0000000 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/AssociationImplProxy.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.mobicents.protocols.sctp.multiclient; - -import org.mobicents.protocols.api.AssociationListener; -import org.mobicents.protocols.api.AssociationType; -import org.mobicents.protocols.api.IpChannelType; -import org.mobicents.protocols.api.PayloadData; - -public class AssociationImplProxy extends ManageableAssociation { - private ManageableAssociation delegate; - - public AssociationImplProxy(ManageableAssociation delagate) { - this.delegate = delagate; - } - - public void hotSwapDelegate(ManageableAssociation newDelegate) { - newDelegate.setAssociationListener(delegate.getAssociationListener()); - this.delegate = newDelegate; - } - - /** - * @return the delagate - */ - protected ManageableAssociation getDelagate() { - return delegate; - } - - /** - * @param delagate the delagate to set - */ - protected void setDelagate(ManageableAssociation delagate) { - this.delegate = delagate; - } - - - /** - * @param management - * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#setManagement(org.mobicents.protocols.sctp.multiclient.MultiManagementImpl) - */ - protected void setManagement(MultiManagementImpl management) { - delegate.setManagement(management); - } - - - - /** - * @throws Exception - * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#start() - */ - protected void start() throws Exception { - delegate.start(); - } - - /** - * @throws Exception - * @see org.mobicents.protocols.sctp.multiclient.ManageableAssociation#stop() - */ - protected void stop() throws Exception { - delegate.stop(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getIpChannelType() - */ - public IpChannelType getIpChannelType() { - return delegate.getIpChannelType(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getAssociationType() - */ - public AssociationType getAssociationType() { - return delegate.getAssociationType(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getName() - */ - public String getName() { - return delegate.getName(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#isStarted() - */ - public boolean isStarted() { - return delegate.isStarted(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#isConnected() - */ - public boolean isConnected() { - return delegate.isConnected(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#isUp() - */ - public boolean isUp() { - return delegate.isUp(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getAssociationListener() - */ - public AssociationListener getAssociationListener() { - return delegate.getAssociationListener(); - } - - /** - * @param associationListener - * @see org.mobicents.protocols.api.Association#setAssociationListener(org.mobicents.protocols.api.AssociationListener) - */ - public void setAssociationListener(AssociationListener associationListener) { - delegate.setAssociationListener(associationListener); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getHostAddress() - */ - public String getHostAddress() { - return delegate.getHostAddress(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getHostPort() - */ - public int getHostPort() { - return delegate.getHostPort(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getPeerAddress() - */ - public String getPeerAddress() { - return delegate.getPeerAddress(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getPeerPort() - */ - public int getPeerPort() { - return delegate.getPeerPort(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getServerName() - */ - public String getServerName() { - return delegate.getServerName(); - } - - /** - * @return - * @see org.mobicents.protocols.api.Association#getExtraHostAddresses() - */ - public String[] getExtraHostAddresses() { - return delegate.getExtraHostAddresses(); - } - - /** - * @param payloadData - * @throws Exception - * @see org.mobicents.protocols.api.Association#send(org.mobicents.protocols.api.PayloadData) - */ - public void send(PayloadData payloadData) throws Exception { - delegate.send(payloadData); - } - - /** - * @param associationListener - * @throws Exception - * @see org.mobicents.protocols.api.Association#acceptAnonymousAssociation(org.mobicents.protocols.api.AssociationListener) - */ - public void acceptAnonymousAssociation( - AssociationListener associationListener) throws Exception { - delegate.acceptAnonymousAssociation(associationListener); - } - - /** - * - * @see org.mobicents.protocols.api.Association#rejectAnonymousAssociation() - */ - public void rejectAnonymousAssociation() { - delegate.rejectAnonymousAssociation(); - } - - /** - * @throws Exception - * @see org.mobicents.protocols.api.Association#stopAnonymousAssociation() - */ - public void stopAnonymousAssociation() throws Exception { - delegate.stopAnonymousAssociation(); - } -} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index e1855c7..af666c7 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -1,11 +1,225 @@ package org.mobicents.protocols.sctp.multiclient; +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.channels.spi.AbstractSelectableChannel; + import org.mobicents.protocols.api.Association; +import org.mobicents.protocols.api.PayloadData; -import com.sun.nio.sctp.AbstractNotificationHandler; +import com.sun.nio.sctp.MessageInfo; public abstract class ManageableAssociation implements Association { - protected abstract void setManagement(MultiManagementImpl management); + + protected MultiManagementImpl management; + protected String hostAddress; + protected int hostPort; + protected String peerAddress; + protected int peerPort; + protected String name; + protected String[] extraHostAddresses; + protected AssociationInfo assocInfo; + + /** + * This is used only for SCTP This is the socket address for peer which will + * be null initially. If the Association has multihome support and if peer + * address changes, this variable is set to new value so new messages are + * now sent to changed peer address + */ + protected volatile SocketAddress peerSocketAddress = null; + protected abstract void start() throws Exception; protected abstract void stop() throws Exception; + protected abstract AbstractSelectableChannel getSocketChannel(); + protected abstract void close(); + protected abstract void reconnect(); + protected abstract boolean writePayload(PayloadData payloadData); + protected abstract void readPayload(PayloadData payloadData); + + + + protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses) throws IOException { + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.name = assocName; + this.extraHostAddresses = extraHostAddresses; + this.peerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); + String secondaryHostAddress = null; + if (extraHostAddresses != null && extraHostAddresses.length >= 1) { + secondaryHostAddress = extraHostAddresses[0]; + } + this.assocInfo = new AssociationInfo(new PeerAddressInfo(peerSocketAddress), + new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); + } + + protected void setManagement(MultiManagementImpl management) { + this.management = management; + } + + protected AssociationInfo getAssocInfo() { + return assocInfo; + } + + protected void setAssocInfo(AssociationInfo assocInfo) { + this.assocInfo = assocInfo; + } + + protected void assignSctpAssociationId(int id) { + this.assocInfo.getPeerInfo().setSctpAssocId(id); + } + + protected boolean isConnectedToPeerAddresses(String peerAddresses) { + return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); + } + + static class PeerAddressInfo { + protected SocketAddress peerSocketAddress; + protected int sctpAssocId; + + public PeerAddressInfo(SocketAddress peerSocketAddress) { + super(); + this.peerSocketAddress = peerSocketAddress; + } + + public SocketAddress getPeerSocketAddress() { + return peerSocketAddress; + } + + public int getSctpAssocId() { + return sctpAssocId; + } + + protected void setPeerSocketAddress(SocketAddress peerSocketAddress) { + this.peerSocketAddress = peerSocketAddress; + } + + protected void setSctpAssocId(int sctpAssocId) { + this.sctpAssocId = sctpAssocId; + } + + @Override + public String toString() { + return "PeerAddressInfo [peerSocketAddress=" + peerSocketAddress + + ", sctpAssocId=" + sctpAssocId + "]"; + } + } + + static class HostAddressInfo { + private final String primaryHostAddress; + private final String secondaryHostAddress; + private final int hostPort; + + + public HostAddressInfo(String primaryHostAddress, + String secondaryHostAddress, int hostPort) { + super(); + if (primaryHostAddress == null || primaryHostAddress.isEmpty()) { + throw new IllegalArgumentException("Constructor HostAddressInfo: primaryHostAddress can not be null!"); + } + this.primaryHostAddress = primaryHostAddress; + this.secondaryHostAddress = secondaryHostAddress; + this.hostPort = hostPort; + } + public String getPrimaryHostAddress() { + return primaryHostAddress; + } + + public String getSecondaryHostAddress() { + return secondaryHostAddress; + } + + public int getHostPort() { + return hostPort; + } + + public boolean matches(HostAddressInfo hostAddressInfo) { + if (hostAddressInfo == null) { + return false; + } + if (this.hostPort != hostAddressInfo.getHostPort()) { + return false; + } + if (this.equals(hostAddressInfo)) { + return true; + } + if (this.getPrimaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getPrimaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { + if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "HostAddressInfo [primaryHostAddress=" + primaryHostAddress + + ", secondaryHostAddress=" + secondaryHostAddress + + ", hostPort=" + hostPort + "]"; + } + + } + static class AssociationInfo { + protected PeerAddressInfo peerInfo; + protected HostAddressInfo hostInfo; + public PeerAddressInfo getPeerInfo() { + return peerInfo; + } + public HostAddressInfo getHostInfo() { + return hostInfo; + } + @Override + public String toString() { + return "AssociationInfo [peerInfo=" + peerInfo + ", hostInfo=" + + hostInfo + "]"; + } + public AssociationInfo(PeerAddressInfo peerInfo, + HostAddressInfo hostInfo) { + super(); + this.peerInfo = peerInfo; + this.hostInfo = hostInfo; + } + protected void setPeerInfo(PeerAddressInfo peerInfo) { + this.peerInfo = peerInfo; + } + protected void setHostInfo(HostAddressInfo hostInfo) { + this.hostInfo = hostInfo; + } + + } + + static class SctpMessage { + private final PayloadData payloadData; + private final MessageInfo messageInfo; + private final ManageableAssociation senderAssoc; + protected SctpMessage(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation senderAssoc) { + super(); + this.payloadData = payloadData; + this.messageInfo = messageInfo; + this.senderAssoc = senderAssoc; + } + protected PayloadData getPayloadData() { + return payloadData; + } + protected MessageInfo getMessageInfo() { + return messageInfo; + } + protected ManageableAssociation getSenderAssoc() { + return senderAssoc; + } + @Override + public String toString() { + return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" + + messageInfo + ", senderAssoc=" + senderAssoc + "]"; + } + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index f852c28..0cf9fca 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -21,17 +21,12 @@ */ package org.mobicents.protocols.sctp.multiclient; -import java.io.IOException; -import java.net.SocketAddress; - - - import org.apache.log4j.Logger; -import com.sun.nio.sctp.Notification; import com.sun.nio.sctp.AbstractNotificationHandler; import com.sun.nio.sctp.AssociationChangeNotification; import com.sun.nio.sctp.HandlerResult; +import com.sun.nio.sctp.Notification; import com.sun.nio.sctp.PeerAddressChangeNotification; import com.sun.nio.sctp.SendFailedNotification; import com.sun.nio.sctp.ShutdownNotification; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java index 053722b..f42019d 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java @@ -39,25 +39,25 @@ public final class MultiChangeRequest { private final int ops; private final AbstractSelectableChannel socketChannel; private final OneToManyAssocMultiplexer assocMultiplexer; - private final OneToOneAssociationImpl oneToOneAssoc; + private final ManageableAssociation association; private final boolean multiAssocRequest; private long executionTime; - protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, OneToOneAssociationImpl oneToOneAssoc, int type, int ops) { - if (assocMultiplexer != null && oneToOneAssoc != null) { - throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: both assocMultiplexer and oneToOneAssoc are specified!"); + protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, ManageableAssociation association, int type, int ops) { + if (assocMultiplexer != null && association != null) { + throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: both assocMultiplexer and association are specified!"); } - if (assocMultiplexer == null && oneToOneAssoc == null) { - throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: nor assocMultiplexer nor oneToOneAssocc are specified!"); + if (assocMultiplexer == null && association == null) { + throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: nor assocMultiplexer nor association are specified!"); } this.type = type; this.ops = ops; if (assocMultiplexer != null) { this.assocMultiplexer = assocMultiplexer; - this.oneToOneAssoc = null; + this.association = null; this.multiAssocRequest = true; if (socketChannel == null) { this.socketChannel = assocMultiplexer.getSocketMultiChannel(); @@ -65,19 +65,19 @@ protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyA this.socketChannel = socketChannel; } } else { - this.oneToOneAssoc = oneToOneAssoc; + this.association = association; this.assocMultiplexer = null; this.multiAssocRequest = false; if (socketChannel == null) { - this.socketChannel = oneToOneAssoc.getSocketChannel(); + this.socketChannel = association.getSocketChannel(); } else { this.socketChannel = socketChannel; } } } - protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, OneToOneAssociationImpl oneToOneAssoc, int type, long executionTime) { - this(null, assocMultiplexer, oneToOneAssoc, type, -1); + protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, ManageableAssociation association, int type, long executionTime) { + this(null, assocMultiplexer, association, type, -1); this.executionTime = executionTime; } @@ -112,8 +112,8 @@ protected OneToManyAssocMultiplexer getAssocMultiplexer() { /** * @return the one-to-one association */ - protected OneToOneAssociationImpl getOneToOneAssociation() { - return oneToOneAssoc; + protected ManageableAssociation getAssociation() { + return association; } protected boolean isMultiAssocRequest() { @@ -134,7 +134,7 @@ protected long getExecutionTime() { public String toString() { return "MultiChangeRequest [type=" + type + ", ops=" + ops + ", socketChannel=" + socketChannel + ", assocMultiplexer=" - + assocMultiplexer + ", oneToOneAssoc=" + oneToOneAssoc + + assocMultiplexer + ", oneToOneAssoc=" + association + ", multiAssocRequest=" + multiAssocRequest + ", executionTime=" + executionTime + "]"; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java index b98b6a9..0ed29a2 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -55,7 +55,7 @@ private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrI mList = new ArrayList(); multiplexers.put(hostAddrInfo.getHostPort(), mList); } - mList.add(multiplexer); + mList.add(multiplexer); } /** @@ -65,7 +65,7 @@ private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrI * @return - the OneToManyAssocMultiplexer that is associated to the OneToManyAssociationImpl assocImpl * @throws IOException */ - protected OneToManyAssocMultiplexer register(OneToManyAssociationImpl assocImpl) throws IOException { + protected OneToManyAssocMultiplexer register(ManageableAssociation assocImpl) throws IOException { if (assocImpl == null || assocImpl.getAssocInfo() == null || assocImpl.getAssocInfo().getHostInfo() == null) { return null; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index a1442f5..a2aef05 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -75,6 +75,7 @@ public class MultiManagementImpl implements Management { private static final Logger logger = Logger.getLogger(MultiManagementImpl.class); private static final String DISABLE_CONFIG_PERSISTANCE_KEY = "ss7.disableDefaultConfigPersistance"; + private static final String ENABLE_SCTP_ASSOC_BRANCHING = "sctp.enableBranching"; private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; private static final String USER_DIR_KEY = "user.dir"; private static final String PERSIST_FILE_NAME = "sctp.xml"; @@ -124,6 +125,8 @@ public class MultiManagementImpl implements Management { private volatile boolean started = false; private final MultiChannelController multiChannelController = new MultiChannelController(this); + + private boolean enableBranching; public MultiManagementImpl(String name) throws IOException { this.name = name; @@ -131,6 +134,8 @@ public MultiManagementImpl(String name) throws IOException { binding.setAlias(OneToManyAssociationImpl.class, "association"); binding.setAlias(String.class, "string"); this.socketSelector = SelectorProvider.provider().openSelector(); + String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "false"); + this.enableBranching = Boolean.valueOf(enableBranchingString); } /** @@ -254,7 +259,7 @@ protected MultiChannelController getMultiChannelController() { public boolean isInBranchingMode() { - return true; + return enableBranching; } public void start() throws Exception { @@ -412,10 +417,8 @@ public void load() throws FileNotFoundException { this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - AssociationImplProxy associationTemp = (AssociationImplProxy) n.getValue(); - associationTemp.setManagement(this); + n.getValue().setManagement(this); } - } catch (XMLStreamException ex) { // this.logger.info( // "Error while re-creating Linksets from persisted file", ex); @@ -537,8 +540,13 @@ public ManageableAssociation addAssociation(String hostAddress, int hostPort, St } */ } - - ManageableAssociation association = new AssociationImplProxy(new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses)); + ManageableAssociation association = null; + if (isInBranchingMode()) { + association = new OneToOneAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + } else { + association = new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + } + association.setManagement(this); AssociationMap newAssociations = new AssociationMap(); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index fb78fcd..0756ce8 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -33,7 +33,6 @@ import com.sun.nio.sctp.AssociationChangeNotification; import com.sun.nio.sctp.AssociationChangeNotification.AssocChangeEvent; -import com.sun.nio.sctp.SctpChannel; /** * @author amit bhayani @@ -89,7 +88,7 @@ public void run() { Iterator changes = pendingChanges.iterator(); while (changes.hasNext()) { MultiChangeRequest change = changes.next(); - SelectionKey key = change.getSocketChannel().keyFor(this.selector); + SelectionKey key = change.getSocketChannel() == null ? null : change.getSocketChannel().keyFor(this.selector); logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector ); switch (change.getType()) { case MultiChangeRequest.CHANGEOPS: @@ -103,36 +102,34 @@ public void run() { case MultiChangeRequest.REGISTER: pendingChanges.remove(change); SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); - logger.debug("run - BUG_TRACE 1: key=" + key1 + " is registered for channel=" + change.getSocketChannel()); - AssocChangeEvent ace = AssocChangeEvent.COMM_UP; AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); if (change.isMultiAssocRequest()) { key1.attach(change.getAssocMultiplexer()); change.getAssocMultiplexer().associationHandler.handleNotification(acn, change.getAssocMultiplexer()); } else { - key1.attach(change.getOneToOneAssociation()); + key1.attach(change.getAssociation()); //change.getOneToOneAssociation().associationHandler.handleNotification(acn, change.getOneToOneAssociation()); } - - break; case MultiChangeRequest.CONNECT: - pendingChanges.remove(change); - if (!change.isMultiAssocRequest()) { - if (change.getOneToOneAssociation().isStarted() - && change.getExecutionTime() <= System.currentTimeMillis()) { - change.getOneToOneAssociation().initiateConnection(); + //in CONNECT request assocociation is filled in both OneToOne and OneToMany cases + if (!change.getAssociation().isStarted()) { + pendingChanges.remove(change); + } else { + if (change.getExecutionTime() <= System.currentTimeMillis()) { + pendingChanges.remove(change); + change.getAssociation().reconnect(); } } break; case MultiChangeRequest.CLOSE: pendingChanges.remove(change); if (!change.isMultiAssocRequest()) { - change.getOneToOneAssociation().close(); + change.getAssociation().close(); } } - }// end of while + } } // Wait for an event one of the registered channels diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 24227a6..cea1b29 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -18,18 +18,21 @@ import org.apache.log4j.Logger; import org.mobicents.protocols.api.PayloadData; -import org.mobicents.protocols.sctp.multiclient.OneToManyAssociationImpl.HostAddressInfo; +import org.mobicents.protocols.sctp.multiclient.ManageableAssociation.HostAddressInfo; +import org.mobicents.protocols.sctp.multiclient.ManageableAssociation.SctpMessage; import com.sun.nio.sctp.MessageInfo; -import com.sun.nio.sctp.SctpMultiChannel; import com.sun.nio.sctp.SctpChannel; +import com.sun.nio.sctp.SctpMultiChannel; public class OneToManyAssocMultiplexer { private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); + private MultiManagementImpl management; + private HostAddressInfo hostAddressInfo; private SctpMultiChannel socketMultiChannel; - private MultiManagementImpl management; + // The buffer into which we'll read data when it's available private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); @@ -40,8 +43,8 @@ public class OneToManyAssocMultiplexer { // Queue holds payloads to be transmitted private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); - private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); - private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); + private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); + private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); @@ -107,7 +110,7 @@ public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagemen initMultiChannel(); } - protected void registerAssociation(OneToManyAssociationImpl association) { + protected void registerAssociation(ManageableAssociation association) { if (!started.get()) { throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); } @@ -135,21 +138,19 @@ protected void start() throws IOException { } } - protected void assignSctpAssocIdToAssociation(Integer id, OneToManyAssociationImpl association) { + protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation association) { if (!started.get()) { throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); } if (id == null || association == null) { return; } - logger.debug("BUG_TRACE - assignSctpAssocIdToAssociation - 1: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); connectedAssocs.put(id, association); pendingAssocs.remove(association); - logger.debug("BUG_TRACE - assignSctpAssocIdToAssociation - 2: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); association.assignSctpAssociationId(id); } - protected OneToManyAssociationImpl findConnectedAssociation(Integer sctpAssocId) { + protected ManageableAssociation findConnectedAssociation(Integer sctpAssocId) { return connectedAssocs.get(sctpAssocId); } @@ -163,14 +164,14 @@ private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation return peerAddresses; } - protected OneToManyAssociationImpl findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { + protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { String peerAddresses = extractPeerAddresses(sctpAssociation); if (logger.isDebugEnabled()) { peerAddresses = peerAddresses.isEmpty() ? peerAddresses : peerAddresses.substring(2); logger.debug("Association("+sctpAssociation.associationID()+") connected to "+peerAddresses); } - OneToManyAssociationImpl ret=null; - for (OneToManyAssociationImpl assocImpl : pendingAssocs) { + ManageableAssociation ret=null; + for (ManageableAssociation assocImpl : pendingAssocs) { if (assocImpl.isConnectedToPeerAddresses(peerAddresses)) { ret = assocImpl; break; @@ -204,8 +205,8 @@ public SctpMultiChannel getSocketMultiChannel() { return socketMultiChannel; } - private OneToManyAssociationImpl getAssociationByMessageInfo(MessageInfo msgInfo) { - OneToManyAssociationImpl ret = null; + private ManageableAssociation getAssociationByMessageInfo(MessageInfo msgInfo) { + ManageableAssociation ret = null; //find connected assoc if (msgInfo.association() != null) { ret = findConnectedAssociation(msgInfo.association().associationID()); @@ -217,7 +218,7 @@ private OneToManyAssociationImpl getAssociationByMessageInfo(MessageInfo msgInfo return ret; } - protected void send(PayloadData payloadData, MessageInfo messageInfo, OneToManyAssociationImpl sender) throws IOException { + protected void send(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation sender) throws IOException { if (!started.get()) { return; } @@ -260,7 +261,7 @@ protected void write(SelectionKey key) { if (skipList.contains(msg.getSenderAssoc().getName())) { retransmitQueue.add(msg); } else { - if (!msg.getSenderAssoc().write(msg.getPayloadData())) { + if (!msg.getSenderAssoc().writePayload(msg.getPayloadData())) { skipList.add(msg.getSenderAssoc().getName()); retransmitQueue.add(msg); } @@ -309,9 +310,9 @@ private void doReadSctp() throws IOException { PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), messageInfo.payloadProtocolID(), messageInfo.streamNumber()); - OneToManyAssociationImpl assoc = getAssociationByMessageInfo(messageInfo); + ManageableAssociation assoc = getAssociationByMessageInfo(messageInfo); if (assoc != null) { - assoc.read(payload); + assoc.readPayload(payload); } } @@ -334,54 +335,37 @@ protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Associat if (!started.get()) { return null; } - OneToManyAssociationImpl association = findConnectedAssociation(sctpAssociation.associationID()); + ManageableAssociation association = findConnectedAssociation(sctpAssociation.associationID()); if (association == null) { association = findPendingAssociation(sctpAssociation); assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); - //BRANCH - logger.info("BRANCH"); - try { - SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); - logger.debug(String.format("resolceAssociationImpl - BUG_TRACE 1: sctpMultiChannel: isBlocking=%s, isOpen=%s, isRegistered=%s, supportedOptions=%s", - getSocketMultiChannel().isBlocking(), - getSocketMultiChannel().isOpen(), - getSocketMultiChannel().isRegistered(), - getSocketMultiChannel().supportedOptions())); - if (sctpChannel.isBlocking()) { - sctpChannel.configureBlocking(false); - } - logger.debug(String.format("resolveAssociationImpl - BUG_TRACE 2: new sctpChannel: isBlocking=%s, isConnectionPending=%s, isOpen=%s, isRegistered=%s, supportedOptions=%s", - sctpChannel.isBlocking(), - sctpChannel.isConnectionPending(), - sctpChannel.isOpen(), - sctpChannel.isRegistered(), - sctpChannel.supportedOptions() - )); + + if (management.isInBranchingMode()) { + //BRANCH + logger.info("BRANCH"); + try { + SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); + if (sctpChannel.isBlocking()) { + sctpChannel.configureBlocking(false); + } + + OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) association; + oneToOneAssoc.setBranchChannel(sctpChannel); + oneToOneAssoc.setManagement(management); - OneToOneAssociationImpl oneToOneAssoc = new OneToOneAssociationImpl( association.getHostAddress()//hostAddress - , association.getHostPort() //hostPort - , association.getPeerAddress()//peerAddress - , association.getPeerPort()//peerPort - , association.getName()//assocName - , association.getIpChannelType()//ipChannelType - , association.getExtraHostAddresses()//extraHostAddresses - ); - oneToOneAssoc.setBranchChannel(sctpChannel); - oneToOneAssoc.setManagement(management); - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(sctpChannel, null, oneToOneAssoc, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(sctpChannel, null, oneToOneAssoc, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + } + if (logger.isDebugEnabled()) { + logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); + } + return oneToOneAssoc; + } catch (Exception ex) { + logger.error(ex); } - ((AssociationImplProxy)management.getAssociation(association.getName())).hotSwapDelegate(oneToOneAssoc); - if (logger.isDebugEnabled()) { - logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); - } - return oneToOneAssoc; - } catch (Exception ex) { - logger.error(ex); } - } if (logger.isDebugEnabled()) { logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); @@ -394,7 +378,7 @@ protected void stop() throws IOException { return; } - for (OneToManyAssociationImpl assocImpl: connectedAssocs.values()) { + for (ManageableAssociation assocImpl: connectedAssocs.values()) { try { assocImpl.stop(); } catch (Exception ex) { @@ -402,7 +386,7 @@ protected void stop() throws IOException { } } connectedAssocs.clear(); - for (OneToManyAssociationImpl assocImpl: pendingAssocs) { + for (ManageableAssociation assocImpl: pendingAssocs) { try { assocImpl.stop(); } catch (Exception e) { @@ -413,54 +397,17 @@ protected void stop() throws IOException { this.socketMultiChannel.close(); } - protected synchronized void stopAssociation(OneToManyAssociationImpl assocImpl) throws IOException { - logger.debug("BUG_TRACE 1"); + protected synchronized void stopAssociation(ManageableAssociation assocImpl) throws IOException { if (!started.get()) { - logger.debug("BUG_TRACE 2_1"); return; } - logger.debug("BUG_TRACE 2_2"); if (connectedAssocs.remove(assocImpl.getAssocInfo().getPeerInfo().getSctpAssocId()) == null) { - logger.debug("BUG_TRACE 3_1"); pendingAssocs.remove(assocImpl); - } else { - logger.debug("BUG_TRACE 3_2"); } - - logger.debug("BUG_TRACE 4: pendingAssocs.size=" + pendingAssocs.size() + " connectedAssocs.size=" + connectedAssocs.size()); - if (pendingAssocs.isEmpty() && connectedAssocs.isEmpty()) { started.set(false); logger.debug("All associations of the multiplexer instance is stopped"); this.socketMultiChannel.close(); } } - - static class SctpMessage { - private final PayloadData payloadData; - private final MessageInfo messageInfo; - private final OneToManyAssociationImpl senderAssoc; - private SctpMessage(PayloadData payloadData, MessageInfo messageInfo, - OneToManyAssociationImpl senderAssoc) { - super(); - this.payloadData = payloadData; - this.messageInfo = messageInfo; - this.senderAssoc = senderAssoc; - } - private PayloadData getPayloadData() { - return payloadData; - } - private MessageInfo getMessageInfo() { - return messageInfo; - } - private OneToManyAssociationImpl getSenderAssoc() { - return senderAssoc; - } - @Override - public String toString() { - return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" - + messageInfo + ", senderAssoc=" + senderAssoc + "]"; - } - } - } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 0c419d7..c575e1d 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -1,8 +1,5 @@ package org.mobicents.protocols.sctp.multiclient; -import java.io.IOException; -import java.net.SocketAddress; - import org.apache.log4j.Logger; import org.apache.log4j.Priority; @@ -53,7 +50,6 @@ public HandlerResult handleNotification(Notification arg0, OneToManyAssociation if (arg0 instanceof PeerAddressChangeNotification) { return handleNotification((PeerAddressChangeNotification) arg0, arg1); } - logger.warn("Polymorphism failure: "+arg0+" arg1: "+arg1); return super.handleNotification(arg0, arg1); } @@ -85,15 +81,14 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo case CANT_START: logger.error(String.format("Can't start for Association=%s", associtaion.getName())); + associtaion.scheduleConnect(); return HandlerResult.CONTINUE; case COMM_LOST: logger.warn(String.format("Communication lost for Association=%s", associtaion.getName())); // Close the Socket - /*TODO mark for delete - * associtaion.close(); - - associtaion.scheduleConnect();*/ + associtaion.close(); + associtaion.scheduleConnect(); try { associtaion.markAssociationDown(); associtaion.getAssociationListener().onCommunicationLost(associtaion); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 3bfc8c4..b8ddadf 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -1,9 +1,6 @@ package org.mobicents.protocols.sctp.multiclient; import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.spi.AbstractSelectableChannel; import java.util.Arrays; @@ -11,6 +8,7 @@ import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicBoolean; +import javolution.util.FastList; import javolution.xml.XMLFormat; import javolution.xml.stream.XMLStreamException; @@ -21,7 +19,6 @@ import org.mobicents.protocols.api.ManagementEventListener; import org.mobicents.protocols.api.PayloadData; -import com.sun.nio.sctp.AbstractNotificationHandler; import com.sun.nio.sctp.MessageInfo; /* @@ -45,12 +42,7 @@ public class OneToManyAssociationImpl extends ManageableAssociation { private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; - private String hostAddress; - private int hostPort; - private String peerAddress; - private int peerPort; - private String name; - private String[] extraHostAddresses; + private AssociationListener associationListener = null; @@ -59,14 +51,6 @@ public class OneToManyAssociationImpl extends ManageableAssociation { protected final OneToManyAssociationHandler associationHandler = new OneToManyAssociationHandler(); - /** - * This is used only for SCTP This is the socket address for peer which will - * be null initially. If the Association has multihome support and if peer - * address changes, this variable is set to new value so new messages are - * now sent to changed peer address - */ - protected volatile SocketAddress peerSocketAddress = null; - // Is the Association been started by management? private AtomicBoolean started = new AtomicBoolean(false); // Is the Association up (connection is established) @@ -74,14 +58,11 @@ public class OneToManyAssociationImpl extends ManageableAssociation { private int workerThreadTable[] = null; - private MultiManagementImpl management; - private volatile MessageInfo msgInfo; private volatile com.sun.nio.sctp.Association sctpAssociation; private final IpChannelType ipChannelType = IpChannelType.SCTP; - private AssociationInfo assocInfo; private OneToManyAssocMultiplexer multiplexer; /** @@ -92,133 +73,6 @@ public class OneToManyAssociationImpl extends ManageableAssociation { //TODO see dev notes private volatile int ioErrors = 0; - static class PeerAddressInfo { - protected SocketAddress peerSocketAddress; - protected int sctpAssocId; - - public PeerAddressInfo(SocketAddress peerSocketAddress) { - super(); - this.peerSocketAddress = peerSocketAddress; - } - - public SocketAddress getPeerSocketAddress() { - return peerSocketAddress; - } - - public int getSctpAssocId() { - return sctpAssocId; - } - - protected void setPeerSocketAddress(SocketAddress peerSocketAddress) { - this.peerSocketAddress = peerSocketAddress; - } - - protected void setSctpAssocId(int sctpAssocId) { - this.sctpAssocId = sctpAssocId; - } - - @Override - public String toString() { - return "PeerAddressInfo [peerSocketAddress=" + peerSocketAddress - + ", sctpAssocId=" + sctpAssocId + "]"; - } - } - - static class HostAddressInfo { - private final String primaryHostAddress; - private final String secondaryHostAddress; - private final int hostPort; - - - public HostAddressInfo(String primaryHostAddress, - String secondaryHostAddress, int hostPort) { - super(); - if (primaryHostAddress == null || primaryHostAddress.isEmpty()) { - throw new IllegalArgumentException("Constructor HostAddressInfo: primaryHostAddress can not be null!"); - } - this.primaryHostAddress = primaryHostAddress; - this.secondaryHostAddress = secondaryHostAddress; - this.hostPort = hostPort; - } - public String getPrimaryHostAddress() { - return primaryHostAddress; - } - - public String getSecondaryHostAddress() { - return secondaryHostAddress; - } - - public int getHostPort() { - return hostPort; - } - - public boolean matches(HostAddressInfo hostAddressInfo) { - if (hostAddressInfo == null) { - return false; - } - if (this.hostPort != hostAddressInfo.getHostPort()) { - return false; - } - if (this.equals(hostAddressInfo)) { - return true; - } - if (this.getPrimaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) - || this.getPrimaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { - return true; - } - if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { - if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) - || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return "HostAddressInfo [primaryHostAddress=" + primaryHostAddress - + ", secondaryHostAddress=" + secondaryHostAddress - + ", hostPort=" + hostPort + "]"; - } - - } - static class AssociationInfo { - protected PeerAddressInfo peerInfo; - protected HostAddressInfo hostInfo; - public PeerAddressInfo getPeerInfo() { - return peerInfo; - } - public HostAddressInfo getHostInfo() { - return hostInfo; - } - @Override - public String toString() { - return "AssociationInfo [peerInfo=" + peerInfo + ", hostInfo=" - + hostInfo + "]"; - } - public AssociationInfo(PeerAddressInfo peerInfo, - HostAddressInfo hostInfo) { - super(); - this.peerInfo = peerInfo; - this.hostInfo = hostInfo; - } - protected void setPeerInfo(PeerAddressInfo peerInfo) { - this.peerInfo = peerInfo; - } - protected void setHostInfo(HostAddressInfo hostInfo) { - this.hostInfo = hostInfo; - } - - } - - protected OneToManyAssociationImpl() { - super(); - // clean transmission buffer - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - } /** * Creating a CLIENT Association @@ -234,41 +88,13 @@ protected OneToManyAssociationImpl() { */ public OneToManyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, String[] extraHostAddresses) throws IOException { - this(); - this.hostAddress = hostAddress; - this.hostPort = hostPort; - this.peerAddress = peerAddress; - this.peerPort = peerPort; - this.name = assocName; - this.extraHostAddresses = extraHostAddresses; - this.peerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); - String secondaryHostAddress = null; - if (extraHostAddresses != null && extraHostAddresses.length >= 1) { - secondaryHostAddress = extraHostAddresses[0]; - } - this.assocInfo = new AssociationInfo(new PeerAddressInfo(peerSocketAddress), - new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); - } - - public AssociationInfo getAssocInfo() { - return assocInfo; - } - - public void setAssocInfo(AssociationInfo assocInfo) { - this.assocInfo = assocInfo; - } - - protected void assignSctpAssociationId(int id) { - this.assocInfo.getPeerInfo().setSctpAssocId(id); + super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); } - protected boolean isConnectedToPeerAddresses(String peerAddresses) { - if (logger.isDebugEnabled()) { - logger.debug("OneToManyAssociationImpl.isConnectedToPeerAddresses - ownPeerAddress: "+getAssocInfo().getPeerInfo().getPeerSocketAddress().toString() - + "parameter peerAddresses: "+peerAddresses); - } - return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); - } public void start() throws Exception { @@ -281,7 +107,7 @@ public void start() throws Exception { return; } - doInitiateConnectionSctp(); + scheduleConnect(); for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { try { @@ -297,7 +123,6 @@ public void stop() throws Exception { logger.warn("Association: "+this+" has been already STOPPED"); return; } - logger.debug("BUG_TRACE_1"); this.multiplexer.stopAssociation(this); for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { try { @@ -446,7 +271,7 @@ protected void setSocketChannel(AbstractSelectableChannel socketChannel) { // } - public void read(PayloadData payload) { + protected void readPayload(PayloadData payload) { if (payload == null) { return; } @@ -488,7 +313,7 @@ public void send(PayloadData payloadData) throws Exception { multiplexer.send(payloadData, this.msgInfo, this); } - protected boolean write(PayloadData payloadData) { + protected boolean writePayload(PayloadData payloadData) { try { if (txBuffer.hasRemaining()) { @@ -570,14 +395,20 @@ private void checkSocketIsOpen() throws Exception { } } + protected void reconnect() { + try { + doInitiateConnectionSctp(); + } catch(Exception ex) { + logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); + scheduleConnect(); + } + } - //TODO proper lifecycle management of multiplexed associations - protected void close() {/* - /* - if (this.sctpAssociation != null && this.socketMultiChannel !=null) { + protected void close() { + if (this.multiplexer != null) { try { - this.socketMultiChannel.shutdown(this.sctpAssociation); + this.multiplexer.stopAssociation(this); } catch (Exception e) { logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); } @@ -591,32 +422,22 @@ protected void close() {/* "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", this.name), e); } - - // Finally clear the txQueue - if (this.txQueue.size() > 0) { - logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", - this.name, this.txQueue.size())); + } + + protected AbstractSelectableChannel getSocketChannel() { + if (this.multiplexer == null) { + return null; } - this.txQueue.clear(); - */ + return this.multiplexer.getSocketMultiChannel(); } -/* + protected void scheduleConnect() { - if (this.getAssociationType() == AssociationType.CLIENT && !useOneToManyConnection) { - // If Associtaion is of Client type, reinitiate the connection - // procedure - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this, MultiChangeRequest.CONNECT, System.currentTimeMillis() + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() + this.management.getConnectDelay())); - } - } else if (this.getAssociationType() == AssociationType.CLIENT && useOneToManyConnection) { - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, MultiChangeRequest.REGISTER, SelectionKey.OP_WRITE)); - } } - }*/ + } protected void initiateConnection() throws IOException { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index 22dff15..0af77a9 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -50,7 +50,6 @@ public HandlerResult handleNotification(Notification arg0, OneToOneAssociationI if (arg0 instanceof PeerAddressChangeNotification) { return handleNotification((PeerAddressChangeNotification) arg0, arg1); } - logger.warn("Polymorphism failure: "+arg0+" arg1: "+arg1); return super.handleNotification(arg0, arg1); } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 59015d0..3ea426b 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -2,12 +2,8 @@ import java.io.IOException; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; -import java.nio.channels.SocketChannel; import java.nio.channels.spi.AbstractSelectableChannel; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ExecutorService; @@ -26,7 +22,6 @@ import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.sctp.ChangeRequest; -import com.sun.nio.sctp.AbstractNotificationHandler; import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; @@ -52,29 +47,12 @@ public class OneToOneAssociationImpl extends ManageableAssociation { private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; - private String hostAddress; - private int hostPort; - private String peerAddress; - private int peerPort; - private String serverName; - private String name; - private IpChannelType ipChannelType; - private String[] extraHostAddresses; - private AssociationType type; private AssociationListener associationListener = null; protected final OneToOneAssociationHandler associationHandler = new OneToOneAssociationHandler(); - /** - * This is used only for SCTP This is the socket address for peer which will - * be null initially. If the Association has multihome support and if peer - * address changes, this variable is set to new value so new messages are - * now sent to changed peer address - */ - protected volatile SocketAddress peerSocketAddress = null; - // Is the Association been started by management? private AtomicBoolean started = new AtomicBoolean(false); // Is the Association up (connection is established) @@ -84,16 +62,15 @@ public class OneToOneAssociationImpl extends ManageableAssociation { private ConcurrentLinkedQueue txQueue = new ConcurrentLinkedQueue(); - private MultiManagementImpl management; - private SctpChannel socketChannelSctp; - private SocketChannel socketChannelTcp; // The buffer into which we'll read data when it's available private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); private volatile MessageInfo msgInfo; + + private OneToManyAssocMultiplexer multiplexer; /** * Count of number of IO Errors occured. If this exceeds the maxIOErrors set @@ -102,19 +79,6 @@ public class OneToOneAssociationImpl extends ManageableAssociation { */ private volatile int ioErrors = 0; - public OneToOneAssociationImpl() { - super(); - // clean transmission buffer - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - - // clean receiver buffer - rxBuffer.clear(); - rxBuffer.rewind(); - rxBuffer.flip(); - } - /** * Creating a CLIENT Association * @@ -127,41 +91,19 @@ public OneToOneAssociationImpl() { * @param extraHostAddresses * @throws IOException */ - public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, - IpChannelType ipChannelType, String[] extraHostAddresses) throws IOException { - this(); - this.hostAddress = hostAddress; - this.hostPort = hostPort; - this.peerAddress = peerAddress; - this.peerPort = peerPort; - this.name = assocName; - this.ipChannelType = ipChannelType; - this.extraHostAddresses = extraHostAddresses; + public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, String[] extraHostAddresses) throws IOException { + super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); this.type = AssociationType.CLIENT; + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); - } - - /** - * Creating a SERVER Association - * - * @param peerAddress - * @param peerPort - * @param serverName - * @param assocName - * @param ipChannelType - */ - public OneToOneAssociationImpl(String peerAddress, int peerPort, String serverName, String assocName, - IpChannelType ipChannelType) { - this(); - this.peerAddress = peerAddress; - this.peerPort = peerPort; - this.serverName = serverName; - this.name = assocName; - this.ipChannelType = ipChannelType; - - this.type = AssociationType.SERVER; - + // clean receiver buffer + rxBuffer.clear(); + rxBuffer.rewind(); + rxBuffer.flip(); } protected void start() throws Exception { @@ -176,9 +118,7 @@ protected void start() throws Exception { } - if (this.type == AssociationType.CLIENT) { - this.scheduleConnect(); - } + scheduleConnect(); for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { try { @@ -349,25 +289,27 @@ protected void setManagement(MultiManagementImpl management) { } protected AbstractSelectableChannel getSocketChannel() { - if (this.ipChannelType == IpChannelType.SCTP) - return this.socketChannelSctp; - else - return this.socketChannelTcp; + return this.socketChannelSctp; } + protected void reconnect() { + try { + doInitiateConnectionSctp(); + } catch(Exception ex) { + logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); + scheduleConnect(); + } + } + /** * @param socketChannel * the socketChannel to set */ protected void setSocketChannel(AbstractSelectableChannel socketChannel) { - if (this.ipChannelType == IpChannelType.SCTP) this.socketChannelSctp = (SctpChannel) socketChannel; - else - this.socketChannelTcp = (SocketChannel) socketChannel; } public void send(PayloadData payloadData) throws Exception { - logger.debug("send - BUG_TRACE 1 - txQueue.size=" + txQueue.size()); try { this.checkSocketIsOpen(); @@ -377,7 +319,6 @@ public void send(PayloadData payloadData) throws Exception { // Indicate we want the interest ops set changed pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE)); - logger.debug("send - BUG_TRACE 2"); // And queue the data we want written // TODO Do we need to synchronize ConcurrentLinkedQueue ? // synchronized (this.txQueue) { @@ -388,36 +329,28 @@ public void send(PayloadData payloadData) throws Exception { // changes this.management.getSocketSelector().wakeup(); } catch (Exception ex) { - logger.error("send - BUG_TRACE ex", ex); + logger.error("Error while sending payload data: " + ex.getMessage(), ex); } } private void checkSocketIsOpen() throws Exception { - if (this.ipChannelType == IpChannelType.SCTP) { - if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() - || this.socketChannelSctp.association() == null) { - logger.warn(String.format( - "Underlying sctp channel doesn't open or doesn't have association for Association=%s", - this.name)); - throw new Exception(String.format( - "Underlying sctp channel doesn't open or doesn't have association for Association=%s", - this.name)); - } - } else { - if (!started.get() || this.socketChannelTcp == null || !this.socketChannelTcp.isOpen() - || !this.socketChannelTcp.isConnected()) - throw new Exception(String.format("Underlying tcp channel doesn't open for Association=%s", this.name)); + if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() + || this.socketChannelSctp.association() == null) { + logger.warn(String.format( + "Underlying sctp channel doesn't open or doesn't have association for Association=%s", + this.name)); + throw new Exception(String.format( + "Underlying sctp channel doesn't open or doesn't have association for Association=%s", + this.name)); } } protected void read() { - logger.debug("read - BUG_TRACE 1"); try { PayloadData payload; - if (this.ipChannelType == IpChannelType.SCTP) - payload = this.doReadSctp(); - else - payload = this.doReadTcp(); + + payload = this.doReadSctp(); + if (payload == null) return; @@ -466,7 +399,6 @@ protected void read() { } private PayloadData doReadSctp() throws IOException { - logger.debug("doReadSctp - BUG_TRACE 1"); rxBuffer.clear(); MessageInfo messageInfo = this.socketChannelSctp.receive(rxBuffer, this, this.associationHandler); @@ -497,30 +429,7 @@ private PayloadData doReadSctp() throws IOException { return payload; } - private PayloadData doReadTcp() throws IOException { - - rxBuffer.clear(); - int len = this.socketChannelTcp.read(rxBuffer); - if (len == -1) { - logger.warn(String.format("Rx -1 while trying to read from underlying socket for Association=%s ", - this.name)); - this.close(); - this.scheduleConnect(); - return null; - } - - rxBuffer.flip(); - byte[] data = new byte[len]; - rxBuffer.get(data); - rxBuffer.clear(); - - PayloadData payload = new PayloadData(len, data, true, false, 0, 0); - - return payload; - } - protected void write(SelectionKey key) { - logger.debug("write - BUG_TRACE 1"); try { if (txBuffer.hasRemaining()) { @@ -546,28 +455,26 @@ protected void write(SelectionKey key) { // TODO: BufferOverflowException ? txBuffer.put(payloadData.getData()); - if (this.ipChannelType == IpChannelType.SCTP) { - int seqControl = payloadData.getStreamNumber(); + int seqControl = payloadData.getStreamNumber(); - if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { - try { - // TODO : calling in same Thread. Is this ok? or - // dangerous? - this.associationListener.inValidStreamId(payloadData); - } catch (Exception e) { + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { - } - txBuffer.clear(); - txBuffer.flip(); - continue; } - - msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); - msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); - msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); + txBuffer.clear(); + txBuffer.flip(); + continue; } + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + txBuffer.flip(); this.doSend(); @@ -606,20 +513,13 @@ protected void write(SelectionKey key) { } private int doSend() throws IOException { - if (this.ipChannelType == IpChannelType.SCTP) - return this.doSendSctp(); - else - return this.doSendTcp(); + return this.doSendSctp(); } private int doSendSctp() throws IOException { return this.socketChannelSctp.send(txBuffer, msgInfo); } - private int doSendTcp() throws IOException { - return this.socketChannelTcp.write(txBuffer); - } - protected void close() { if (this.getSocketChannel() != null) { try { @@ -647,10 +547,7 @@ protected void close() { } protected void scheduleConnect() { - logger.debug("scheduleConnect - BUG_TRACE 1"); if (this.getAssociationType() == AssociationType.CLIENT) { - // If Associtaion is of Client type, reinitiate the connection - // procedure FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() @@ -660,12 +557,9 @@ protected void scheduleConnect() { } protected void initiateConnection() throws IOException { - - // If Association is stopped, don't try to initiate connect if (!this.started.get()) { return; } - if (this.getSocketChannel() != null) { try { this.getSocketChannel().close(); @@ -676,14 +570,10 @@ protected void initiateConnection() throws IOException { this.name), e); } } - try { - if (this.ipChannelType == IpChannelType.SCTP) - this.doInitiateConnectionSctp(); - else - this.doInitiateConnectionTcp(); + this.doInitiateConnectionSctp(); } catch (Exception e) { - logger.error("Error while initiating a connection", e); + logger.error("Error while initiating a connection: " + e.getMessage(), e); this.scheduleConnect(); return; } @@ -714,33 +604,11 @@ protected void setBranchChannel(SctpChannel sctpChannel) { } private void doInitiateConnectionSctp() throws IOException { - // Create a non-blocking socket channel - this.socketChannelSctp = SctpChannel.open(); - this.socketChannelSctp.configureBlocking(false); - - // bind to host address:port - this.socketChannelSctp.bind(new InetSocketAddress(this.hostAddress, this.hostPort)); - if (this.extraHostAddresses != null) { - for (String s : extraHostAddresses) { - this.socketChannelSctp.bindAddress(InetAddress.getByName(s)); - } - } - - // Kick off connection establishment - this.socketChannelSctp.connect(new InetSocketAddress(this.peerAddress, this.peerPort), 32, 32); - } - - private void doInitiateConnectionTcp() throws IOException { - - // Create a non-blocking socket channel - this.socketChannelTcp = SocketChannel.open(); - this.socketChannelTcp.configureBlocking(false); - - // bind to host address:port - this.socketChannelTcp.bind(new InetSocketAddress(this.hostAddress, this.hostPort)); - - // Kick off connection establishment - this.socketChannelTcp.connect(new InetSocketAddress(this.peerAddress, this.peerPort)); + this.multiplexer = management.getMultiChannelController().register(this); + //send init msg + byte[] spaceTrash = new byte[]{0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x12, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00}; + PayloadData payloadData = new PayloadData(spaceTrash.length, spaceTrash, true, false, 0, 0); + this.multiplexer.send(payloadData, null, this); } protected void createworkerThreadTable(int maximumBooundStream) { @@ -758,10 +626,10 @@ public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Association [name=").append(this.name).append(", associationType=").append(this.type) - .append(", ipChannelType=").append(this.ipChannelType).append(", hostAddress=") + .append(", ipChannelType=").append("SCTP").append(", hostAddress=") .append(this.hostAddress).append(", hostPort=").append(this.hostPort).append(", peerAddress=") .append(this.peerAddress).append(", peerPort=").append(this.peerPort).append(", serverName=") - .append(this.serverName); + .append(""); sb.append(", extraHostAddress=["); @@ -796,18 +664,13 @@ public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationI association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); association.peerPort = xml.getAttribute(PEER_PORT, 0); - association.serverName = xml.getAttribute(SERVER_NAME, ""); - association.ipChannelType = IpChannelType.getInstance(xml.getAttribute(IPCHANNEL_TYPE, - IpChannelType.SCTP.getCode())); - if (association.ipChannelType == null) - association.ipChannelType = IpChannelType.SCTP; - int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); - association.extraHostAddresses = new String[extraHostAddressesSize]; + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; - for (int i = 0; i < extraHostAddressesSize; i++) { - association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); - } + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } } @@ -822,8 +685,8 @@ public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat. xml.setAttribute(PEER_ADDRESS, association.peerAddress); xml.setAttribute(PEER_PORT, association.peerPort); - xml.setAttribute(SERVER_NAME, association.serverName); - xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode()); + xml.setAttribute(SERVER_NAME,""); + xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); @@ -834,6 +697,111 @@ public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat. } } }; + + @Override + protected void readPayload(PayloadData payload) { + if (payload == null) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + // If single thread model the listener should be called in the + // selector thread itself + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, + payload), e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + + ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload + .getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } + + @Override + protected boolean writePayload(PayloadData payloadData) { + try { + + if (txBuffer.hasRemaining()) { + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + } + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txBuffer.hasRemaining()) { + txBuffer.clear(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + logger.warn(e); + } + txBuffer.clear(); + txBuffer.flip(); + return false; + } + + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + + logger.debug("write() - msgInfo: "+msgInfo); + txBuffer.flip(); + + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return true; + } + return true; + } + return false; + } catch (IOException e) { + this.ioErrors++; + logger.error(String.format( + "IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), e); + return false; + } catch (Exception ex) { + logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", + this.name, ex.getMessage()), ex); + return false; + } + } + + @Override public void acceptAnonymousAssociation( From b2072a8318c2836a8a92894c1c4b8db4e5b42789 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 12 May 2015 16:26:42 +0200 Subject: [PATCH 10/28] fixing association management functions --- .../multiclient/ManageableAssociation.java | 4 +- .../sctp/multiclient/MultiManagementImpl.java | 2 +- .../sctp/multiclient/MultiSelectorThread.java | 51 ++-------------- .../OneToManyAssocMultiplexer.java | 21 ++----- .../OneToManyAssociationHandler.java | 3 + .../multiclient/OneToManyAssociationImpl.java | 58 ++++++++++++------- 6 files changed, 49 insertions(+), 90 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index af666c7..a01f509 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -37,9 +37,7 @@ public abstract class ManageableAssociation implements Association { protected abstract void reconnect(); protected abstract boolean writePayload(PayloadData payloadData); protected abstract void readPayload(PayloadData payloadData); - - - + protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, String[] extraHostAddresses) throws IOException { this.hostAddress = hostAddress; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index a2aef05..3960491 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -134,7 +134,7 @@ public MultiManagementImpl(String name) throws IOException { binding.setAlias(OneToManyAssociationImpl.class, "association"); binding.setAlias(String.class, "string"); this.socketSelector = SelectorProvider.provider().openSelector(); - String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "false"); + String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "true"); this.enableBranching = Boolean.valueOf(enableBranchingString); } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 0756ce8..116ab71 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -89,7 +89,10 @@ public void run() { while (changes.hasNext()) { MultiChangeRequest change = changes.next(); SelectionKey key = change.getSocketChannel() == null ? null : change.getSocketChannel().keyFor(this.selector); - logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector ); + if (logger.isDebugEnabled()) { + logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector + + " key interesOps=" + (key == null ? "null":key.interestOps())); + } switch (change.getType()) { case MultiChangeRequest.CHANGEOPS: pendingChanges.remove(change); @@ -179,52 +182,6 @@ public void run() { logger.info(String.format("SelectorThread for Management=%s stopped.", this.management.getName())); } } -/* - private void finishConnection(SelectionKey key) throws IOException{ - this.finishConnectionMultiSctp(key); - } -/* - private void finishConnectionMultiSctp(SelectionKey key) throws IOException { - OneToManyAssociationImpl association = (OneToManyAssociationImpl) key.attachment(); - if (logger.isInfoEnabled()) { - logger.info(String.format("Association=%s connected to=%s", association.getName(), "TODO")); - } - this.read(key); - // Register an interest in writing on this channel - key.interestOps(SelectionKey.OP_READ); - - - AssocChangeEvent ace = AssocChangeEvent.COMM_UP; - AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); - association.associationHandler.handleNotification(acn, association); - } -/* - private void finishConnectionSctp(SelectionKey key) throws IOException { - - OneToManyAssociationImpl association = (OneToManyAssociationImpl) key.attachment(); - try { - - SctpChannel socketChannel = (SctpChannel) key.channel(); - - if (socketChannel.isConnectionPending()) { - - // TODO Loop? Or may be sleep for while? - while (socketChannel.isConnectionPending()) { - socketChannel.finishConnect(); - } - } - - if (logger.isInfoEnabled()) { - logger.info(String.format("Association=%s connected to=%s", association.getName(), socketChannel.getRemoteAddresses())); - } - - // Register an interest in writing on this channel - key.interestOps(SelectionKey.OP_READ); - } catch (Exception e) { - logger.error(String.format("Exception while finishing connection for Association=%s", association.getName()), e); - association.scheduleConnect(); - } - }*/ private void read(SelectionKey key) throws IOException { if (key.attachment() instanceof OneToManyAssocMultiplexer) { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index cea1b29..7081788 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -112,7 +112,7 @@ public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagemen protected void registerAssociation(ManageableAssociation association) { if (!started.get()) { - throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); + throw new IllegalStateException("OneToManyAssocMultiplexer is stopped!"); } pendingAssocs.add(association); @@ -341,8 +341,9 @@ protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Associat assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); if (management.isInBranchingMode()) { - //BRANCH - logger.info("BRANCH"); + if (logger.isInfoEnabled()) { + logger.info("Branching association: " + association.getName()); + } try { SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); if (sctpChannel.isBlocking()) { @@ -396,18 +397,4 @@ protected void stop() throws IOException { pendingAssocs.clear(); this.socketMultiChannel.close(); } - - protected synchronized void stopAssociation(ManageableAssociation assocImpl) throws IOException { - if (!started.get()) { - return; - } - if (connectedAssocs.remove(assocImpl.getAssocInfo().getPeerInfo().getSctpAssocId()) == null) { - pendingAssocs.remove(assocImpl); - } - if (pendingAssocs.isEmpty() && connectedAssocs.isEmpty()) { - started.set(false); - logger.debug("All associations of the multiplexer instance is stopped"); - this.socketMultiChannel.close(); - } - } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index c575e1d..5c89fdf 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -18,6 +18,7 @@ public class OneToManyAssociationHandler extends AbstractNotificationHandler pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() - + this.management.getConnectDelay())); + if (!started.get()) { + logger.info("Association " + name + " is not started, no need to reconnect"); + return; + } + if (up.get()) { + logger.info("Associoation " + name + " is up, no need to reconnect"); + try { + this.associationListener.onCommunicationUp(this, associationHandler.getMaxInboundStreams(), associationHandler.getMaxOutboundStreams()); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + this.name), e); + } + } else { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() + + this.management.getConnectDelay())); + } } } From 4e4056eedc4d1133f4acc4c10f136ef9f2c859ef Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Mon, 18 May 2015 16:12:39 +0200 Subject: [PATCH 11/28] fixing some bugs related to association management --- .../multiclient/MultiAssociationHandler.java | 19 +++-- .../sctp/multiclient/MultiSelectorThread.java | 21 ++--- .../OneToManyAssocMultiplexer.java | 80 +++++++++++-------- .../OneToManyAssociationHandler.java | 2 +- .../multiclient/OneToManyAssociationImpl.java | 8 ++ .../OneToOneAssociationHandler.java | 8 +- .../multiclient/OneToOneAssociationImpl.java | 54 +++++++++++-- 7 files changed, 134 insertions(+), 58 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index 0cf9fca..dac5ce6 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -84,7 +84,7 @@ private HandlerResult delegateNotificationHandling(Notification not, HandlerResu @Override public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) { if (logger.isDebugEnabled()) { - logger.debug("handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) is called"); + logger.debug("handleNotification(AssociationChangeNotification=" + not + ", OneToManyAssocMultiplexer=" + multiplexer + ") is called"); } if (not.association() == null) { logger.error("Cannot handle AssociationChangeNotification: association method of AssociationChangeNotification: "+not+" returns null value, handler returns CONTINUE"); @@ -110,11 +110,20 @@ public HandlerResult handleNotification(SendFailedNotification notification, One if (logger.isDebugEnabled()) { logger.debug("handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); } - if (notification.association() == null) { - logger.error("Cannot handle SendFailedNotification: assoction method of SendFailedNotification: "+notification+" returns null value, handler returns RETURN"); + ManageableAssociation assoc = multiplexer.findPendingAssociationByAddress(notification.address()); + if (assoc == null) { + logger.warn("Can not handle sendfafiled notification: no pending manageable association found for address=" + notification.address() + " by the multiplexer"); return HandlerResult.RETURN; - } - return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); + } + //delegate notification + if (assoc instanceof OneToManyAssociationImpl) { + OneToManyAssociationImpl oneToManyAssoc = (OneToManyAssociationImpl) assoc; + return oneToManyAssoc.associationHandler.handleNotification(notification, oneToManyAssoc); + } else if (assoc instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) assoc; + return oneToOneAssoc.associationHandler.handleNotification(notification, oneToOneAssoc); + } + return HandlerResult.RETURN; } @Override diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 116ab71..f1ca9cd 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -23,6 +23,7 @@ package org.mobicents.protocols.sctp.multiclient; import java.io.IOException; +import java.nio.channels.CancelledKeyException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.util.Iterator; @@ -90,8 +91,10 @@ public void run() { MultiChangeRequest change = changes.next(); SelectionKey key = change.getSocketChannel() == null ? null : change.getSocketChannel().keyFor(this.selector); if (logger.isDebugEnabled()) { - logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector - + " key interesOps=" + (key == null ? "null":key.interestOps())); + if (key != null && key.isValid()) { + logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector + + " key interesOps=" + key.interestOps()); + } } switch (change.getType()) { case MultiChangeRequest.CHANGEOPS: @@ -105,14 +108,14 @@ public void run() { case MultiChangeRequest.REGISTER: pendingChanges.remove(change); SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); - AssocChangeEvent ace = AssocChangeEvent.COMM_UP; - AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); + if (change.isMultiAssocRequest()) { key1.attach(change.getAssocMultiplexer()); + AssocChangeEvent ace = AssocChangeEvent.COMM_UP; + AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); change.getAssocMultiplexer().associationHandler.handleNotification(acn, change.getAssocMultiplexer()); } else { - key1.attach(change.getAssociation()); - //change.getOneToOneAssociation().associationHandler.handleNotification(acn, change.getOneToOneAssociation()); + key1.attach(change.getAssociation()); } break; case MultiChangeRequest.CONNECT: @@ -138,8 +141,6 @@ public void run() { // Wait for an event one of the registered channels this.selector.select(500); - //logger.debug("Done selecting, selected keys size: " + this.selector.selectedKeys().size()); - // Iterate over the set of keys for which events are available Iterator selectedKeys = this.selector.selectedKeys().iterator(); @@ -165,7 +166,9 @@ public void run() { this.write(key); } } - + } catch (CancelledKeyException cke) { + //having this exception when closing a channel can be normal, but we log it on WARN level + logger.warn("Selecting a cancelled ready key: " + cke.getMessage()); } catch (Exception e) { logger.error("Error while selecting the ready keys", e); e.printStackTrace(); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 7081788..0da5fa1 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -39,7 +39,7 @@ public class OneToManyAssocMultiplexer { // Is the multiplexer been started by management? private AtomicBoolean started = new AtomicBoolean(false); - + // Queue holds payloads to be transmitted private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); @@ -50,7 +50,7 @@ public class OneToManyAssocMultiplexer { /* * Support fast and save queue operations like: - * + * */ static class ConcurrentLinkedQueueSwapper { private ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -59,17 +59,17 @@ static class ConcurrentLinkedQueueSwapper { public ConcurrentLinkedQueueSwapper(ConcurrentLinkedQueue queue) { this.queue = queue; } - + public void add(T e) { lock.readLock().lock(); queue.add(e); lock.readLock().unlock(); } - + public boolean isEmpty() { return queue.isEmpty(); } - + public ConcurrentLinkedQueue swap(ConcurrentLinkedQueue newQueue) { if (newQueue == null) { throw new NullPointerException(this.getClass()+".swap(ConcurrentLinkedQueue newQueue): newQueue parameter can not be null!"); @@ -81,7 +81,7 @@ public ConcurrentLinkedQueue swap(ConcurrentLinkedQueue newQueue) { lock.writeLock().unlock(); return oldQueue; } - + public void concatAsHead(ConcurrentLinkedQueue newHead) { if (newHead == null) { throw new NullPointerException(this.getClass()+".concatAsHead(ConcurrentLinkedQueue newHead): newHead parameter can not be null!"); @@ -94,7 +94,7 @@ public void concatAsHead(ConcurrentLinkedQueue newHead) { this.queue = newQueueCopy; lock.writeLock().unlock(); } - + } public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagementImpl management) throws IOException { super(); @@ -109,7 +109,7 @@ public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagemen this.rxBuffer.flip(); initMultiChannel(); } - + protected void registerAssociation(ManageableAssociation association) { if (!started.get()) { throw new IllegalStateException("OneToManyAssocMultiplexer is stopped!"); @@ -117,7 +117,7 @@ protected void registerAssociation(ManageableAssociation association) { pendingAssocs.add(association); } - + protected void start() throws IOException { if (!started.compareAndSet(false, true)) { return; @@ -137,7 +137,7 @@ protected void start() throws IOException { SelectionKey.OP_WRITE|SelectionKey.OP_READ)); } } - + protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation association) { if (!started.get()) { throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); @@ -149,11 +149,11 @@ protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation pendingAssocs.remove(association); association.assignSctpAssociationId(id); } - + protected ManageableAssociation findConnectedAssociation(Integer sctpAssocId) { return connectedAssocs.get(sctpAssocId); } - + private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation) { String peerAddresses = ""; try { @@ -163,14 +163,14 @@ private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation } catch (IOException e) { } return peerAddresses; } - + protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { String peerAddresses = extractPeerAddresses(sctpAssociation); if (logger.isDebugEnabled()) { peerAddresses = peerAddresses.isEmpty() ? peerAddresses : peerAddresses.substring(2); logger.debug("Association("+sctpAssociation.associationID()+") connected to "+peerAddresses); } - ManageableAssociation ret=null; + ManageableAssociation ret = null; for (ManageableAssociation assocImpl : pendingAssocs) { if (assocImpl.isConnectedToPeerAddresses(peerAddresses)) { ret = assocImpl; @@ -179,7 +179,26 @@ protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Associat } return ret; } - + + protected boolean removeAssociationFromPendingAssociations(ManageableAssociation association) { + return this.pendingAssocs.remove(association); + } + + protected ManageableAssociation findPendingAssociationByAddress(SocketAddress address) { + String peerAddress = address.toString(); + if (logger.isDebugEnabled()) { + logger.debug("findPendingAssociationByAddress is called with address parameter=" + peerAddress); + } + ManageableAssociation ret = null; + for (ManageableAssociation assocImpl : pendingAssocs) { + if (assocImpl.isConnectedToPeerAddresses(peerAddress)) { + ret = assocImpl; + break; + } + } + return ret; + } + private void initMultiChannel() throws IOException { socketMultiChannel = SctpMultiChannel.open(); socketMultiChannel.configureBlocking(false); @@ -197,14 +216,14 @@ private void initMultiChannel() throws IOException { SelectionKey.OP_WRITE|SelectionKey.OP_READ)); } } - + public HostAddressInfo getHostAddressInfo() { return hostAddressInfo; } public SctpMultiChannel getSocketMultiChannel() { return socketMultiChannel; } - + private ManageableAssociation getAssociationByMessageInfo(MessageInfo msgInfo) { ManageableAssociation ret = null; //find connected assoc @@ -217,7 +236,7 @@ private ManageableAssociation getAssociationByMessageInfo(MessageInfo msgInfo) { } return ret; } - + protected void send(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation sender) throws IOException { if (!started.get()) { return; @@ -236,7 +255,7 @@ protected void send(PayloadData payloadData, MessageInfo messageInfo, Manageable // changes this.management.getSocketSelector().wakeup(); } - + protected void write(SelectionKey key) { if (!started.get()) { return; @@ -255,7 +274,7 @@ protected void write(SelectionKey key) { } return; } - + while (!txQueueTmp.isEmpty()) { SctpMessage msg = txQueueTmp.poll(); if (skipList.contains(msg.getSenderAssoc().getName())) { @@ -267,7 +286,7 @@ protected void write(SelectionKey key) { } } } - + if (!retransmitQueue.isEmpty()) { txQueueSwapper.concatAsHead(retransmitQueue); } @@ -281,7 +300,6 @@ protected void write(SelectionKey key) { } } - private void doReadSctp() throws IOException { rxBuffer.clear(); @@ -317,7 +335,7 @@ private void doReadSctp() throws IOException { } - + protected void read() { if (!started.get()) { return; @@ -330,7 +348,7 @@ protected void read() { logger.error("Unexpected exception: unnable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, ex); } } - + protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Association sctpAssociation) { if (!started.get()) { return null; @@ -349,16 +367,10 @@ protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Associat if (sctpChannel.isBlocking()) { sctpChannel.configureBlocking(false); } - + OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) association; - oneToOneAssoc.setBranchChannel(sctpChannel); - oneToOneAssoc.setManagement(management); - - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(sctpChannel, null, oneToOneAssoc, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE|SelectionKey.OP_READ)); - } + oneToOneAssoc.branch(sctpChannel, management); + if (logger.isDebugEnabled()) { logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); } @@ -373,7 +385,7 @@ protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Associat } return association; } - + protected void stop() throws IOException { if (!started.compareAndSet(true, false)) { return; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 5c89fdf..1f4451d 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -147,8 +147,8 @@ public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssoc @Override public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssociationImpl associtaion) { -// logger.error(String.format("Association=%s SendFailedNotification", associtaion.getName())); logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + associtaion.onSendFailed(); return HandlerResult.RETURN; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 36d2824..6b142a5 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -583,6 +583,14 @@ public void write(OneToManyAssociationImpl association, javolution.xml.XMLFormat } }; + protected void onSendFailed() { + //if started and down then it means it is a CANT_START event and scheduleConnect must be called. + if (started.get() && !up.get()) { + logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); + reconnect(); + } + } + @Override public void acceptAnonymousAssociation( AssociationListener associationListener) throws Exception { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index 0af77a9..ac48701 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -58,6 +58,11 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo switch (not.event()) { case COMM_UP: + //in case when comm is go online but the association has been already stopped COMM_UP event is sinked. + if (!association.isStarted()) { + association.silentlyShutdown(); + return HandlerResult.CONTINUE; + } if (not.association() != null) { this.maxOutboundStreams = not.association().maxOutboundStreams(); this.maxInboundStreams = not.association().maxInboundStreams(); @@ -78,7 +83,6 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", association.getName()), e); } return HandlerResult.CONTINUE; - case CANT_START: logger.error(String.format("Can't start for Association=%s", association.getName())); return HandlerResult.CONTINUE; @@ -121,7 +125,6 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); break; } - return HandlerResult.CONTINUE; } @@ -146,6 +149,7 @@ public HandlerResult handleNotification(ShutdownNotification not, OneToOneAssoci @Override public HandlerResult handleNotification(SendFailedNotification notification, OneToOneAssociationImpl associtaion) { logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + associtaion.onSendFailed(); return HandlerResult.RETURN; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 3ea426b..df718d1 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -20,7 +20,6 @@ import org.mobicents.protocols.api.IpChannelType; import org.mobicents.protocols.api.ManagementEventListener; import org.mobicents.protocols.api.PayloadData; -import org.mobicents.protocols.sctp.ChangeRequest; import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; @@ -317,7 +316,7 @@ public void send(PayloadData payloadData) throws Exception { synchronized (pendingChanges) { // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.CHANGEOPS, + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE)); // And queue the data we want written // TODO Do we need to synchronize ConcurrentLinkedQueue ? @@ -524,6 +523,9 @@ protected void close() { if (this.getSocketChannel() != null) { try { this.getSocketChannel().close(); + if (logger.isDebugEnabled()) { + logger.debug("close() - socketChannel is closed for association=" + getName()); + } } catch (Exception e) { logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); } @@ -587,7 +589,7 @@ protected void initiateConnection() throws IOException { // is ready to complete connection establishment. FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, ChangeRequest.REGISTER, + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.REGISTER, SelectionKey.OP_CONNECT)); } @@ -597,10 +599,22 @@ protected void initiateConnection() throws IOException { } - protected void setBranchChannel(SctpChannel sctpChannel) { - this.started.set(true); - this.up.set(true); + protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { this.socketChannelSctp = sctpChannel; + this.management = management; + + //if association is stopped, channel wont be registered. + if (!started.get()) { + if (logger.isInfoEnabled()) { + logger.info("Branching a stopped association, channel wont be registered to the selector."); + } + } else { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(sctpChannel, null, this, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE|SelectionKey.OP_READ)); + } + } } private void doInitiateConnectionSctp() throws IOException { @@ -625,7 +639,8 @@ protected void createworkerThreadTable(int maximumBooundStream) { public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Association [name=").append(this.name).append(", associationType=").append(this.type) + sb.append("Association [name=").append(this.name).append(", started=").append(started.get()).append(", up=").append(up) + .append(", associationType=").append(this.type) .append(", ipChannelType=").append("SCTP").append(", hostAddress=") .append(this.hostAddress).append(", hostPort=").append(this.hostPort).append(", peerAddress=") .append(this.peerAddress).append(", peerPort=").append(this.peerPort).append(", serverName=") @@ -801,7 +816,32 @@ protected boolean writePayload(PayloadData payloadData) { } } + protected void onSendFailed() { + //if started and down then it means it is a CANT_START event and scheduleConnect must be called. + if (started.get() && !up.get()) { + logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); + reconnect(); + } + } + //called when COMM_UP event arrived after association was stopped. + protected void silentlyShutdown() { + if (!started.get()) { + if (logger.isInfoEnabled()) { + logger.info("Association=" + getName() + " has been already stopped when COMM_UP event arrived, closing sctp association without notifying any listeners."); + } + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + if (logger.isDebugEnabled()) { + logger.debug("close() - socketChannel is closed for association=" + getName()); + } + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + } + } + } @Override public void acceptAnonymousAssociation( From 041b0d71c22240bf370759bf62a405b4cd0cb421 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Thu, 21 May 2015 17:58:04 +0200 Subject: [PATCH 12/28] bug fix: preventing stopped association to transit to comm_up --- .../OneToManyAssociationHandler.java | 48 ++++++++++--------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 1f4451d..356ef58 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -56,10 +56,14 @@ public HandlerResult handleNotification(Notification arg0, OneToManyAssociation } @Override - public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssociationImpl associtaion) { + public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssociationImpl association) { switch (not.event()) { case COMM_UP: + //in case when comm is go online but the association has been already stopped COMM_UP event is sinked. + if (!association.isStarted()) { + return HandlerResult.CONTINUE; + } if (not.association() != null) { this.maxOutboundStreams = not.association().maxOutboundStreams(); this.maxInboundStreams = not.association().maxInboundStreams(); @@ -67,60 +71,60 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo if (logger.isInfoEnabled()) { logger.info(String.format("New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", - associtaion.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); + association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); } - associtaion.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); + association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); // TODO assign Thread's ? try { - associtaion.markAssociationUp(); - associtaion.getAssociationListener().onCommunicationUp(associtaion, this.maxInboundStreams, this.maxOutboundStreams); + association.markAssociationUp(); + association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", associtaion.getName()), e); + logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", association.getName()), e); } return HandlerResult.CONTINUE; case CANT_START: - logger.error(String.format("Can't start for Association=%s", associtaion.getName())); - associtaion.scheduleConnect(); + logger.error(String.format("Can't start for Association=%s", association.getName())); + association.scheduleConnect(); return HandlerResult.CONTINUE; case COMM_LOST: - logger.warn(String.format("Communication lost for Association=%s", associtaion.getName())); + logger.warn(String.format("Communication lost for Association=%s", association.getName())); // Close the Socket - associtaion.close(); - associtaion.scheduleConnect(); + association.close(); + association.scheduleConnect(); try { - associtaion.markAssociationDown(); - associtaion.getAssociationListener().onCommunicationLost(associtaion); + association.markAssociationDown(); + association.getAssociationListener().onCommunicationLost(association); } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", associtaion.getName()), e); + logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", association.getName()), e); } return HandlerResult.RETURN; case RESTART: - logger.warn(String.format("Restart for Association=%s", associtaion.getName())); + logger.warn(String.format("Restart for Association=%s", association.getName())); try { - associtaion.getAssociationListener().onCommunicationRestart(associtaion); + association.getAssociationListener().onCommunicationRestart(association); } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", associtaion.getName()), + logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", association.getName()), e); } return HandlerResult.CONTINUE; case SHUTDOWN: if (logger.isInfoEnabled()) { - logger.info(String.format("Shutdown for Association=%s", associtaion.getName())); + logger.info(String.format("Shutdown for Association=%s", association.getName())); } try { - associtaion.markAssociationDown(); - associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + association.markAssociationDown(); + association.getAssociationListener().onCommunicationShutdown(association); } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), + logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), e); } return HandlerResult.RETURN; default: - logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), associtaion.getName())); + logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); break; } From b39ac850f5930286e7d9795f5d0a8563c4161c04 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 26 May 2015 18:46:44 +0200 Subject: [PATCH 13/28] Closing multichannel if initiate connection is failed. Fixing address already in use error thrown on recconect attempts --- .../OneToManyAssocMultiplexer.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 0da5fa1..531cf29 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -200,11 +200,21 @@ protected ManageableAssociation findPendingAssociationByAddress(SocketAddress ad } private void initMultiChannel() throws IOException { - socketMultiChannel = SctpMultiChannel.open(); - socketMultiChannel.configureBlocking(false); - socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); - if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { - socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); + try { + socketMultiChannel = SctpMultiChannel.open(); + socketMultiChannel.configureBlocking(false); + socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); + if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { + socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); + } + } catch (IOException ex) { + logger.warn("Error while init multi channel: " + ex.getMessage()); + if (socketMultiChannel.isOpen()) { + try { + socketMultiChannel.close(); + } catch (IOException closeEx) {}; + } + throw ex; } started.set(true); if (logger.isDebugEnabled()) { From afcbd55804f105dd843f9d75c377bdb80d11f2b2 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Mon, 13 Jul 2015 17:06:02 +0200 Subject: [PATCH 14/28] refactor, debug logging --- .../multiclient/ManageableAssociation.java | 37 +++------- .../multiclient/MultiAssociationHandler.java | 10 +-- .../sctp/multiclient/MultiChangeRequest.java | 7 +- .../multiclient/MultiChannelController.java | 2 +- .../sctp/multiclient/MultiManagementImpl.java | 32 ++++----- .../sctp/multiclient/MultiSctpXMLBinding.java | 25 +++---- .../sctp/multiclient/MultiSelectorThread.java | 8 +-- .../sctp/multiclient/MultiWorker.java | 5 -- .../sctp/multiclient/NamingThreadFactory.java | 37 ++++++++++ .../OneToManyAssocMultiplexer.java | 56 +++++++++------ .../OneToManyAssociationHandler.java | 6 ++ .../multiclient/OneToManyAssociationImpl.java | 70 ++----------------- .../OneToOneAssociationHandler.java | 9 ++- .../multiclient/OneToOneAssociationImpl.java | 30 +++++--- 14 files changed, 152 insertions(+), 182 deletions(-) create mode 100644 sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index a01f509..d07daf9 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -9,7 +9,16 @@ import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.PayloadData; -import com.sun.nio.sctp.MessageInfo; +/** + * Abstract super class for associations. + * It represents an SCTP association with an interface which provides management functionality + * like: start, stop, reconnect. + * It also defines static classes to describe associations: PeerAddressInfo, HostAddressInfo, + * AssociationInfo used by other classes to identify and compare association objects. + * + * @author balogh.gabor@alerant.hu + * + */ public abstract class ManageableAssociation implements Association { @@ -194,30 +203,4 @@ protected void setHostInfo(HostAddressInfo hostInfo) { } } - - static class SctpMessage { - private final PayloadData payloadData; - private final MessageInfo messageInfo; - private final ManageableAssociation senderAssoc; - protected SctpMessage(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation senderAssoc) { - super(); - this.payloadData = payloadData; - this.messageInfo = messageInfo; - this.senderAssoc = senderAssoc; - } - protected PayloadData getPayloadData() { - return payloadData; - } - protected MessageInfo getMessageInfo() { - return messageInfo; - } - protected ManageableAssociation getSenderAssoc() { - return senderAssoc; - } - @Override - public String toString() { - return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" - + messageInfo + ", senderAssoc=" + senderAssoc + "]"; - } - } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index dac5ce6..a5974c1 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -32,12 +32,14 @@ import com.sun.nio.sctp.ShutdownNotification; /** - * Implements NotificationHandler for the OneToManyAssocMultiplexer class. Its main responsibility is to delegate the notification to the NotificationHandler of the corresponding - * OneToManyAssociationImpl. Each handler method calls the resolveAssociatonImpl method to find the corresponding Association. + * Implements NotificationHandler for the OneToManyAssocMultiplexer class. + * Its main responsibility is to delegate notifications to the NotificationHandler of the corresponding + * OneToManyAssociationImpl. * - * @author alerant appngin + * @author balogh.gabor@alerant.hu * */ +@SuppressWarnings("restriction") class MultiAssociationHandler extends AbstractNotificationHandler { private static final Logger logger = Logger.getLogger(MultiAssociationHandler.class); @@ -49,7 +51,7 @@ public MultiAssociationHandler() { /** * The delegateNotificationHandling method resolves the OneToManyAssociationImpl instance which belongs to the given Notification and calls the handleNotification method of the resolved Association. - * In case the association instance can not be resolved the method returns the value of the defaultResult parameter. + * In case the association instance can not be resolved the method returns the value of the defaultResult parameter. * * @param not - Notification that need to be delegated * @param defaultResult - Default return value used when Association instance can not be resolved diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java index f42019d..7d097be 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java @@ -25,7 +25,7 @@ /** * @author amit bhayani - * @author alerant appngin + * @author balogh.gabor@alerant.hu * */ public final class MultiChangeRequest { @@ -127,9 +127,6 @@ protected long getExecutionTime() { return executionTime; } - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ @Override public String toString() { return "MultiChangeRequest [type=" + type + ", ops=" + ops @@ -138,6 +135,4 @@ public String toString() { + ", multiAssocRequest=" + multiAssocRequest + ", executionTime=" + executionTime + "]"; } - - } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java index 0ed29a2..33b8685 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -10,7 +10,7 @@ /** * Stores and manages of the OneToManyAssocMultiplexer instances of a MultiManagementImpl (SCTP stack) * - * @author alerant appngin + * @author balogh.gabor@alerant.hu * */ public class MultiChannelController { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index 3960491..5403803 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -37,6 +37,7 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; import javolution.text.TextBuilder; import javolution.util.FastList; @@ -65,10 +66,7 @@ * TCP ipChannelType * * @author amit bhayani - * @author alerant appngin - * - * MultiManagementImpl is a limited implemention OneToMany client associations. - * + * @author balogh.gabor@alerant.hu */ public class MultiManagementImpl implements Management { @@ -90,7 +88,8 @@ public class MultiManagementImpl implements Management { protected static final MultiSctpXMLBinding binding = new MultiSctpXMLBinding(); protected static final String TAB_INDENT = "\t"; private static final String CLASS_ATTRIBUTE = "type"; - + private static final AtomicInteger WORKER_POOL_INDEX = new AtomicInteger(0); + private final String name; protected String persistDir = null; @@ -291,13 +290,18 @@ public void start() throws Exception { // If not single thread model we create worker threads this.executorServices = new ExecutorService[this.workerThreads]; for (int i = 0; i < this.workerThreads; i++) { - this.executorServices[i] = Executors.newSingleThreadExecutor(); + this.executorServices[i] = Executors.newSingleThreadExecutor(new NamingThreadFactory("SCTP-" + WORKER_POOL_INDEX.incrementAndGet())); + //this.executorServices[i] = Executors.newSingleThreadExecutor(); + if (logger.isDebugEnabled()) { + logger.debug("Executor service=" + this.executorServices[i] + " assigned to workerThread index of " + i); + } } } + this.socketSelector = SelectorProvider.provider().openSelector(); this.selectorThread = new MultiSelectorThread(this.socketSelector, this); this.selectorThread.setStarted(true); - (new Thread(this.selectorThread)).start(); + (new Thread(this.selectorThread, "SCTP-selector")).start(); this.started = true; @@ -412,7 +416,7 @@ public void load() throws FileNotFoundException { } catch (java.lang.NullPointerException npe) { // ignore. // For backward compatibility we can ignore if these values are not defined - } + } this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); @@ -528,17 +532,7 @@ public ManageableAssociation addAssociation(String hostAddress, int hostPort, St if (assocName.equals(associationTemp.getName())) { throw new Exception(String.format("Already has association=%s", associationTemp.getName())); } -/* TODO: We should need a new condition - if (peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) { - throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d", associationTemp.getName(), - peerAddress, peerPort)); - } - if (hostAddress.equals(associationTemp.getHostAddress()) && associationTemp.getHostPort() == hostPort) { - throw new Exception(String.format("Already has association=%s with same host address=%s and port=%d", associationTemp.getName(), - hostAddress, hostPort)); - } -*/ } ManageableAssociation association = null; if (isInBranchingMode()) { @@ -692,7 +686,7 @@ protected void populateWorkerThread(int workerThreadTable[]) { this.workerThreadCount = 0; } - workerThreadTable[count] = this.workerThreadCount; + workerThreadTable[count] = this.workerThreadCount; this.workerThreadCount++; } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java index 35d1555..85dd815 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java @@ -21,8 +21,7 @@ */ package org.mobicents.protocols.sctp.multiclient; -import java.util.Iterator; -import java.util.Map; +import java.util.Map.Entry; import javolution.xml.XMLBinding; import javolution.xml.XMLFormat; @@ -32,36 +31,34 @@ /** * @author amit bhayani - * @author alerant appngin + * @author balogh.gabor@alerant.hu */ +@SuppressWarnings("serial") public class MultiSctpXMLBinding extends XMLBinding { - protected static final XMLFormat ASSOCIATION_MAP = new XMLFormat(AssociationMap.class) { + protected static final XMLFormat> ASSOCIATION_MAP = new XMLFormat>(null) { @Override - public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { - final Map map = (Map) obj; - - for (Iterator it = map.entrySet().iterator(); it.hasNext();) { - Map.Entry entry = (Map.Entry) it.next(); - + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { + for (Entry entry: obj.entrySet()) { xml.add((String) entry.getKey(), "name", String.class); xml.add((OneToManyAssociationImpl) entry.getValue(), "association", OneToManyAssociationImpl.class); } } @Override - public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { while (xml.hasNext()) { String key = xml.get("name", String.class); OneToManyAssociationImpl association = xml.get("association", OneToManyAssociationImpl.class); obj.put(key, association); } } - }; - - protected XMLFormat getFormat(Class forClass) throws XMLStreamException { + + + @SuppressWarnings("rawtypes") + protected XMLFormat getFormat( Class forClass) throws XMLStreamException { if (AssociationMap.class.equals(forClass)) { return ASSOCIATION_MAP; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index f1ca9cd..4446feb 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -37,9 +37,10 @@ /** * @author amit bhayani - * @author alerant appngin + * @author balogh.gabo@alerant.hu * */ +@SuppressWarnings("restriction") public class MultiSelectorThread implements Runnable { protected static final Logger logger = Logger.getLogger(MultiSelectorThread.class); @@ -70,11 +71,6 @@ protected void setStarted(boolean started) { this.started = started; } - /* - * (non-Javadoc) - * - * @see java.lang.Runnable#run() - */ @Override public void run() { if (logger.isInfoEnabled()) { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java index dc81489..2aabf04 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java @@ -47,11 +47,6 @@ protected MultiWorker(Association association, AssociationListener associationLi this.payloadData = payloadData; } - /* - * (non-Javadoc) - * - * @see java.lang.Runnable#run() - */ @Override public void run() { try { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java new file mode 100644 index 0000000..5b51749 --- /dev/null +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java @@ -0,0 +1,37 @@ +package org.mobicents.protocols.sctp.multiclient; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Thread factory which names threads by "pool--thread-n". + * This is a replacement for Executors.defaultThreadFactory() to be able to identify pools. + * Optionally a delegate thread factory can be given which creates the Thread + * object itself, if no delegate has been given, Executors.defaultThreadFactory is used. + * @author pocsaji.miklos@alerant.hu + * + */ +public class NamingThreadFactory implements ThreadFactory { + + private final ThreadGroup group; + private String baseName; + private final AtomicInteger index = new AtomicInteger(1); + + public NamingThreadFactory(String baseName) { + this.baseName = baseName; + SecurityManager s = System.getSecurityManager(); + group = (s != null) ? s.getThreadGroup() : + Thread.currentThread().getThreadGroup(); + } + + public Thread newThread(Runnable r) { + Thread t = new Thread(group, r, + "pool-" + baseName + "-thread-" + index.getAndIncrement(), + 0); + if (t.isDaemon()) + t.setDaemon(false); + if (t.getPriority() != Thread.NORM_PRIORITY) + t.setPriority(Thread.NORM_PRIORITY); + return t; + } +} diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 531cf29..29838e4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -19,12 +19,12 @@ import org.apache.log4j.Logger; import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.sctp.multiclient.ManageableAssociation.HostAddressInfo; -import org.mobicents.protocols.sctp.multiclient.ManageableAssociation.SctpMessage; import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; import com.sun.nio.sctp.SctpMultiChannel; +@SuppressWarnings("restriction") public class OneToManyAssocMultiplexer { private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); @@ -41,7 +41,7 @@ public class OneToManyAssocMultiplexer { // Queue holds payloads to be transmitted - private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); + private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); @@ -103,11 +103,11 @@ public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagemen } this.hostAddressInfo = hostAddressInfo; this.management = management; - // clean receiver buffer this.rxBuffer.clear(); this.rxBuffer.rewind(); this.rxBuffer.flip(); initMultiChannel(); + started.set(true); } protected void registerAssociation(ManageableAssociation association) { @@ -122,20 +122,10 @@ protected void start() throws IOException { if (!started.compareAndSet(false, true)) { return; } - socketMultiChannel = SctpMultiChannel.open(); - socketMultiChannel.configureBlocking(false); - socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); - if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { - socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); - } - if (logger.isDebugEnabled()) { - logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); - } - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, null, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE|SelectionKey.OP_READ)); - } + this.rxBuffer.clear(); + this.rxBuffer.rewind(); + this.rxBuffer.flip(); + initMultiChannel(); } protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation association) { @@ -180,10 +170,6 @@ protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Associat return ret; } - protected boolean removeAssociationFromPendingAssociations(ManageableAssociation association) { - return this.pendingAssocs.remove(association); - } - protected ManageableAssociation findPendingAssociationByAddress(SocketAddress address) { String peerAddress = address.toString(); if (logger.isDebugEnabled()) { @@ -216,7 +202,7 @@ private void initMultiChannel() throws IOException { } throw ex; } - started.set(true); + if (logger.isDebugEnabled()) { logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); } @@ -419,4 +405,30 @@ protected void stop() throws IOException { pendingAssocs.clear(); this.socketMultiChannel.close(); } + + static class SctpMessage { + private final PayloadData payloadData; + private final MessageInfo messageInfo; + private final ManageableAssociation senderAssoc; + protected SctpMessage(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation senderAssoc) { + super(); + this.payloadData = payloadData; + this.messageInfo = messageInfo; + this.senderAssoc = senderAssoc; + } + protected PayloadData getPayloadData() { + return payloadData; + } + protected MessageInfo getMessageInfo() { + return messageInfo; + } + protected ManageableAssociation getSenderAssoc() { + return senderAssoc; + } + @Override + public String toString() { + return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" + + messageInfo + ", senderAssoc=" + senderAssoc + "]"; + } + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 356ef58..68c6d30 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -11,6 +11,12 @@ import com.sun.nio.sctp.SendFailedNotification; import com.sun.nio.sctp.ShutdownNotification; +/** + * + * @author balogh.gabor@alerant.hu + * + */ +@SuppressWarnings("restriction") public class OneToManyAssociationHandler extends AbstractNotificationHandler { private static final Logger logger = Logger.getLogger(OneToManyAssociationHandler.class); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 6b142a5..eb3aeaa 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -21,10 +21,11 @@ import com.sun.nio.sctp.MessageInfo; -/* - * This Association implementation is limited to ONE-TO-MANY TYPE CLIENT SCTP association +/** + * This Association implementation is limited to ONE-TO-MANY TYPE CLIENT SCTP association. + * @author balogh.gabor@alerant.hu */ - +@SuppressWarnings("restriction") public class OneToManyAssociationImpl extends ManageableAssociation { protected static final Logger logger = Logger.getLogger(OneToManyAssociationImpl.class); @@ -46,7 +47,6 @@ public class OneToManyAssociationImpl extends ManageableAssociation { private AssociationListener associationListener = null; - //TODO see dev notes private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); protected final OneToManyAssociationHandler associationHandler = new OneToManyAssociationHandler(); @@ -114,8 +114,6 @@ public void start() throws Exception { } } scheduleConnect(); - - } public void stop() throws Exception { @@ -395,14 +393,6 @@ private int doSend() throws IOException { return multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); } - - private void checkSocketIsOpen() throws Exception { - if (!started.get()) { - throw new Exception(String.format( - "Association is not open (started) Association=%s", this.name)); - } - } - protected void reconnect() { try { doInitiateConnectionSctp(); @@ -453,57 +443,6 @@ protected void scheduleConnect() { } } - protected void initiateConnection() throws IOException { - - /* // If Association is stopped, don't try to initiate connect - if (!this.started) { - return; - } - - if (this.getSocketChannel() != null) { - try { - this.getSocketChannel().close(); - } catch (Exception e) { - logger.error( - String.format( - "Exception while trying to close existing sctp socket and initiate new socket for Association=%s", - this.name), e); - } - } - - try { - if (this.ipChannelType == IpChannelType.SCTP) - this.doInitiateConnectionSctp(); - else - this.doInitiateConnectionTcp(); - } catch (Exception e) { - logger.error("Error while initiating a connection", e); - this.scheduleConnect(); - return; - } - - // reset the ioErrors - this.ioErrors = 0; - - // Queue a channel registration since the caller is not the - // selecting thread. As part of the registration we'll register - // an interest in connection events. These are raised when a channel - // is ready to complete connection establishment. - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), this, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE)); - } - - sendInit(); - this.connecting = true; - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); -*/ - } - - private void doInitiateConnectionSctp() throws IOException { this.multiplexer = management.getMultiChannelController().register(this); //send init msg @@ -536,7 +475,6 @@ public String toString() { protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( OneToManyAssociationImpl.class) { - @SuppressWarnings("unchecked") @Override public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociationImpl association) throws XMLStreamException { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index ac48701..b10e440 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -11,6 +11,12 @@ import com.sun.nio.sctp.SendFailedNotification; import com.sun.nio.sctp.ShutdownNotification; +/** + * + * @author balogh.gabor@alerant.hu + * + */ +@SuppressWarnings("restriction") public class OneToOneAssociationHandler extends AbstractNotificationHandler { private static final Logger logger = Logger.getLogger(OneToOneAssociationHandler.class); @@ -75,7 +81,6 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); - // TODO assign Thread's ? try { association.markAssociationUp(); association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); @@ -134,8 +139,6 @@ public HandlerResult handleNotification(ShutdownNotification not, OneToOneAssoci logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); } - // TODO assign Thread's ? - try { associtaion.markAssociationDown(); associtaion.getAssociationListener().onCommunicationShutdown(associtaion); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index df718d1..0dab05e 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -26,9 +26,10 @@ /** * @author amit bhayani - * @ + * @author balogh.gabor@alerant.hu * */ +@SuppressWarnings("restriction") public class OneToOneAssociationImpl extends ManageableAssociation { protected static final Logger logger = Logger.getLogger(OneToOneAssociationImpl.class.getName()); @@ -361,16 +362,23 @@ protected void read() { // If single thread model the listener should be called in the // selector thread itself try { + if (logger.isDebugEnabled()) { + logger.debug("Association " + getName() + " read(): singleThread callback"); + } this.associationListener.onPayload(this, payload); } catch (Exception e) { logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, payload), e); } } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload .getStreamNumber()]); + if (logger.isDebugEnabled()) { + logger.debug("Association " + getName() + " read(): payload.streamNumber="+payload.getStreamNumber()+ ", executorSerice=" + executorService); + } try { executorService.execute(worker); } catch (RejectedExecutionException e) { @@ -433,7 +441,6 @@ protected void write(SelectionKey key) { if (txBuffer.hasRemaining()) { // All data wasn't sent in last doWrite. Try to send it now - // this.socketChannel.send(txBuffer, msgInfo); this.doSend(); } @@ -512,13 +519,9 @@ protected void write(SelectionKey key) { } private int doSend() throws IOException { - return this.doSendSctp(); - } - - private int doSendSctp() throws IOException { return this.socketChannelSctp.send(txBuffer, msgInfo); } - + protected void close() { if (this.getSocketChannel() != null) { try { @@ -628,6 +631,11 @@ private void doInitiateConnectionSctp() throws IOException { protected void createworkerThreadTable(int maximumBooundStream) { this.workerThreadTable = new int[maximumBooundStream]; this.management.populateWorkerThread(this.workerThreadTable); + for (int i=0;i ASSOCIATION_XML = new XMLFormat( OneToOneAssociationImpl.class) { - @SuppressWarnings("unchecked") @Override public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationImpl association) throws XMLStreamException { @@ -727,6 +734,9 @@ protected void readPayload(PayloadData payload) { // If single thread model the listener should be called in the // selector thread itself try { + if (logger.isDebugEnabled()) { + logger.debug("Association " + getName() + " readPayload(): singleThread callback"); + } this.associationListener.onPayload(this, payload); } catch (Exception e) { logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, @@ -734,7 +744,9 @@ protected void readPayload(PayloadData payload) { } } else { MultiWorker worker = new MultiWorker(this, this.associationListener, payload); - + if (logger.isDebugEnabled()) { + logger.debug("Association " + getName() + " readPayload(): payload.streamNumber="+payload.getStreamNumber()); + } ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload .getStreamNumber()]); try { From 0c4d76eecc3184c0535ce3c7f2231ffe5b445d80 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 14 Jul 2015 17:14:41 +0200 Subject: [PATCH 15/28] thread naming, delete some logging --- .../sctp/multiclient/MultiManagementImpl.java | 4 --- .../sctp/multiclient/NamingThreadFactory.java | 34 +++++++++++-------- .../multiclient/OneToOneAssociationImpl.java | 5 --- 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index 5403803..514898b 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -291,10 +291,6 @@ public void start() throws Exception { this.executorServices = new ExecutorService[this.workerThreads]; for (int i = 0; i < this.workerThreads; i++) { this.executorServices[i] = Executors.newSingleThreadExecutor(new NamingThreadFactory("SCTP-" + WORKER_POOL_INDEX.incrementAndGet())); - //this.executorServices[i] = Executors.newSingleThreadExecutor(); - if (logger.isDebugEnabled()) { - logger.debug("Executor service=" + this.executorServices[i] + " assigned to workerThread index of " + i); - } } } this.socketSelector = SelectorProvider.provider().openSelector(); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java index 5b51749..2db8be4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java @@ -1,5 +1,6 @@ package org.mobicents.protocols.sctp.multiclient; +import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; @@ -12,26 +13,29 @@ * */ public class NamingThreadFactory implements ThreadFactory { - - private final ThreadGroup group; + + private ThreadFactory delegate; private String baseName; - private final AtomicInteger index = new AtomicInteger(1); + private AtomicInteger index; public NamingThreadFactory(String baseName) { + this(baseName, null); + } + + public NamingThreadFactory(String baseName, ThreadFactory delegate) { this.baseName = baseName; - SecurityManager s = System.getSecurityManager(); - group = (s != null) ? s.getThreadGroup() : - Thread.currentThread().getThreadGroup(); + this.delegate = delegate; + if (this.delegate == null) { + this.delegate = Executors.defaultThreadFactory(); + } + this.index = new AtomicInteger(1); } - public Thread newThread(Runnable r) { - Thread t = new Thread(group, r, - "pool-" + baseName + "-thread-" + index.getAndIncrement(), - 0); - if (t.isDaemon()) - t.setDaemon(false); - if (t.getPriority() != Thread.NORM_PRIORITY) - t.setPriority(Thread.NORM_PRIORITY); - return t; + @Override + public Thread newThread(Runnable r) { + String name = "pool-" + baseName + "-thread-" + index.getAndIncrement(); + Thread ret = delegate.newThread(r); + ret.setName(name); + return ret; } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 0dab05e..83dfa6d 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -631,11 +631,6 @@ private void doInitiateConnectionSctp() throws IOException { protected void createworkerThreadTable(int maximumBooundStream) { this.workerThreadTable = new int[maximumBooundStream]; this.management.populateWorkerThread(this.workerThreadTable); - for (int i=0;i Date: Wed, 22 Jul 2015 14:52:24 +0200 Subject: [PATCH 16/28] fixed configuration persistance features added named threads added some documentation --- .../multiclient/ManageableAssociation.java | 81 +++- .../multiclient/MultiAssociationHandler.java | 9 +- .../multiclient/MultiChannelController.java | 12 +- .../sctp/multiclient/MultiManagementImpl.java | 29 +- .../sctp/multiclient/MultiSctpXMLBinding.java | 47 ++- .../sctp/multiclient/MultiSelectorThread.java | 37 +- .../sctp/multiclient/MultiWorker.java | 4 +- .../sctp/multiclient/NamingThreadFactory.java | 2 +- .../OneToManyAssocMultiplexer.java | 30 +- .../OneToManyAssociationHandler.java | 9 +- .../multiclient/OneToManyAssociationImpl.java | 28 +- .../OneToOneAssociationHandler.java | 1 + .../multiclient/OneToOneAssociationImpl.java | 360 ++++++++---------- 13 files changed, 345 insertions(+), 304 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index d07daf9..aca9085 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -6,6 +6,9 @@ import java.net.SocketAddress; import java.nio.channels.spi.AbstractSelectableChannel; +import javolution.xml.XMLFormat; +import javolution.xml.stream.XMLStreamException; + import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.PayloadData; @@ -21,7 +24,17 @@ */ public abstract class ManageableAssociation implements Association { - + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + protected MultiManagementImpl management; protected String hostAddress; protected int hostPort; @@ -30,7 +43,7 @@ public abstract class ManageableAssociation implements Association { protected String name; protected String[] extraHostAddresses; protected AssociationInfo assocInfo; - + /** * This is used only for SCTP This is the socket address for peer which will * be null initially. If the Association has multihome support and if peer @@ -47,6 +60,10 @@ public abstract class ManageableAssociation implements Association { protected abstract boolean writePayload(PayloadData payloadData); protected abstract void readPayload(PayloadData payloadData); + protected ManageableAssociation() { + + } + protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, String[] extraHostAddresses) throws IOException { this.hostAddress = hostAddress; @@ -55,15 +72,19 @@ protected ManageableAssociation(String hostAddress, int hostPort, String peerAdd this.peerPort = peerPort; this.name = assocName; this.extraHostAddresses = extraHostAddresses; + initDerivedFields(); + } + + protected void initDerivedFields() throws IOException { this.peerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); String secondaryHostAddress = null; if (extraHostAddresses != null && extraHostAddresses.length >= 1) { secondaryHostAddress = extraHostAddresses[0]; } this.assocInfo = new AssociationInfo(new PeerAddressInfo(peerSocketAddress), - new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); + new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); } - + protected void setManagement(MultiManagementImpl management) { this.management = management; } @@ -161,7 +182,7 @@ public boolean matches(HostAddressInfo hostAddressInfo) { if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { - return true; + return true; } } return false; @@ -203,4 +224,54 @@ protected void setHostInfo(HostAddressInfo hostInfo) { } } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + ManageableAssociation.class) { + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, ManageableAssociation association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + + } + + @Override + public void write(ManageableAssociation association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + //xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME,""); + //xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index a5974c1..f97422f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -43,7 +43,7 @@ class MultiAssociationHandler extends AbstractNotificationHandler { private static final Logger logger = Logger.getLogger(MultiAssociationHandler.class); - + public MultiAssociationHandler() { @@ -88,10 +88,11 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo if (logger.isDebugEnabled()) { logger.debug("handleNotification(AssociationChangeNotification=" + not + ", OneToManyAssocMultiplexer=" + multiplexer + ") is called"); } + if (not.association() == null) { logger.error("Cannot handle AssociationChangeNotification: association method of AssociationChangeNotification: "+not+" returns null value, handler returns CONTINUE"); return HandlerResult.CONTINUE; - } + } return delegateNotificationHandling(not, HandlerResult.CONTINUE, multiplexer); } @@ -114,7 +115,7 @@ public HandlerResult handleNotification(SendFailedNotification notification, One } ManageableAssociation assoc = multiplexer.findPendingAssociationByAddress(notification.address()); if (assoc == null) { - logger.warn("Can not handle sendfafiled notification: no pending manageable association found for address=" + notification.address() + " by the multiplexer"); + logger.warn("Can not handle sendfailed notification: no pending manageable association found for address=" + notification.address() + " by the multiplexer"); return HandlerResult.RETURN; } //delegate notification @@ -134,7 +135,7 @@ public HandlerResult handleNotification(PeerAddressChangeNotification notificat logger.debug("handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); } if (notification.association() == null) { - logger.error("Cannot handle PeerAddressChangeNotification: assoction method of PeerAddressChangeNotification: "+notification+" returns null value, handler returns CONTINUE"); + logger.error("Cannot handle PeerAddressChangeNotification: assoction method of PeerAddressChangeNotification: "+notification+" returns null value, handler returns RETURN"); return HandlerResult.RETURN; } return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java index 33b8685..c52d9bd 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -8,7 +8,7 @@ import org.apache.log4j.Logger; /** - * Stores and manages of the OneToManyAssocMultiplexer instances of a MultiManagementImpl (SCTP stack) + * Stores and manages OneToManyAssocMultiplexer and ManageableAssociation objects of a SCTP stack (MultiManagementImpl instance). * * @author balogh.gabor@alerant.hu * @@ -25,7 +25,6 @@ public MultiChannelController(MultiManagementImpl management) { this.management = management; } - private OneToManyAssocMultiplexer findMultiplexerByHostAddrInfo(OneToManyAssociationImpl.HostAddressInfo hostAddressInfo) { OneToManyAssocMultiplexer ret = null; if (logger.isDebugEnabled()) { @@ -48,7 +47,7 @@ private OneToManyAssocMultiplexer findMultiplexerByHostAddrInfo(OneToManyAssocia } return ret; } - + private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrInfo, OneToManyAssocMultiplexer multiplexer) { ArrayList mList = multiplexers.get(hostAddrInfo.getHostPort()); if (mList == null) { @@ -67,6 +66,7 @@ private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrI */ protected OneToManyAssocMultiplexer register(ManageableAssociation assocImpl) throws IOException { if (assocImpl == null || assocImpl.getAssocInfo() == null || assocImpl.getAssocInfo().getHostInfo() == null) { + logger.error("Unable to register association=" + assocImpl); return null; } if (logger.isDebugEnabled()) { @@ -77,13 +77,13 @@ protected OneToManyAssocMultiplexer register(ManageableAssociation assocImpl) th ret = findMultiplexerByHostAddrInfo(assocImpl.getAssocInfo().getHostInfo()); if (ret == null) { ret = new OneToManyAssocMultiplexer(assocImpl.getAssocInfo().getHostInfo(), management); - storeMultiplexer(assocImpl.getAssocInfo().getHostInfo(), ret); + storeMultiplexer(assocImpl.getAssocInfo().getHostInfo(), ret); } ret.registerAssociation(assocImpl); } return ret; } - + protected void stopAllMultiplexers() { for (List mList: multiplexers.values()) { for (OneToManyAssocMultiplexer multiplexer: mList) { @@ -96,5 +96,5 @@ protected void stopAllMultiplexers() { } multiplexers.clear(); } - + } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index 514898b..fb98a11 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -56,14 +56,14 @@ import org.mobicents.protocols.sctp.AssociationMap; /** - * This class is a partial implementation of the Management interface of the sctp-api. + * This class is a partial implementation of the Management interface of the Mobicents SCTP-API. * It is partial because it does not support the whole functionality of the interface instead - * it extends the capabilities of the implementition provided by org.mobicents.protocols.sctp package + * it extends the capabilities of the implementation provided by org.mobicents.protocols.sctp package * with the capability to use One-To-Many type SCTP client associations. * * Therefore the following functionality is not supported by this class: - * server type associations - * TCP ipChannelType + * server type associations + * TCP ipChannelType * * @author amit bhayani * @author balogh.gabor@alerant.hu @@ -85,7 +85,7 @@ public class MultiManagementImpl implements Management { private final TextBuilder persistFile = TextBuilder.newInstance(); - protected static final MultiSctpXMLBinding binding = new MultiSctpXMLBinding(); + protected final MultiSctpXMLBinding binding; protected static final String TAB_INDENT = "\t"; private static final String CLASS_ATTRIBUTE = "type"; private static final AtomicInteger WORKER_POOL_INDEX = new AtomicInteger(0); @@ -129,12 +129,18 @@ public class MultiManagementImpl implements Management { public MultiManagementImpl(String name) throws IOException { this.name = name; - binding.setClassAttribute(CLASS_ATTRIBUTE); - binding.setAlias(OneToManyAssociationImpl.class, "association"); - binding.setAlias(String.class, "string"); - this.socketSelector = SelectorProvider.provider().openSelector(); String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "true"); this.enableBranching = Boolean.valueOf(enableBranchingString); + this.binding = new MultiSctpXMLBinding(enableBranching); + this.binding.setClassAttribute(CLASS_ATTRIBUTE); + if (enableBranching) { + this.binding.setAlias(OneToManyAssociationImpl.class, "association"); + } else { + this.binding.setAlias(OneToOneAssociationImpl.class, "association"); + } + this.binding.setAlias(String.class, "string"); + this.socketSelector = SelectorProvider.provider().openSelector(); + } /** @@ -414,14 +420,13 @@ public void load() throws FileNotFoundException { // For backward compatibility we can ignore if these values are not defined } - this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { n.getValue().setManagement(this); } } catch (XMLStreamException ex) { - // this.logger.info( - // "Error while re-creating Linksets from persisted file", ex); + logger.error("Error while re-creating Linksets from persisted file", ex); } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java index 85dd815..82fb992 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java @@ -36,31 +36,60 @@ @SuppressWarnings("serial") public class MultiSctpXMLBinding extends XMLBinding { - protected static final XMLFormat> ASSOCIATION_MAP = new XMLFormat>(null) { + private final boolean isBranched; + + public MultiSctpXMLBinding(boolean isBranched) { + this.isBranched = isBranched; + } + + protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_ONE = new XMLFormat>(null) { @Override - public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { - for (Entry entry: obj.entrySet()) { + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { + for (Entry entry: obj.entrySet()) { xml.add((String) entry.getKey(), "name", String.class); - xml.add((OneToManyAssociationImpl) entry.getValue(), "association", OneToManyAssociationImpl.class); + xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); } } @Override - public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { while (xml.hasNext()) { String key = xml.get("name", String.class); - OneToManyAssociationImpl association = xml.get("association", OneToManyAssociationImpl.class); + OneToOneAssociationImpl association = xml.get("association", OneToOneAssociationImpl.class); obj.put(key, association); } } }; - - + + protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_MANY = new XMLFormat>(null) { + + @Override + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { + for (Entry entry: obj.entrySet()) { + xml.add((String) entry.getKey(), "name", String.class); + xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); + } + } + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { + while (xml.hasNext()) { + String key = xml.get("name", String.class); + OneToManyAssociationImpl association = null; + association = xml.get("association", OneToManyAssociationImpl.class); + obj.put(key, association); + } + } + }; + @SuppressWarnings("rawtypes") protected XMLFormat getFormat( Class forClass) throws XMLStreamException { if (AssociationMap.class.equals(forClass)) { - return ASSOCIATION_MAP; + if (isBranched) { + return ASSOCIATION_MAP_ONE_TO_ONE; + } + return ASSOCIATION_MAP_ONE_TO_MANY; } return super.getFormat(forClass); } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 4446feb..0f9526e 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -32,15 +32,14 @@ import org.apache.log4j.Logger; -import com.sun.nio.sctp.AssociationChangeNotification; -import com.sun.nio.sctp.AssociationChangeNotification.AssocChangeEvent; - /** + * This class controls the nio sockets and manages the I/O operations. + * * @author amit bhayani * @author balogh.gabo@alerant.hu * */ -@SuppressWarnings("restriction") + public class MultiSelectorThread implements Runnable { protected static final Logger logger = Logger.getLogger(MultiSelectorThread.class); @@ -94,11 +93,11 @@ public void run() { } switch (change.getType()) { case MultiChangeRequest.CHANGEOPS: - pendingChanges.remove(change); + pendingChanges.remove(change); key.interestOps(change.getOps()); break; case MultiChangeRequest.ADD_OPS : - pendingChanges.remove(change); + pendingChanges.remove(change); key.interestOps(key.interestOps() | change.getOps()); break; case MultiChangeRequest.REGISTER: @@ -107,15 +106,11 @@ public void run() { if (change.isMultiAssocRequest()) { key1.attach(change.getAssocMultiplexer()); - AssocChangeEvent ace = AssocChangeEvent.COMM_UP; - AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace); - change.getAssocMultiplexer().associationHandler.handleNotification(acn, change.getAssocMultiplexer()); } else { - key1.attach(change.getAssociation()); + key1.attach(change.getAssociation()); } break; case MultiChangeRequest.CONNECT: - //in CONNECT request assocociation is filled in both OneToOne and OneToMany cases if (!change.getAssociation().isStarted()) { pendingChanges.remove(change); } else { @@ -199,26 +194,8 @@ private void write(SelectionKey key) throws IOException { } else if (key.attachment() instanceof OneToOneAssociationImpl) { OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); association.write(key); - } - } - - class AssociationChangeNotification2 extends AssociationChangeNotification { - - private AssocChangeEvent assocChangeEvent; - - public AssociationChangeNotification2(AssocChangeEvent assocChangeEvent) { - this.assocChangeEvent = assocChangeEvent; - } - - @Override - public com.sun.nio.sctp.Association association() { - return null; - } - - @Override - public AssocChangeEvent event() { - return this.assocChangeEvent; } } + } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java index 2aabf04..c73e95a 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java @@ -26,8 +26,10 @@ import org.mobicents.protocols.api.PayloadData; /** - * @author amit bhayani + * The MultiWorker class is a runnable task which runs the onPayload callback method of the associated associationListener. * + * @author amit bhayani + * @author balogh.gabor@alerant.hu */ public class MultiWorker implements Runnable { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java index 2db8be4..b878487 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java @@ -9,8 +9,8 @@ * This is a replacement for Executors.defaultThreadFactory() to be able to identify pools. * Optionally a delegate thread factory can be given which creates the Thread * object itself, if no delegate has been given, Executors.defaultThreadFactory is used. + * * @author pocsaji.miklos@alerant.hu - * */ public class NamingThreadFactory implements ThreadFactory { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 29838e4..c9ce27f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -6,7 +6,6 @@ import java.net.SocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; -import java.util.HashSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.CopyOnWriteArrayList; @@ -23,7 +22,11 @@ import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; import com.sun.nio.sctp.SctpMultiChannel; - +/** + * Controls the read, write and init operations of SCTP associations of a SctpMultiChannel. + * + * @author balogh.gabor@alerant.hu + */ @SuppressWarnings("restriction") public class OneToManyAssocMultiplexer { private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); @@ -49,7 +52,7 @@ public class OneToManyAssocMultiplexer { protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); /* - * Support fast and save queue operations like: + * Support fast and save queue operations like: swap, conactAsHead. * */ static class ConcurrentLinkedQueueSwapper { @@ -257,8 +260,6 @@ protected void write(SelectionKey key) { return; } ConcurrentLinkedQueue txQueueTmp = txQueueSwapper.swap(new ConcurrentLinkedQueue()); - HashSet skipList = new HashSet(); - ConcurrentLinkedQueue retransmitQueue = new ConcurrentLinkedQueue(); if (txQueueTmp.isEmpty()) { // We wrote away all data, so we're no longer interested @@ -273,21 +274,9 @@ protected void write(SelectionKey key) { while (!txQueueTmp.isEmpty()) { SctpMessage msg = txQueueTmp.poll(); - if (skipList.contains(msg.getSenderAssoc().getName())) { - retransmitQueue.add(msg); - } else { - if (!msg.getSenderAssoc().writePayload(msg.getPayloadData())) { - skipList.add(msg.getSenderAssoc().getName()); - retransmitQueue.add(msg); - } - } + msg.getSenderAssoc().writePayload(msg.getPayloadData()); } - if (!retransmitQueue.isEmpty()) { - txQueueSwapper.concatAsHead(retransmitQueue); - } - - //TODO see dev notes if (txQueueTmp.isEmpty()) { // We wrote away all data, so we're no longer interested // in writing on this socket. Switch back to waiting for @@ -331,7 +320,6 @@ private void doReadSctp() throws IOException { } - protected void read() { if (!started.get()) { return; @@ -375,7 +363,7 @@ protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Associat logger.error(ex); } } - } + }; if (logger.isDebugEnabled()) { logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); } @@ -405,7 +393,7 @@ protected void stop() throws IOException { pendingAssocs.clear(); this.socketMultiChannel.close(); } - + static class SctpMessage { private final PayloadData payloadData; private final MessageInfo messageInfo; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 68c6d30..6e4f21c 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -12,6 +12,7 @@ import com.sun.nio.sctp.ShutdownNotification; /** + * Handles notifications for OneToManyAssociationImpl objects. * * @author balogh.gabor@alerant.hu * @@ -82,7 +83,6 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); - // TODO assign Thread's ? try { association.markAssociationUp(); association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); @@ -143,8 +143,6 @@ public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssoc logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); } - // TODO assign Thread's ? - try { associtaion.markAssociationDown(); associtaion.getAssociationListener().onCommunicationShutdown(associtaion); @@ -164,9 +162,8 @@ public HandlerResult handleNotification(SendFailedNotification notification, One @Override public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssociationImpl associtaion) { - //associtaion.peerSocketAddress = notification.address(); - if(logger.isEnabledFor(Priority.WARN)){ - logger.warn(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); + if(logger.isEnabledFor(Priority.INFO)){ + logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); } return HandlerResult.CONTINUE; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index eb3aeaa..a538a86 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -22,7 +22,9 @@ import com.sun.nio.sctp.MessageInfo; /** - * This Association implementation is limited to ONE-TO-MANY TYPE CLIENT SCTP association. + * Implements a one-to-many type ManagableAssociation. Used when associations is NOT peeled off the sctp multi channel sockets to + * a separate sctp socket channel. + * * @author balogh.gabor@alerant.hu */ @SuppressWarnings("restriction") @@ -66,14 +68,17 @@ public class OneToManyAssociationImpl extends ManageableAssociation { private OneToManyAssocMultiplexer multiplexer; /** - * Count of number of IO Errors occured. If this exceeds the maxIOErrors set - * in Management, socket will be closed and request to reopen the cosket - * will be initiated - */ - //TODO see dev notes + * Count of number of IO Errors occured. + */ private volatile int ioErrors = 0; + public OneToManyAssociationImpl() { + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + } + /** * Creating a CLIENT Association * @@ -210,7 +215,6 @@ protected void markAssociationDown() { logger.debug("Association: "+this+" has been already marked DOWN"); return; } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { try { lstr.onAssociationDown(this); @@ -287,8 +291,6 @@ protected void readPayload(PayloadData payload) { } if (this.management.isSingleThread()) { - // If single thread model the listener should be called in the - // selector thread itself try { this.associationListener.onPayload(this, payload); } catch (Exception e) { @@ -479,7 +481,7 @@ public String toString() { public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociationImpl association) throws XMLStreamException { association.name = xml.getAttribute(NAME, ""); - //association.type = AssociationType.getAssociationType(xml.getAttribute(ASSOCIATION_TYPE, "")); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); association.hostPort = xml.getAttribute(HOST_PORT, 0); @@ -494,7 +496,11 @@ public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociation for (int i = 0; i < extraHostAddressesSize; i++) { association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); } - + try { + association.initDerivedFields(); + } catch (IOException e) { + logger.error("Unable to load association from XML: error while calculating derived fields", e); + } } @Override diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index b10e440..b499cfd 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -12,6 +12,7 @@ import com.sun.nio.sctp.ShutdownNotification; /** + * Handles notifications for OneToOneAssociationImpl objects. * * @author balogh.gabor@alerant.hu * diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 83dfa6d..40dbe48 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -25,6 +25,9 @@ import com.sun.nio.sctp.SctpChannel; /** + * Implements a one-to-one type ManagableAssociation. Used when associations is peeled off the sctp multi channel sockets to + * a separate sctp socket channel. + * * @author amit bhayani * @author balogh.gabor@alerant.hu * @@ -79,6 +82,19 @@ public class OneToOneAssociationImpl extends ManageableAssociation { */ private volatile int ioErrors = 0; + public OneToOneAssociationImpl() { + this.type = AssociationType.CLIENT; + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + + // clean receiver buffer + rxBuffer.clear(); + rxBuffer.rewind(); + rxBuffer.flip(); + } + /** * Creating a CLIENT Association * @@ -362,9 +378,6 @@ protected void read() { // If single thread model the listener should be called in the // selector thread itself try { - if (logger.isDebugEnabled()) { - logger.debug("Association " + getName() + " read(): singleThread callback"); - } this.associationListener.onPayload(this, payload); } catch (Exception e) { logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, @@ -376,9 +389,6 @@ protected void read() { ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload .getStreamNumber()]); - if (logger.isDebugEnabled()) { - logger.debug("Association " + getName() + " read(): payload.streamNumber="+payload.getStreamNumber()+ ", executorSerice=" + executorService); - } try { executorService.execute(worker); } catch (RejectedExecutionException e) { @@ -561,47 +571,6 @@ protected void scheduleConnect() { } } - protected void initiateConnection() throws IOException { - if (!this.started.get()) { - return; - } - if (this.getSocketChannel() != null) { - try { - this.getSocketChannel().close(); - } catch (Exception e) { - logger.error( - String.format( - "Exception while trying to close existing sctp socket and initiate new socket for Association=%s", - this.name), e); - } - } - try { - this.doInitiateConnectionSctp(); - } catch (Exception e) { - logger.error("Error while initiating a connection: " + e.getMessage(), e); - this.scheduleConnect(); - return; - } - - // reset the ioErrors - this.ioErrors = 0; - - // Queue a channel registration since the caller is not the - // selecting thread. As part of the registration we'll register - // an interest in connection events. These are raised when a channel - // is ready to complete connection establishment. - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.REGISTER, - SelectionKey.OP_CONNECT)); - } - - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); - - } - protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { this.socketChannelSctp = sctpChannel; this.management = management; @@ -674,7 +643,6 @@ public String toString() { public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationImpl association) throws XMLStreamException { association.name = xml.getAttribute(NAME, ""); - association.type = AssociationType.getAssociationType(xml.getAttribute(ASSOCIATION_TYPE, "")); association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); association.hostPort = xml.getAttribute(HOST_PORT, 0); @@ -688,181 +656,177 @@ public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationI for (int i = 0; i < extraHostAddressesSize; i++) { association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); } - + try { + association.initDerivedFields(); + } catch (IOException e) { + logger.error("Unable to load association from XML: error while calculating derived fields", e); } + } - @Override - public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) - throws XMLStreamException { - xml.setAttribute(NAME, association.name); - xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); - xml.setAttribute(HOST_ADDRESS, association.hostAddress); - xml.setAttribute(HOST_PORT, association.hostPort); - - xml.setAttribute(PEER_ADDRESS, association.peerAddress); - xml.setAttribute(PEER_PORT, association.peerPort); - - xml.setAttribute(SERVER_NAME,""); - xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); - - xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, - association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); - if (association.extraHostAddresses != null) { - for (String s : association.extraHostAddresses) { - xml.add(s, EXTRA_HOST_ADDRESS, String.class); - } + @Override + public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME,""); + xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); } } - }; + } + }; - @Override - protected void readPayload(PayloadData payload) { - if (payload == null) { - return; - } - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); - } + @Override + protected void readPayload(PayloadData payload) { + if (payload == null) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } - if (this.management.isSingleThread()) { - // If single thread model the listener should be called in the - // selector thread itself - try { - if (logger.isDebugEnabled()) { - logger.debug("Association " + getName() + " readPayload(): singleThread callback"); - } - this.associationListener.onPayload(this, payload); - } catch (Exception e) { - logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, - payload), e); - } - } else { - MultiWorker worker = new MultiWorker(this, this.associationListener, payload); - if (logger.isDebugEnabled()) { - logger.debug("Association " + getName() + " readPayload(): payload.streamNumber="+payload.getStreamNumber()); - } - ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload - .getStreamNumber()]); - try { - executorService.execute(worker); - } catch (RejectedExecutionException e) { - logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); - } catch (NullPointerException e) { - logger.error(String.format("NullPointerException while submitting %s", payload), e); - } catch (Exception e) { - logger.error(String.format("Exception while submitting %s", payload), e); - } + if (this.management.isSingleThread()) { + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, + payload), e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload + .getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); } } + } - @Override - protected boolean writePayload(PayloadData payloadData) { - try { + @Override + protected boolean writePayload(PayloadData payloadData) { + try { - if (txBuffer.hasRemaining()) { - multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + if (txBuffer.hasRemaining()) { + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + } + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txBuffer.hasRemaining()) { + txBuffer.clear(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); } - // TODO Do we need to synchronize ConcurrentLinkedQueue? - // synchronized (this.txQueue) { - if (!txBuffer.hasRemaining()) { - txBuffer.clear(); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); - } - // load ByteBuffer - // TODO: BufferOverflowException ? - txBuffer.put(payloadData.getData()); - - int seqControl = payloadData.getStreamNumber(); + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); - if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { - try { - // TODO : calling in same Thread. Is this ok? or - // dangerous? - this.associationListener.inValidStreamId(payloadData); - } catch (Exception e) { - logger.warn(e); - } - txBuffer.clear(); - txBuffer.flip(); - return false; + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + logger.warn(e); } + txBuffer.clear(); + txBuffer.flip(); + return false; + } - msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); - - msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); - msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); - logger.debug("write() - msgInfo: "+msgInfo); - txBuffer.flip(); + logger.debug("write() - msgInfo: "+msgInfo); + txBuffer.flip(); - multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); - if (txBuffer.hasRemaining()) { - // Couldn't send all data. Lets return now and try to - // send - // this message in next cycle - return true; - } + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle return true; } - return false; - } catch (IOException e) { - this.ioErrors++; - logger.error(String.format( - "IOException while trying to write to underlying socket for Association=%s IOError count=%d", - this.name, this.ioErrors), e); - return false; - } catch (Exception ex) { - logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", - this.name, ex.getMessage()), ex); - return false; + return true; } + return false; + } catch (IOException e) { + this.ioErrors++; + logger.error(String.format( + "IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), e); + return false; + } catch (Exception ex) { + logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", + this.name, ex.getMessage()), ex); + return false; } - - protected void onSendFailed() { - //if started and down then it means it is a CANT_START event and scheduleConnect must be called. - if (started.get() && !up.get()) { - logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); - reconnect(); - } + } + + protected void onSendFailed() { + //if started and down then it means it is a CANT_START event and scheduleConnect must be called. + if (started.get() && !up.get()) { + logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); + reconnect(); } - - //called when COMM_UP event arrived after association was stopped. - protected void silentlyShutdown() { - if (!started.get()) { - if (logger.isInfoEnabled()) { - logger.info("Association=" + getName() + " has been already stopped when COMM_UP event arrived, closing sctp association without notifying any listeners."); - } - if (this.getSocketChannel() != null) { - try { - this.getSocketChannel().close(); - if (logger.isDebugEnabled()) { - logger.debug("close() - socketChannel is closed for association=" + getName()); - } - } catch (Exception e) { - logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + + //called when COMM_UP event arrived after association was stopped. + protected void silentlyShutdown() { + if (!started.get()) { + if (logger.isInfoEnabled()) { + logger.info("Association=" + getName() + " has been already stopped when COMM_UP event arrived, closing sctp association without notifying any listeners."); + } + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + if (logger.isDebugEnabled()) { + logger.debug("close() - socketChannel is closed for association=" + getName()); } + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); } } } - - @Override - public void acceptAnonymousAssociation( - AssociationListener associationListener) throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void rejectAnonymousAssociation() { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void stopAnonymousAssociation() throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } + } + + @Override + public void acceptAnonymousAssociation( + AssociationListener associationListener) throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void rejectAnonymousAssociation() { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } + + @Override + public void stopAnonymousAssociation() throws Exception { + throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); + } } From 1d6d08c671cf53fb52fc0e563a1def5a84448272 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Wed, 5 Aug 2015 16:47:02 +0200 Subject: [PATCH 17/28] + initPayloadData used as "dummy" message to initiate SCTP connection now is able to be configured through MultiManagementImpl --- .../sctp/multiclient/ManageableAssociation.java | 11 +++++++++++ .../sctp/multiclient/MultiManagementImpl.java | 13 +++++++++++++ .../sctp/multiclient/OneToManyAssociationImpl.java | 5 +---- .../sctp/multiclient/OneToOneAssociationImpl.java | 5 +---- 4 files changed, 26 insertions(+), 8 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index aca9085..3e4634c 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -43,6 +43,9 @@ public abstract class ManageableAssociation implements Association { protected String name; protected String[] extraHostAddresses; protected AssociationInfo assocInfo; + + //payload data used in the first dummy message which initiate the connect procedure + protected PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); /** * This is used only for SCTP This is the socket address for peer which will @@ -105,6 +108,14 @@ protected boolean isConnectedToPeerAddresses(String peerAddresses) { return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); } + protected PayloadData getInitPayloadData() { + return initPayloadData; + } + + protected void setInitPayloadData(PayloadData initPayloadData) { + this.initPayloadData = initPayloadData; + } + static class PeerAddressInfo { protected SocketAddress peerSocketAddress; protected int sctpAssocId; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index fb98a11..3118a58 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -51,6 +51,7 @@ import org.mobicents.protocols.api.IpChannelType; import org.mobicents.protocols.api.Management; import org.mobicents.protocols.api.ManagementEventListener; +import org.mobicents.protocols.api.PayloadData; import org.mobicents.protocols.api.Server; import org.mobicents.protocols.api.ServerListener; import org.mobicents.protocols.sctp.AssociationMap; @@ -126,6 +127,9 @@ public class MultiManagementImpl implements Management { private final MultiChannelController multiChannelController = new MultiChannelController(this); private boolean enableBranching; + + //default value of the dummy message sent to initiate the SCTP connection + private PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); public MultiManagementImpl(String name) throws IOException { this.name = name; @@ -543,6 +547,7 @@ public ManageableAssociation addAssociation(String hostAddress, int hostPort, St } association.setManagement(this); + association.setInitPayloadData(initPayloadData); AssociationMap newAssociations = new AssociationMap(); newAssociations.putAll(this.associations); @@ -667,6 +672,14 @@ public void removeAssociation(String assocName) throws Exception { } } + public PayloadData getInitPayloadData() { + return initPayloadData; + } + + public void setInitPayloadData(PayloadData initPayloadData) { + this.initPayloadData = initPayloadData; + } + /** * @return the pendingChanges */ diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index a538a86..43da831 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -447,10 +447,7 @@ protected void scheduleConnect() { private void doInitiateConnectionSctp() throws IOException { this.multiplexer = management.getMultiChannelController().register(this); - //send init msg - byte[] spaceTrash = new byte[]{0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x12, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00}; - PayloadData payloadData = new PayloadData(spaceTrash.length, spaceTrash, true, false, 0, 0); - this.multiplexer.send(payloadData, null, this); + this.multiplexer.send(getInitPayloadData(), null, this); } protected void createworkerThreadTable(int maximumBooundStream) { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 40dbe48..421dda2 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -591,10 +591,7 @@ protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { private void doInitiateConnectionSctp() throws IOException { this.multiplexer = management.getMultiChannelController().register(this); - //send init msg - byte[] spaceTrash = new byte[]{0x01, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x18, 0x00, 0x06, 0x00, 0x08, 0x00, 0x00, 0x00, 0x05, 0x00, 0x12, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00}; - PayloadData payloadData = new PayloadData(spaceTrash.length, spaceTrash, true, false, 0, 0); - this.multiplexer.send(payloadData, null, this); + this.multiplexer.send(getInitPayloadData(), null, this); } protected void createworkerThreadTable(int maximumBooundStream) { From d195465ddde7fe1f991d25be0e2b875ce6137569 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Tue, 25 Aug 2015 10:52:51 +0200 Subject: [PATCH 18/28] modified setConnectDelay, setSingleThread, setWorkerThreads in MultiManagementImpl to comply with the Management API changes (see issue #8 of the original Mobicents project: https://github.com/Mobicents/sctp/issues/8) --- .../sctp/multiclient/MultiManagementImpl.java | 39 +++++++++---------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index 3118a58..b359195 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -81,8 +81,6 @@ public class MultiManagementImpl implements Management { private static final String ASSOCIATIONS = "associations"; private static final String CONNECT_DELAY_PROP = "connectdelay"; - private static final String SINGLE_THREAD_PROP = "singlethread"; - private static final String WORKER_THREADS_PROP = "workerthreads"; private final TextBuilder persistFile = TextBuilder.newInstance(); @@ -173,7 +171,10 @@ public int getConnectDelay() { * @param connectDelay * the connectDelay to set */ - public void setConnectDelay(int connectDelay) { + public void setConnectDelay(int connectDelay) throws Exception { + if (!this.started) + throw new Exception("ConnectDelay parameter can be updated only when SCTP stack is running"); + this.connectDelay = connectDelay; this.store(); @@ -190,13 +191,14 @@ public int getWorkerThreads() { * @param workerThreads * the workerThreads to set */ - public void setWorkerThreads(int workerThreads) { + public void setWorkerThreads(int workerThreads) throws Exception { + if (this.started) + throw new Exception("WorkerThreads parameter can be updated only when SCTP stack is NOT running"); + if (workerThreads < 1) { workerThreads = DEFAULT_IO_THREADS; } this.workerThreads = workerThreads; - - this.store(); } /** @@ -228,10 +230,11 @@ public boolean isSingleThread() { * @param singleThread * the singleThread to set */ - public void setSingleThread(boolean singleThread) { + public void setSingleThread(boolean singleThread) throws Exception { + if (this.started) + throw new Exception("SingleThread parameter can be updated only when SCTP stack is NOT running"); + this.singleThread = singleThread; - - this.store(); } protected FastList getManagementEventListeners() { @@ -415,14 +418,12 @@ public void load() throws FileNotFoundException { reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); reader.setBinding(binding); - try { - this.connectDelay = reader.read(CONNECT_DELAY_PROP, Integer.class); - this.workerThreads = reader.read(WORKER_THREADS_PROP, Integer.class); - this.singleThread = reader.read(SINGLE_THREAD_PROP, Boolean.class); - } catch (java.lang.NullPointerException npe) { - // ignore. - // For backward compatibility we can ignore if these values are not defined - } + try { + this.connectDelay = reader.read(CONNECT_DELAY_PROP, Integer.class); + } catch (java.lang.NullPointerException npe) { + // ignore. + // For backward compatibility we can ignore if these values are not defined + } this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); @@ -445,9 +446,7 @@ public void store() { // writer.setReferenceResolver(new XMLReferenceResolver()); writer.setIndentation(TAB_INDENT); - writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class); - writer.write(this.workerThreads, WORKER_THREADS_PROP, Integer.class); - writer.write(this.singleThread, SINGLE_THREAD_PROP, Boolean.class); + writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class); writer.write(this.associations, ASSOCIATIONS, AssociationMap.class); From a19a76bac83fa1804eff35d16913190525a2800e Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Thu, 3 Sep 2015 11:09:57 +0200 Subject: [PATCH 19/28] bug fix: issue #1 - Init storm --- .../protocols/sctp/multiclient/OneToManyAssociationImpl.java | 2 +- .../protocols/sctp/multiclient/OneToOneAssociationImpl.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 43da831..27cbf37 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -528,7 +528,7 @@ protected void onSendFailed() { //if started and down then it means it is a CANT_START event and scheduleConnect must be called. if (started.get() && !up.get()) { logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); - reconnect(); + scheduleConnect(); } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 421dda2..1607cc5 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -788,7 +788,7 @@ protected void onSendFailed() { //if started and down then it means it is a CANT_START event and scheduleConnect must be called. if (started.get() && !up.get()) { logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); - reconnect(); + scheduleConnect(); } } From 0ba76675abac4135c6675cd7be56781c6949283f Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Wed, 23 Sep 2015 13:47:52 +0200 Subject: [PATCH 20/28] solution for issue #2 --- .../sctp/multiclient/OneToManyAssociationHandler.java | 1 + .../sctp/multiclient/OneToManyAssociationImpl.java | 7 ------- .../sctp/multiclient/OneToOneAssociationHandler.java | 1 + 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 6e4f21c..37f3b45 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -124,6 +124,7 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo try { association.markAssociationDown(); association.getAssociationListener().onCommunicationShutdown(association); + association.scheduleConnect(); } catch (Exception e) { logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), e); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 27cbf37..1536343 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -429,13 +429,6 @@ protected void scheduleConnect() { } if (up.get()) { logger.info("Associoation " + name + " is up, no need to reconnect"); - try { - this.associationListener.onCommunicationUp(this, associationHandler.getMaxInboundStreams(), associationHandler.getMaxOutboundStreams()); - } catch (Exception e) { - logger.error(String.format( - "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", - this.name), e); - } } else { FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index b499cfd..fbaf6eb 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -122,6 +122,7 @@ public HandlerResult handleNotification(AssociationChangeNotification not, OneTo try { association.markAssociationDown(); association.getAssociationListener().onCommunicationShutdown(association); + association.scheduleConnect(); } catch (Exception e) { logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), e); From ee52e01bfc38a2684c339e5e5c69a309645fa293 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Wed, 4 Nov 2015 11:26:05 +0100 Subject: [PATCH 21/28] bug fix for issue #3 --- .../sctp/multiclient/OneToManyAssociationImpl.java | 3 +++ .../sctp/multiclient/OneToOneAssociationImpl.java | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 1536343..c020c4f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -383,6 +383,9 @@ protected boolean writePayload(PayloadData payloadData) { logger.error(String.format( "IOException while trying to write to underlying socket for Association=%s IOError count=%d", this.name, this.ioErrors), e); + logger.error("Internal send failed, retrying."); + this.close(); + onSendFailed(); return false; } catch (Exception ex) { logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 1607cc5..757eec4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -310,6 +310,9 @@ protected AbstractSelectableChannel getSocketChannel() { protected void reconnect() { try { + if (isOpen()) { + close(); + } doInitiateConnectionSctp(); } catch(Exception ex) { logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); @@ -532,6 +535,10 @@ private int doSend() throws IOException { return this.socketChannelSctp.send(txBuffer, msgInfo); } + protected boolean isOpen() { + return this.getSocketChannel() != null && this.getSocketChannel().isOpen(); + } + protected void close() { if (this.getSocketChannel() != null) { try { @@ -590,6 +597,8 @@ protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { } private void doInitiateConnectionSctp() throws IOException { + // reset the ioErrors + this.ioErrors = 0; this.multiplexer = management.getMultiChannelController().register(this); this.multiplexer.send(getInitPayloadData(), null, this); } @@ -776,6 +785,9 @@ protected boolean writePayload(PayloadData payloadData) { logger.error(String.format( "IOException while trying to write to underlying socket for Association=%s IOError count=%d", this.name, this.ioErrors), e); + logger.error("Internal send failed, retrying."); + this.close(); + onSendFailed(); return false; } catch (Exception ex) { logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", From 176c58829e4a5ad7c90ed16781e73f989be80d75 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Wed, 11 Nov 2015 14:50:05 +0100 Subject: [PATCH 22/28] fix issue #3 fix race condition bug in branching add some extra logging --- .../sctp/multiclient/MultiSelectorThread.java | 25 ++++++++++++++-- .../OneToManyAssocMultiplexer.java | 4 +-- .../multiclient/OneToOneAssociationImpl.java | 30 ++++++++----------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 0f9526e..19b7c05 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -84,6 +84,7 @@ public void run() { Iterator changes = pendingChanges.iterator(); while (changes.hasNext()) { MultiChangeRequest change = changes.next(); + SelectionKey key = change.getSocketChannel() == null ? null : change.getSocketChannel().keyFor(this.selector); if (logger.isDebugEnabled()) { if (key != null && key.isValid()) { @@ -94,20 +95,39 @@ public void run() { switch (change.getType()) { case MultiChangeRequest.CHANGEOPS: pendingChanges.remove(change); - key.interestOps(change.getOps()); + if (key == null ) { + logger.warn("change=" + change + ": key is null", new NullPointerException("Selection key is null")); + } else if (!key.isValid()) { + logger.warn("change=" + change + ": key=" + key + " key is invalid", new InternalError("Selection key is invalid")); + } else { + key.interestOps(change.getOps()); + } break; case MultiChangeRequest.ADD_OPS : pendingChanges.remove(change); - key.interestOps(key.interestOps() | change.getOps()); + if (key == null ) { + logger.warn("change=" + change + ": key is null", new NullPointerException("Selection key is null")); + } else if (!key.isValid()) { + logger.warn("change=" + change + ": key=" + key + " key is invalid", new InternalError("Selection key is invalid")); + } else { + key.interestOps(key.interestOps() | change.getOps()); + } break; case MultiChangeRequest.REGISTER: pendingChanges.remove(change); + SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); if (change.isMultiAssocRequest()) { key1.attach(change.getAssocMultiplexer()); + if (logger.isDebugEnabled()) { + logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + " of the association=" + change.getAssocMultiplexer()); + } } else { key1.attach(change.getAssociation()); + if (logger.isDebugEnabled()) { + logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + " of the association=" + change.getAssociation()); + } } break; case MultiChangeRequest.CONNECT: @@ -125,6 +145,7 @@ public void run() { if (!change.isMultiAssocRequest()) { change.getAssociation().close(); } + break; } } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index c9ce27f..4f1271a 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -197,8 +197,8 @@ private void initMultiChannel() throws IOException { socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); } } catch (IOException ex) { - logger.warn("Error while init multi channel: " + ex.getMessage()); - if (socketMultiChannel.isOpen()) { + logger.warn("Error while init multi channel ", ex); + if (socketMultiChannel != null && socketMultiChannel.isOpen()) { try { socketMultiChannel.close(); } catch (IOException closeEx) {}; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 757eec4..9ec80f6 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -310,9 +310,6 @@ protected AbstractSelectableChannel getSocketChannel() { protected void reconnect() { try { - if (isOpen()) { - close(); - } doInitiateConnectionSctp(); } catch(Exception ex) { logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); @@ -334,7 +331,6 @@ public void send(PayloadData payloadData) throws Exception { FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { - // Indicate we want the interest ops set changed pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE)); @@ -343,7 +339,6 @@ public void send(PayloadData payloadData) throws Exception { // synchronized (this.txQueue) { this.txQueue.add(payloadData); } - // Finally, wake up our selecting thread so it can make the required // changes this.management.getSocketSelector().wakeup(); @@ -550,16 +545,16 @@ protected void close() { logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); } } - - try { - this.markAssociationDown(); - this.associationListener.onCommunicationShutdown(this); - } catch (Exception e) { - logger.error(String.format( - "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", - this.name), e); + if (this.up.get()) { + try { + this.markAssociationDown(); + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + this.name), e); + } } - // Finally clear the txQueue if (this.txQueue.size() > 0) { logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", @@ -579,9 +574,6 @@ protected void scheduleConnect() { } protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { - this.socketChannelSctp = sctpChannel; - this.management = management; - //if association is stopped, channel wont be registered. if (!started.get()) { if (logger.isInfoEnabled()) { @@ -590,10 +582,12 @@ protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { } else { FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { + this.socketChannelSctp = sctpChannel; + this.management = management; pendingChanges.add(new MultiChangeRequest(sctpChannel, null, this, MultiChangeRequest.REGISTER, SelectionKey.OP_WRITE|SelectionKey.OP_READ)); } - } + }; } private void doInitiateConnectionSctp() throws IOException { From e4c7a84a163c79c3469d0a8fcfbc41a65fd8d525 Mon Sep 17 00:00:00 2001 From: "alerant.appngin" Date: Wed, 11 Nov 2015 19:40:00 +0100 Subject: [PATCH 23/28] fixed a bug when an already closed association got an onAssociationUp event and the channel is not closed properly --- .../protocols/sctp/multiclient/OneToOneAssociationImpl.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 9ec80f6..31a666b 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -579,9 +579,13 @@ protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { if (logger.isInfoEnabled()) { logger.info("Branching a stopped association, channel wont be registered to the selector."); } + //set channel to able to close later + this.socketChannelSctp = sctpChannel; + this.management = management; } else { FastList pendingChanges = this.management.getPendingChanges(); synchronized (pendingChanges) { + //setting the channel must be synchronized this.socketChannelSctp = sctpChannel; this.management = management; pendingChanges.add(new MultiChangeRequest(sctpChannel, null, this, MultiChangeRequest.REGISTER, From 7ca91a3b69f3b31ea4dcbf5d70b5a13d2f8fb111 Mon Sep 17 00:00:00 2001 From: Gabor Balogh Date: Tue, 9 Aug 2016 13:54:20 +0200 Subject: [PATCH 24/28] Add secondary peer address to association and implement failover in INIT message flow Extend the API to be able to set a secondary peer address to associations and modify INIT flow to use this secondary peer address if connection establishment with the primary peer address is failed. --- .../multiclient/ManageableAssociation.java | 585 +++--- .../multiclient/MultiAssociationHandler.java | 195 +- .../sctp/multiclient/MultiChangeRequest.java | 214 +-- .../sctp/multiclient/MultiManagementImpl.java | 1414 +++++++------- .../sctp/multiclient/MultiSctpXMLBinding.java | 106 +- .../sctp/multiclient/MultiSelectorThread.java | 358 ++-- .../sctp/multiclient/MultiWorker.java | 44 +- .../sctp/multiclient/NamingThreadFactory.java | 9 +- .../OneToManyAssocMultiplexer.java | 815 +++++---- .../OneToManyAssociationHandler.java | 309 ++-- .../multiclient/OneToManyAssociationImpl.java | 1039 +++++------ .../OneToOneAssociationHandler.java | 309 ++-- .../multiclient/OneToOneAssociationImpl.java | 1618 +++++++++-------- 13 files changed, 3595 insertions(+), 3420 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index 3e4634c..a2532b6 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -9,280 +9,333 @@ import javolution.xml.XMLFormat; import javolution.xml.stream.XMLStreamException; +import org.apache.log4j.Logger; import org.mobicents.protocols.api.Association; import org.mobicents.protocols.api.PayloadData; /** - * Abstract super class for associations. - * It represents an SCTP association with an interface which provides management functionality - * like: start, stop, reconnect. - * It also defines static classes to describe associations: PeerAddressInfo, HostAddressInfo, - * AssociationInfo used by other classes to identify and compare association objects. + * Abstract super class for associations. It represents an SCTP association with an interface which provides management + * functionality like: start, stop, reconnect. It also defines static classes to describe associations: PeerAddressInfo, + * HostAddressInfo, AssociationInfo used by other classes to identify and compare association objects. * * @author balogh.gabor@alerant.hu * */ public abstract class ManageableAssociation implements Association { - private static final String NAME = "name"; - private static final String SERVER_NAME = "serverName"; - private static final String HOST_ADDRESS = "hostAddress"; - private static final String HOST_PORT = "hostPort"; - - private static final String PEER_ADDRESS = "peerAddress"; - private static final String PEER_PORT = "peerPort"; - - private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; - private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; - - protected MultiManagementImpl management; - protected String hostAddress; - protected int hostPort; - protected String peerAddress; - protected int peerPort; - protected String name; - protected String[] extraHostAddresses; - protected AssociationInfo assocInfo; - - //payload data used in the first dummy message which initiate the connect procedure - protected PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); - - /** - * This is used only for SCTP This is the socket address for peer which will - * be null initially. If the Association has multihome support and if peer - * address changes, this variable is set to new value so new messages are - * now sent to changed peer address - */ - protected volatile SocketAddress peerSocketAddress = null; - - protected abstract void start() throws Exception; - protected abstract void stop() throws Exception; - protected abstract AbstractSelectableChannel getSocketChannel(); - protected abstract void close(); - protected abstract void reconnect(); - protected abstract boolean writePayload(PayloadData payloadData); - protected abstract void readPayload(PayloadData payloadData); - - protected ManageableAssociation() { - - } - - protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, - String[] extraHostAddresses) throws IOException { - this.hostAddress = hostAddress; - this.hostPort = hostPort; - this.peerAddress = peerAddress; - this.peerPort = peerPort; - this.name = assocName; - this.extraHostAddresses = extraHostAddresses; - initDerivedFields(); - } - - protected void initDerivedFields() throws IOException { - this.peerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); - String secondaryHostAddress = null; - if (extraHostAddresses != null && extraHostAddresses.length >= 1) { - secondaryHostAddress = extraHostAddresses[0]; - } - this.assocInfo = new AssociationInfo(new PeerAddressInfo(peerSocketAddress), - new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); - } - - protected void setManagement(MultiManagementImpl management) { - this.management = management; - } - - protected AssociationInfo getAssocInfo() { - return assocInfo; - } - - protected void setAssocInfo(AssociationInfo assocInfo) { - this.assocInfo = assocInfo; - } - - protected void assignSctpAssociationId(int id) { - this.assocInfo.getPeerInfo().setSctpAssocId(id); - } - - protected boolean isConnectedToPeerAddresses(String peerAddresses) { - return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); - } - - protected PayloadData getInitPayloadData() { - return initPayloadData; - } - - protected void setInitPayloadData(PayloadData initPayloadData) { - this.initPayloadData = initPayloadData; - } - - static class PeerAddressInfo { - protected SocketAddress peerSocketAddress; - protected int sctpAssocId; - - public PeerAddressInfo(SocketAddress peerSocketAddress) { - super(); - this.peerSocketAddress = peerSocketAddress; - } - - public SocketAddress getPeerSocketAddress() { - return peerSocketAddress; - } - - public int getSctpAssocId() { - return sctpAssocId; - } - - protected void setPeerSocketAddress(SocketAddress peerSocketAddress) { - this.peerSocketAddress = peerSocketAddress; - } - - protected void setSctpAssocId(int sctpAssocId) { - this.sctpAssocId = sctpAssocId; - } - - @Override - public String toString() { - return "PeerAddressInfo [peerSocketAddress=" + peerSocketAddress - + ", sctpAssocId=" + sctpAssocId + "]"; - } - } - - static class HostAddressInfo { - private final String primaryHostAddress; - private final String secondaryHostAddress; - private final int hostPort; - - - public HostAddressInfo(String primaryHostAddress, - String secondaryHostAddress, int hostPort) { - super(); - if (primaryHostAddress == null || primaryHostAddress.isEmpty()) { - throw new IllegalArgumentException("Constructor HostAddressInfo: primaryHostAddress can not be null!"); - } - this.primaryHostAddress = primaryHostAddress; - this.secondaryHostAddress = secondaryHostAddress; - this.hostPort = hostPort; - } - public String getPrimaryHostAddress() { - return primaryHostAddress; - } - - public String getSecondaryHostAddress() { - return secondaryHostAddress; - } - - public int getHostPort() { - return hostPort; - } - - public boolean matches(HostAddressInfo hostAddressInfo) { - if (hostAddressInfo == null) { - return false; - } - if (this.hostPort != hostAddressInfo.getHostPort()) { - return false; - } - if (this.equals(hostAddressInfo)) { - return true; - } - if (this.getPrimaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) - || this.getPrimaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { - return true; - } - if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { - if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) - || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { - return true; - } - } - return false; - } - - @Override - public String toString() { - return "HostAddressInfo [primaryHostAddress=" + primaryHostAddress - + ", secondaryHostAddress=" + secondaryHostAddress - + ", hostPort=" + hostPort + "]"; - } - - } - static class AssociationInfo { - protected PeerAddressInfo peerInfo; - protected HostAddressInfo hostInfo; - public PeerAddressInfo getPeerInfo() { - return peerInfo; - } - public HostAddressInfo getHostInfo() { - return hostInfo; - } - @Override - public String toString() { - return "AssociationInfo [peerInfo=" + peerInfo + ", hostInfo=" - + hostInfo + "]"; - } - public AssociationInfo(PeerAddressInfo peerInfo, - HostAddressInfo hostInfo) { - super(); - this.peerInfo = peerInfo; - this.hostInfo = hostInfo; - } - protected void setPeerInfo(PeerAddressInfo peerInfo) { - this.peerInfo = peerInfo; - } - protected void setHostInfo(HostAddressInfo hostInfo) { - this.hostInfo = hostInfo; - } - - } - - /** - * XML Serialization/Deserialization - */ - protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( - ManageableAssociation.class) { - - @Override - public void read(javolution.xml.XMLFormat.InputElement xml, ManageableAssociation association) - throws XMLStreamException { - association.name = xml.getAttribute(NAME, ""); - association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); - association.hostPort = xml.getAttribute(HOST_PORT, 0); - - association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); - association.peerPort = xml.getAttribute(PEER_PORT, 0); - - - int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); - association.extraHostAddresses = new String[extraHostAddressesSize]; - - for (int i = 0; i < extraHostAddressesSize; i++) { - association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); - } - - } - - @Override - public void write(ManageableAssociation association, javolution.xml.XMLFormat.OutputElement xml) - throws XMLStreamException { - xml.setAttribute(NAME, association.name); - //xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); - xml.setAttribute(HOST_ADDRESS, association.hostAddress); - xml.setAttribute(HOST_PORT, association.hostPort); - - xml.setAttribute(PEER_ADDRESS, association.peerAddress); - xml.setAttribute(PEER_PORT, association.peerPort); - - xml.setAttribute(SERVER_NAME,""); - //xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); - - xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, - association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); - if (association.extraHostAddresses != null) { - for (String s : association.extraHostAddresses) { - xml.add(s, EXTRA_HOST_ADDRESS, String.class); - } - } - } - }; + protected static final Logger logger = Logger.getLogger(ManageableAssociation.class.getName()); + + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String SECONDARY_PEER_ADDRESS = "secondaryPeerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + + protected MultiManagementImpl management; + protected String hostAddress; + protected int hostPort; + protected String peerAddress; + protected int peerPort; + protected String name; + protected String[] extraHostAddresses; + protected String secondaryPeerAddress; + protected AssociationInfo assocInfo; + + /** + * If association can't start it tries to send INIT to the secondary peer address. + * It alternates between the two peer addresses until the connection is established. + */ + protected SocketAddress initSocketAddress; + protected SocketAddress primaryPeerSocketAddress; + protected SocketAddress secondaryPeerSocketAddress; + + // payload data used in the first dummy message which initiate the connect procedure + protected PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); + + /** + * This is used only for SCTP This is the socket address. If the Association has multihome support and if peer address + * changes, this variable is set to new value so new messages are now sent to changed peer address + */ + protected volatile SocketAddress peerSocketAddress = null; + + protected abstract void start() throws Exception; + + protected abstract void stop() throws Exception; + + protected abstract AbstractSelectableChannel getSocketChannel(); + + protected abstract void close(); + + protected abstract void reconnect(); + + protected abstract boolean writePayload(PayloadData payloadData, boolean initMsg); + + protected abstract void readPayload(PayloadData payloadData); + + protected ManageableAssociation() { + + } + + protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses) throws IOException { + this(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses, null); + } + + protected ManageableAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses, String secondaryPeerAddress) throws IOException { + this.hostAddress = hostAddress; + this.hostPort = hostPort; + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.name = assocName; + this.extraHostAddresses = extraHostAddresses; + this.secondaryPeerAddress = secondaryPeerAddress; + initDerivedFields(); + } + + protected void initDerivedFields() throws IOException { + this.primaryPeerSocketAddress = new InetSocketAddress(InetAddress.getByName(peerAddress), peerPort); + this.peerSocketAddress = this.primaryPeerSocketAddress; + this.initSocketAddress = this.primaryPeerSocketAddress; + if (secondaryPeerAddress != null && !secondaryPeerAddress.isEmpty()) { + this.secondaryPeerSocketAddress = new InetSocketAddress(InetAddress.getByName(secondaryPeerAddress), peerPort); + } else { + this.secondaryPeerAddress = null; + } + String secondaryHostAddress = null; + if (extraHostAddresses != null && extraHostAddresses.length >= 1) { + secondaryHostAddress = extraHostAddresses[0]; + } + this.assocInfo = new AssociationInfo(new PeerAddressInfo(primaryPeerSocketAddress, secondaryPeerSocketAddress), + new HostAddressInfo(hostAddress, secondaryHostAddress, hostPort)); + } + + protected void setManagement(MultiManagementImpl management) { + this.management = management; + } + + protected AssociationInfo getAssocInfo() { + return assocInfo; + } + + protected void setAssocInfo(AssociationInfo assocInfo) { + this.assocInfo = assocInfo; + } + + protected void assignSctpAssociationId(int id) { + this.assocInfo.getPeerInfo().setSctpAssocId(id); + } + + protected boolean isConnectedToPeerAddresses(String peerAddresses) { + return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); + } + + protected PayloadData getInitPayloadData() { + return initPayloadData; + } + + protected void setInitPayloadData(PayloadData initPayloadData) { + this.initPayloadData = initPayloadData; + } + + void switchInitSocketAddress() { + if (logger.isDebugEnabled()) { + logger.debug("switchInitSocketAddress() - enter: initSocketAddress=" + this.initSocketAddress + ", secondaryPeerSocketAddress=" + secondaryPeerSocketAddress); + } + if (this.secondaryPeerSocketAddress != null) { + this.initSocketAddress = (this.initSocketAddress == this.secondaryPeerSocketAddress ? this.primaryPeerSocketAddress : this.secondaryPeerSocketAddress); + } + } + + static class PeerAddressInfo { + protected SocketAddress peerSocketAddress; + protected SocketAddress secondaryPeerSocketAddress; + protected int sctpAssocId; + + public PeerAddressInfo(SocketAddress peerSocketAddress, SocketAddress secondaryPeerSocketAddress) { + super(); + this.peerSocketAddress = peerSocketAddress; + this.secondaryPeerSocketAddress = secondaryPeerSocketAddress; + } + + public SocketAddress getPeerSocketAddress() { + return peerSocketAddress; + } + + public SocketAddress getSecondaryPeerSocketAddress() { + return secondaryPeerSocketAddress; + } + + public int getSctpAssocId() { + return sctpAssocId; + } + + protected void setPeerSocketAddress(SocketAddress peerSocketAddress) { + this.peerSocketAddress = peerSocketAddress; + } + + protected void setSecondaryPeerSocketAddress(SocketAddress secondaryPeerSocketAddress) { + this.secondaryPeerSocketAddress = secondaryPeerSocketAddress; + } + + protected void setSctpAssocId(int sctpAssocId) { + this.sctpAssocId = sctpAssocId; + } + + @Override + public String toString() { + return "PeerAddressInfo [peerSocketAddress=" + peerSocketAddress + ", secondaryPeerSocketAddress=" + + secondaryPeerSocketAddress + ", sctpAssocId=" + sctpAssocId + "]"; + } + } + + static class HostAddressInfo { + private final String primaryHostAddress; + private final String secondaryHostAddress; + private final int hostPort; + + public HostAddressInfo(String primaryHostAddress, String secondaryHostAddress, int hostPort) { + super(); + if (primaryHostAddress == null || primaryHostAddress.isEmpty()) { + throw new IllegalArgumentException("Constructor HostAddressInfo: primaryHostAddress can not be null!"); + } + this.primaryHostAddress = primaryHostAddress; + this.secondaryHostAddress = secondaryHostAddress; + this.hostPort = hostPort; + } + + public String getPrimaryHostAddress() { + return primaryHostAddress; + } + + public String getSecondaryHostAddress() { + return secondaryHostAddress; + } + + public int getHostPort() { + return hostPort; + } + + public boolean matches(HostAddressInfo hostAddressInfo) { + if (hostAddressInfo == null) { + return false; + } + if (this.hostPort != hostAddressInfo.getHostPort()) { + return false; + } + if (this.equals(hostAddressInfo)) { + return true; + } + if (this.getPrimaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getPrimaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + if (this.getSecondaryHostAddress() != null && !this.getSecondaryHostAddress().isEmpty()) { + if (this.getSecondaryHostAddress().equals(hostAddressInfo.getPrimaryHostAddress()) + || this.getSecondaryHostAddress().equals(hostAddressInfo.getSecondaryHostAddress())) { + return true; + } + } + return false; + } + + @Override + public String toString() { + return "HostAddressInfo [primaryHostAddress=" + primaryHostAddress + ", secondaryHostAddress=" + + secondaryHostAddress + ", hostPort=" + hostPort + "]"; + } + + } + + static class AssociationInfo { + protected PeerAddressInfo peerInfo; + protected HostAddressInfo hostInfo; + + public PeerAddressInfo getPeerInfo() { + return peerInfo; + } + + public HostAddressInfo getHostInfo() { + return hostInfo; + } + + @Override + public String toString() { + return "AssociationInfo [peerInfo=" + peerInfo + ", hostInfo=" + hostInfo + "]"; + } + + public AssociationInfo(PeerAddressInfo peerInfo, HostAddressInfo hostInfo) { + super(); + this.peerInfo = peerInfo; + this.hostInfo = hostInfo; + } + + protected void setPeerInfo(PeerAddressInfo peerInfo) { + this.peerInfo = peerInfo; + } + + protected void setHostInfo(HostAddressInfo hostInfo) { + this.hostInfo = hostInfo; + } + } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + ManageableAssociation.class) { + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, ManageableAssociation association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + association.secondaryPeerAddress = xml.getAttribute(SECONDARY_PEER_ADDRESS, null); + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + + } + + @Override + public void write(ManageableAssociation association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + // xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + if (association.secondaryPeerAddress != null && !association.secondaryPeerAddress.isEmpty()) { + xml.setAttribute(SECONDARY_PEER_ADDRESS, association.secondaryPeerAddress); + } + + xml.setAttribute(SERVER_NAME, ""); + // xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index f97422f..c334221 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -32,112 +32,121 @@ import com.sun.nio.sctp.ShutdownNotification; /** - * Implements NotificationHandler for the OneToManyAssocMultiplexer class. - * Its main responsibility is to delegate notifications to the NotificationHandler of the corresponding - * OneToManyAssociationImpl. + * Implements NotificationHandler for the OneToManyAssocMultiplexer class. Its main responsibility is to delegate notifications + * to the NotificationHandler of the corresponding OneToManyAssociationImpl. * - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu * */ @SuppressWarnings("restriction") class MultiAssociationHandler extends AbstractNotificationHandler { - private static final Logger logger = Logger.getLogger(MultiAssociationHandler.class); + private static final Logger logger = Logger.getLogger(MultiAssociationHandler.class); + public MultiAssociationHandler() { - public MultiAssociationHandler() { + } - } + /** + * The delegateNotificationHandling method resolves the OneToManyAssociationImpl instance which belongs to the given + * Notification and calls the handleNotification method of the resolved Association. In case the association instance can + * not be resolved the method returns the value of the defaultResult parameter. + * + * @param not - Notification that need to be delegated + * @param defaultResult - Default return value used when Association instance can not be resolved + * @param multiplexer - The OneToManyAssocMultiplexer that is attached to this MultiAssociationHandler instance + * @return - If delegation is successful it returns the return result of the handler method of the corresponding + * OneToManyAssociationImpl instance otherwise ű returns the value of the defaultResult parameter. + */ + private HandlerResult delegateNotificationHandling(Notification not, HandlerResult defaultResult, + OneToManyAssocMultiplexer multiplexer) { + ManageableAssociation mAssociation = multiplexer.resolveAssociationImpl(not.association()); - /** - * The delegateNotificationHandling method resolves the OneToManyAssociationImpl instance which belongs to the given Notification and calls the handleNotification method of the resolved Association. - * In case the association instance can not be resolved the method returns the value of the defaultResult parameter. - * - * @param not - Notification that need to be delegated - * @param defaultResult - Default return value used when Association instance can not be resolved - * @param multiplexer - The OneToManyAssocMultiplexer that is attached to this MultiAssociationHandler instance - * @return - If delegation is successful it returns the return result of the handler method of the corresponding OneToManyAssociationImpl instance otherwise ű - * returns the value of the defaultResult parameter. - */ - private HandlerResult delegateNotificationHandling(Notification not, HandlerResult defaultResult, OneToManyAssocMultiplexer multiplexer) { - ManageableAssociation mAssociation = multiplexer.resolveAssociationImpl(not.association()); - - if (mAssociation == null) { - return defaultResult; - } - if (mAssociation instanceof OneToManyAssociationImpl) { + if (mAssociation == null) { + return defaultResult; + } + if (mAssociation instanceof OneToManyAssociationImpl) { - OneToManyAssociationImpl association = (OneToManyAssociationImpl) mAssociation; - if (logger.isDebugEnabled()) { - logger.debug("Notification: "+not+" is being delagated to associationHandler: "+association.associationHandler); - } - return association.associationHandler.handleNotification(not, association); - } else if (mAssociation instanceof OneToOneAssociationImpl) { - OneToOneAssociationImpl association = (OneToOneAssociationImpl) mAssociation; - if (logger.isDebugEnabled()) { - logger.debug("Notification: "+not+" is being delagated to associationHandler: "+association.associationHandler); - } - return association.associationHandler.handleNotification(not, association); - } - logger.warn(String.format("Unexpected super type: %s", mAssociation.getClass())); - return defaultResult; - } - - @Override - public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) { - if (logger.isDebugEnabled()) { - logger.debug("handleNotification(AssociationChangeNotification=" + not + ", OneToManyAssocMultiplexer=" + multiplexer + ") is called"); - } + OneToManyAssociationImpl association = (OneToManyAssociationImpl) mAssociation; + if (logger.isDebugEnabled()) { + logger.debug("Notification: " + not + " is being delagated to associationHandler: " + + association.associationHandler); + } + return association.associationHandler.handleNotification(not, association); + } else if (mAssociation instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl association = (OneToOneAssociationImpl) mAssociation; + if (logger.isDebugEnabled()) { + logger.debug("Notification: " + not + " is being delagated to associationHandler: " + + association.associationHandler); + } + return association.associationHandler.handleNotification(not, association); + } + logger.warn(String.format("Unexpected super type: %s", mAssociation.getClass())); + return defaultResult; + } - if (not.association() == null) { - logger.error("Cannot handle AssociationChangeNotification: association method of AssociationChangeNotification: "+not+" returns null value, handler returns CONTINUE"); - return HandlerResult.CONTINUE; - } - return delegateNotificationHandling(not, HandlerResult.CONTINUE, multiplexer); - } + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(AssociationChangeNotification=" + not + ", OneToManyAssocMultiplexer=" + + multiplexer + ") is called"); + } - @Override - public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) { - if (logger.isDebugEnabled()) { - logger.debug("handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) is called"); - } - if (not.association() == null) { - logger.error("Cannot handle ShutdownNotification: assoction method of ShutdownNotification: "+not+" returns null value, handler returns RETURN"); - return HandlerResult.RETURN; - } - return delegateNotificationHandling(not, HandlerResult.RETURN, multiplexer); - } + if (not.association() == null) { + logger.error("Cannot handle AssociationChangeNotification: association method of AssociationChangeNotification: " + + not + " returns null value, handler returns CONTINUE"); + return HandlerResult.CONTINUE; + } + return delegateNotificationHandling(not, HandlerResult.CONTINUE, multiplexer); + } - @Override - public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) { - if (logger.isDebugEnabled()) { - logger.debug("handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); - } - ManageableAssociation assoc = multiplexer.findPendingAssociationByAddress(notification.address()); - if (assoc == null) { - logger.warn("Can not handle sendfailed notification: no pending manageable association found for address=" + notification.address() + " by the multiplexer"); - return HandlerResult.RETURN; - } - //delegate notification - if (assoc instanceof OneToManyAssociationImpl) { - OneToManyAssociationImpl oneToManyAssoc = (OneToManyAssociationImpl) assoc; - return oneToManyAssoc.associationHandler.handleNotification(notification, oneToManyAssoc); - } else if (assoc instanceof OneToOneAssociationImpl) { - OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) assoc; - return oneToOneAssoc.associationHandler.handleNotification(notification, oneToOneAssoc); - } - return HandlerResult.RETURN; - } + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug("handleNotification(ShutdownNotification not, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (not.association() == null) { + logger.error("Cannot handle ShutdownNotification: assoction method of ShutdownNotification: " + not + + " returns null value, handler returns RETURN"); + return HandlerResult.RETURN; + } + return delegateNotificationHandling(not, HandlerResult.RETURN, multiplexer); + } - @Override - public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) { - if (logger.isDebugEnabled()) { - logger.debug("handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); - } - if (notification.association() == null) { - logger.error("Cannot handle PeerAddressChangeNotification: assoction method of PeerAddressChangeNotification: "+notification+" returns null value, handler returns RETURN"); - return HandlerResult.RETURN; - } - return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); - } + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug( + "handleNotification(SendFailedNotification notification, OneToManyAssocMultiplexer multiplexer) is called not=" + notification + " multiplexer=" + multiplexer); + } + ManageableAssociation assoc = multiplexer.findPendingAssociationByAddress(notification.address()); + if (assoc == null) { + logger.warn("Can not handle sendfailed notification: no pending manageable association found for address=" + + notification.address() + " by the multiplexer"); + return HandlerResult.RETURN; + } + // delegate notification + if (assoc instanceof OneToManyAssociationImpl) { + OneToManyAssociationImpl oneToManyAssoc = (OneToManyAssociationImpl) assoc; + return oneToManyAssoc.associationHandler.handleNotification(notification, oneToManyAssoc); + } else if (assoc instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) assoc; + return oneToOneAssoc.associationHandler.handleNotification(notification, oneToOneAssoc); + } + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) { + if (logger.isDebugEnabled()) { + logger.debug( + "handleNotification(PeerAddressChangeNotification notification, OneToManyAssocMultiplexer multiplexer) is called"); + } + if (notification.association() == null) { + logger.error("Cannot handle PeerAddressChangeNotification: assoction method of PeerAddressChangeNotification: " + + notification + " returns null value, handler returns RETURN"); + return HandlerResult.RETURN; + } + return delegateNotificationHandling(notification, HandlerResult.RETURN, multiplexer); + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java index 7d097be..0c3c633 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChangeRequest.java @@ -29,110 +29,112 @@ * */ public final class MultiChangeRequest { - public static final int REGISTER = 1; - public static final int CHANGEOPS = 2; - public static final int CONNECT = 3; - public static final int CLOSE = 4; - public static final int ADD_OPS = 5; - - private final int type; - private final int ops; - private final AbstractSelectableChannel socketChannel; - private final OneToManyAssocMultiplexer assocMultiplexer; - private final ManageableAssociation association; - - private final boolean multiAssocRequest; - - private long executionTime; - - protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, ManageableAssociation association, int type, int ops) { - if (assocMultiplexer != null && association != null) { - throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: both assocMultiplexer and association are specified!"); - } - if (assocMultiplexer == null && association == null) { - throw new IllegalArgumentException("MultiChangeRequest can not be instatiated because of ambiougos arguments: nor assocMultiplexer nor association are specified!"); - } - this.type = type; - this.ops = ops; - - if (assocMultiplexer != null) { - this.assocMultiplexer = assocMultiplexer; - this.association = null; - this.multiAssocRequest = true; - if (socketChannel == null) { - this.socketChannel = assocMultiplexer.getSocketMultiChannel(); - } else { - this.socketChannel = socketChannel; - } - } else { - this.association = association; - this.assocMultiplexer = null; - this.multiAssocRequest = false; - if (socketChannel == null) { - this.socketChannel = association.getSocketChannel(); - } else { - this.socketChannel = socketChannel; - } - } - } - - protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, ManageableAssociation association, int type, long executionTime) { - this(null, assocMultiplexer, association, type, -1); - this.executionTime = executionTime; - } - - /** - * @return the type - */ - protected int getType() { - return type; - } - - /** - * @return the ops - */ - protected int getOps() { - return ops; - } - - /** - * @return the socketChannel - */ - protected AbstractSelectableChannel getSocketChannel() { - return socketChannel; - } - - /** - * @return the one-to-many multiplexer instance - */ - protected OneToManyAssocMultiplexer getAssocMultiplexer() { - return assocMultiplexer; - } - - /** - * @return the one-to-one association - */ - protected ManageableAssociation getAssociation() { - return association; - } - - protected boolean isMultiAssocRequest() { - return multiAssocRequest; - } - - /** - * @return the executionTime - */ - protected long getExecutionTime() { - return executionTime; - } - - @Override - public String toString() { - return "MultiChangeRequest [type=" + type + ", ops=" + ops - + ", socketChannel=" + socketChannel + ", assocMultiplexer=" - + assocMultiplexer + ", oneToOneAssoc=" + association - + ", multiAssocRequest=" + multiAssocRequest - + ", executionTime=" + executionTime + "]"; - } + public static final int REGISTER = 1; + public static final int CHANGEOPS = 2; + public static final int CONNECT = 3; + public static final int CLOSE = 4; + public static final int ADD_OPS = 5; + + private final int type; + private final int ops; + private final AbstractSelectableChannel socketChannel; + private final OneToManyAssocMultiplexer assocMultiplexer; + private final ManageableAssociation association; + + private final boolean multiAssocRequest; + + private long executionTime; + + protected MultiChangeRequest(AbstractSelectableChannel socketChannel, OneToManyAssocMultiplexer assocMultiplexer, + ManageableAssociation association, int type, int ops) { + if (assocMultiplexer != null && association != null) { + throw new IllegalArgumentException( + "MultiChangeRequest can not be instatiated because of ambiougos arguments: both assocMultiplexer and association are specified!"); + } + if (assocMultiplexer == null && association == null) { + throw new IllegalArgumentException( + "MultiChangeRequest can not be instatiated because of ambiougos arguments: nor assocMultiplexer nor association are specified!"); + } + this.type = type; + this.ops = ops; + + if (assocMultiplexer != null) { + this.assocMultiplexer = assocMultiplexer; + this.association = null; + this.multiAssocRequest = true; + if (socketChannel == null) { + this.socketChannel = assocMultiplexer.getSocketMultiChannel(); + } else { + this.socketChannel = socketChannel; + } + } else { + this.association = association; + this.assocMultiplexer = null; + this.multiAssocRequest = false; + if (socketChannel == null) { + this.socketChannel = association.getSocketChannel(); + } else { + this.socketChannel = socketChannel; + } + } + } + + protected MultiChangeRequest(OneToManyAssocMultiplexer assocMultiplexer, ManageableAssociation association, int type, + long executionTime) { + this(null, assocMultiplexer, association, type, -1); + this.executionTime = executionTime; + } + + /** + * @return the type + */ + protected int getType() { + return type; + } + + /** + * @return the ops + */ + protected int getOps() { + return ops; + } + + /** + * @return the socketChannel + */ + protected AbstractSelectableChannel getSocketChannel() { + return socketChannel; + } + + /** + * @return the one-to-many multiplexer instance + */ + protected OneToManyAssocMultiplexer getAssocMultiplexer() { + return assocMultiplexer; + } + + /** + * @return the one-to-one association + */ + protected ManageableAssociation getAssociation() { + return association; + } + + protected boolean isMultiAssocRequest() { + return multiAssocRequest; + } + + /** + * @return the executionTime + */ + protected long getExecutionTime() { + return executionTime; + } + + @Override + public String toString() { + return "MultiChangeRequest [type=" + type + ", ops=" + ops + ", socketChannel=" + socketChannel + ", assocMultiplexer=" + + assocMultiplexer + ", oneToOneAssoc=" + association + ", multiAssocRequest=" + multiAssocRequest + + ", executionTime=" + executionTime + "]"; + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index b359195..a6da521 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -57,710 +57,730 @@ import org.mobicents.protocols.sctp.AssociationMap; /** - * This class is a partial implementation of the Management interface of the Mobicents SCTP-API. - * It is partial because it does not support the whole functionality of the interface instead - * it extends the capabilities of the implementation provided by org.mobicents.protocols.sctp package - * with the capability to use One-To-Many type SCTP client associations. + * This class is a partial implementation of the Management interface of the Mobicents SCTP-API. It is partial because it does + * not support the whole functionality of the interface instead it extends the capabilities of the implementation provided by + * org.mobicents.protocols.sctp package with the capability to use One-To-Many type SCTP client associations. * - * Therefore the following functionality is not supported by this class: - * server type associations - * TCP ipChannelType + * Therefore the following functionality is not supported by this class: server type associations TCP ipChannelType * * @author amit bhayani - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu */ public class MultiManagementImpl implements Management { - private static final Logger logger = Logger.getLogger(MultiManagementImpl.class); + private static final Logger logger = Logger.getLogger(MultiManagementImpl.class); + + private static final String DISABLE_CONFIG_PERSISTANCE_KEY = "ss7.disableDefaultConfigPersistance"; + private static final String ENABLE_SCTP_ASSOC_BRANCHING = "sctp.enableBranching"; + private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; + private static final String USER_DIR_KEY = "user.dir"; + private static final String PERSIST_FILE_NAME = "sctp.xml"; + private static final String ASSOCIATIONS = "associations"; - private static final String DISABLE_CONFIG_PERSISTANCE_KEY = "ss7.disableDefaultConfigPersistance"; - private static final String ENABLE_SCTP_ASSOC_BRANCHING = "sctp.enableBranching"; - private static final String SCTP_PERSIST_DIR_KEY = "sctp.persist.dir"; - private static final String USER_DIR_KEY = "user.dir"; - private static final String PERSIST_FILE_NAME = "sctp.xml"; - private static final String ASSOCIATIONS = "associations"; - private static final String CONNECT_DELAY_PROP = "connectdelay"; - private final TextBuilder persistFile = TextBuilder.newInstance(); - - protected final MultiSctpXMLBinding binding; - protected static final String TAB_INDENT = "\t"; - private static final String CLASS_ATTRIBUTE = "type"; - private static final AtomicInteger WORKER_POOL_INDEX = new AtomicInteger(0); - - private final String name; - - protected String persistDir = null; - - protected AssociationMap associations = new AssociationMap(); - - private FastList pendingChanges = new FastList(); - - // Create a new selector - private Selector socketSelector = null; - - private MultiSelectorThread selectorThread = null; - - static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2; - - private int workerThreads = DEFAULT_IO_THREADS; - - private boolean singleThread = true; - - private int workerThreadCount = 0; - - // Maximum IO Errors tolerated by Socket. After this the Socket will be - // closed and attempt will be made to open again - private int maxIOErrors = 3; - - private int connectDelay = 5000; - - private ExecutorService[] executorServices = null; - - private FastList managementEventListeners = new FastList(); - - private volatile boolean started = false; - - private final MultiChannelController multiChannelController = new MultiChannelController(this); - - private boolean enableBranching; - - //default value of the dummy message sent to initiate the SCTP connection - private PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); - - public MultiManagementImpl(String name) throws IOException { - this.name = name; - String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "true"); - this.enableBranching = Boolean.valueOf(enableBranchingString); - this.binding = new MultiSctpXMLBinding(enableBranching); - this.binding.setClassAttribute(CLASS_ATTRIBUTE); - if (enableBranching) { - this.binding.setAlias(OneToManyAssociationImpl.class, "association"); - } else { - this.binding.setAlias(OneToOneAssociationImpl.class, "association"); - } - this.binding.setAlias(String.class, "string"); - this.socketSelector = SelectorProvider.provider().openSelector(); - - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - public String getPersistDir() { - return persistDir; - } - - public void setPersistDir(String persistDir) { - this.persistDir = persistDir; - } - - /** - * @return the connectDelay - */ - public int getConnectDelay() { - return connectDelay; - } - - /** - * @param connectDelay - * the connectDelay to set - */ - public void setConnectDelay(int connectDelay) throws Exception { - if (!this.started) - throw new Exception("ConnectDelay parameter can be updated only when SCTP stack is running"); - - this.connectDelay = connectDelay; - - this.store(); - } - - /** - * @return the workerThreads - */ - public int getWorkerThreads() { - return workerThreads; - } - - /** - * @param workerThreads - * the workerThreads to set - */ - public void setWorkerThreads(int workerThreads) throws Exception { - if (this.started) - throw new Exception("WorkerThreads parameter can be updated only when SCTP stack is NOT running"); - - if (workerThreads < 1) { - workerThreads = DEFAULT_IO_THREADS; - } - this.workerThreads = workerThreads; - } - - /** - * @return the maxIOErrors - */ - public int getMaxIOErrors() { - return maxIOErrors; - } - - /** - * @param maxIOErrors - * the maxIOErrors to set - */ - public void setMaxIOErrors(int maxIOErrors) { - if (maxIOErrors < 1) { - maxIOErrors = 1; - } - this.maxIOErrors = maxIOErrors; - } - - /** - * @return the singleThread - */ - public boolean isSingleThread() { - return singleThread; - } - - /** - * @param singleThread - * the singleThread to set - */ - public void setSingleThread(boolean singleThread) throws Exception { - if (this.started) - throw new Exception("SingleThread parameter can be updated only when SCTP stack is NOT running"); - - this.singleThread = singleThread; - } - - protected FastList getManagementEventListeners() { - return managementEventListeners; - } - - public void addManagementEventListener(ManagementEventListener listener) { - synchronized (this) { - if (this.managementEventListeners.contains(listener)) - return; - - FastList newManagementEventListeners = new FastList(); - newManagementEventListeners.addAll(this.managementEventListeners); - newManagementEventListeners.add(listener); - this.managementEventListeners = newManagementEventListeners; - } - } - - public void removeManagementEventListener(ManagementEventListener listener) { - synchronized (this) { - if (!this.managementEventListeners.contains(listener)) - return; - - FastList newManagementEventListeners = new FastList(); - newManagementEventListeners.addAll(this.managementEventListeners); - newManagementEventListeners.remove(listener); - this.managementEventListeners = newManagementEventListeners; - } - } - - protected MultiChannelController getMultiChannelController() { - return multiChannelController; - } - - - public boolean isInBranchingMode() { - return enableBranching; - } - - public void start() throws Exception { - - if (this.started) { - logger.warn(String.format("management=%s is already started", this.name)); - return; - } - - synchronized (this) { - this.persistFile.clear(); - - if (persistDir != null) { - this.persistFile.append(persistDir).append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME); - } else { - persistFile.append(System.getProperty(SCTP_PERSIST_DIR_KEY, System.getProperty(USER_DIR_KEY))).append(File.separator).append(this.name) - .append("_").append(PERSIST_FILE_NAME); - } - - logger.info(String.format("SCTP configuration file path %s", persistFile.toString())); - - try { - this.load(); - } catch (FileNotFoundException e) { - logger.warn(String.format("Failed to load the SCTP configuration file. \n%s", e.getMessage())); - } - - if (!this.singleThread) { - // If not single thread model we create worker threads - this.executorServices = new ExecutorService[this.workerThreads]; - for (int i = 0; i < this.workerThreads; i++) { - this.executorServices[i] = Executors.newSingleThreadExecutor(new NamingThreadFactory("SCTP-" + WORKER_POOL_INDEX.incrementAndGet())); - } - } - this.socketSelector = SelectorProvider.provider().openSelector(); - this.selectorThread = new MultiSelectorThread(this.socketSelector, this); - this.selectorThread.setStarted(true); - - (new Thread(this.selectorThread, "SCTP-selector")).start(); - - this.started = true; - - if (logger.isInfoEnabled()) { - logger.info(String.format("Started SCTP Management=%s WorkerThreads=%d SingleThread=%s", this.name, - (this.singleThread ? 0 : this.workerThreads), this.singleThread)); - } - - for (ManagementEventListener lstr : managementEventListeners) { - try { - lstr.onServiceStarted(); - } catch (Throwable ee) { - logger.error("Exception while invoking onServiceStarted", ee); - } - } - } - } - - public void stop() throws Exception { - - if (!this.started) { - logger.warn(String.format("management=%s is already stopped", this.name)); - return; - } - - for (ManagementEventListener lstr : managementEventListeners) { - try { - lstr.onServiceStopped(); - } catch (Throwable ee) { - logger.error("Exception while invoking onServiceStopped", ee); - } - } - - // We store the original state first - this.store(); - - multiChannelController.stopAllMultiplexers(); - // Stop all associations - /*FastMap associationsTemp = this.associations; - for (FastMap.Entry n = associationsTemp.head(), end = associationsTemp.tail(); (n = n.getNext()) != end;) { - Association associationTemp = n.getValue(); - if (associationTemp.isStarted()) { - ((OneToManyAssociationImpl) associationTemp).stop(); - } - }*/ - - if (this.executorServices != null) { - for (int i = 0; i < this.executorServices.length; i++) { - this.executorServices[i].shutdown(); - } - } - - this.selectorThread.setStarted(false); - this.socketSelector.wakeup(); // Wakeup selector so SelectorThread dies - - // waiting till stopping associations - for (int i1 = 0; i1 < 20; i1++) { - boolean assConnected = false; - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - Association associationTemp = n.getValue(); - if (associationTemp.isConnected()) { - assConnected = true; - break; - } - } - if (!assConnected) - break; - Thread.sleep(100); - } - - // Graceful shutdown for each of Executors - if (this.executorServices != null) { - for (int i = 0; i < this.executorServices.length; i++) { - if (!this.executorServices[i].isTerminated()) { - if (logger.isInfoEnabled()) { - logger.info("Waiting for worker thread to die gracefully ...."); - } - try { - this.executorServices[i].awaitTermination(5000, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - // Do we care? - } - } - } - } - - this.started = false; - } - - public boolean isStarted(){ - return this.started; - } - private boolean isConfigPersistanceDisabled() { - String disableConfigPersistanceString = System.getProperty(DISABLE_CONFIG_PERSISTANCE_KEY, "false"); - return Boolean.valueOf(disableConfigPersistanceString); - } - - @SuppressWarnings("unchecked") - public void load() throws FileNotFoundException { - if (isConfigPersistanceDisabled()) { - return; - } - XMLObjectReader reader = null; - try { - reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); - reader.setBinding(binding); - - try { - this.connectDelay = reader.read(CONNECT_DELAY_PROP, Integer.class); - } catch (java.lang.NullPointerException npe) { - // ignore. - // For backward compatibility we can ignore if these values are not defined - } - - this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); - - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - n.getValue().setManagement(this); - } - } catch (XMLStreamException ex) { - logger.error("Error while re-creating Linksets from persisted file", ex); - } - } - - public void store() { - if (isConfigPersistanceDisabled()) { - return; - } - try { - XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString())); - writer.setBinding(binding); - // Enables cross-references. - // writer.setReferenceResolver(new XMLReferenceResolver()); - writer.setIndentation(TAB_INDENT); - - writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class); - - writer.write(this.associations, ASSOCIATIONS, AssociationMap.class); - - writer.close(); - } catch (Exception e) { - logger.error("Error while persisting the Rule state in file", e); - } - } - - public void removeAllResourses() throws Exception { - - synchronized (this) { - if (!this.started) { - throw new Exception(String.format("Management=%s not started", this.name)); - } - - if (this.associations.size() == 0) - // no resources allocated - nothing to do - return; - - if (logger.isInfoEnabled()) { - logger.info(String.format("Removing allocated resources: Associations=%d", this.associations.size())); - } - - synchronized (this) { - - // Remove all associations - ArrayList lst = new ArrayList(); - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - lst.add(n.getKey()); - } - for (String n : lst) { - this.stopAssociation(n); - this.removeAssociation(n); - } - - // We store the cleared state - this.store(); - } - - for (ManagementEventListener lstr : managementEventListeners) { - try { - lstr.onRemoveAllResources(); - } catch (Throwable ee) { - logger.error("Exception while invoking onRemoveAllResources", ee); - } - } - } - } - - - public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName) throws Exception { - return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, null); - } - - public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, IpChannelType ipChannelType, - String[] extraHostAddresses) throws Exception { - - if (!this.started) { - throw new Exception(String.format("Management=%s not started", this.name)); - } - - if (hostAddress == null) { - throw new Exception("Host address cannot be null"); - } - - if (hostPort < 0) { - throw new Exception("Host port cannot be less than 0"); - } - - if (peerAddress == null) { - throw new Exception("Peer address cannot be null"); - } - - if (peerPort < 1) { - throw new Exception("Peer port cannot be less than 1"); - } - - if (assocName == null) { - throw new Exception("Association name cannot be null"); - } - - synchronized (this) { - for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { - Association associationTemp = n.getValue(); - - if (assocName.equals(associationTemp.getName())) { - throw new Exception(String.format("Already has association=%s", associationTemp.getName())); - } - - } - ManageableAssociation association = null; - if (isInBranchingMode()) { - association = new OneToOneAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); - } else { - association = new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); - } - - association.setManagement(this); - association.setInitPayloadData(initPayloadData); - - AssociationMap newAssociations = new AssociationMap(); - newAssociations.putAll(this.associations); - newAssociations.put(assocName, association); - this.associations = newAssociations; - - this.store(); - - for (ManagementEventListener lstr : managementEventListeners) { - try { - lstr.onAssociationAdded(association); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationAdded", ee); - } - } - - if (logger.isInfoEnabled()) { - logger.info(String.format("Added Associoation=%s of type=%s", association.getName(), association.getAssociationType())); - } - - return association; - } - } - - public Association getAssociation(String assocName) throws Exception { - if (assocName == null) { - throw new Exception("Association name cannot be null"); - } - Association associationTemp = this.associations.get(assocName); - - if (associationTemp == null) { - throw new Exception(String.format("No Association found for name=%s", assocName)); - } - return associationTemp; - } - - /** - * @return the associations - */ - public Map getAssociations() { - Map routeTmp = new HashMap(); - routeTmp.putAll(this.associations); - return routeTmp; - } - - public void startAssociation(String assocName) throws Exception { - if (!this.started) { - throw new Exception(String.format("Management=%s not started", this.name)); - } - - if (assocName == null) { - throw new Exception("Association name cannot be null"); - } - - ManageableAssociation association = this.associations.get(assocName); - - if (association == null) { - throw new Exception(String.format("No Association found for name=%s", assocName)); - } - - if (association.isStarted()) { - throw new Exception(String.format("Association=%s is already started", assocName)); - } - - association.start(); - this.store(); - } - - public void stopAssociation(String assocName) throws Exception { - if (!this.started) { - throw new Exception(String.format("Management=%s not started", this.name)); - } - - if (assocName == null) { - throw new Exception("Association name cannot be null"); - } - - ManageableAssociation association = this.associations.get(assocName); - - if (association == null) { - throw new Exception(String.format("No Association found for name=%s", assocName)); - } - - association.stop(); - this.store(); - } - - public void removeAssociation(String assocName) throws Exception { - if (!this.started) { - throw new Exception(String.format("Management=%s not started", this.name)); - } - - if (assocName == null) { - throw new Exception("Association name cannot be null"); - } - - synchronized (this) { - Association association = this.associations.get(assocName); - - if (association == null) { - throw new Exception(String.format("No Association found for name=%s", assocName)); - } - - if (association.isStarted()) { - throw new Exception(String.format("Association name=%s is started. Stop before removing", assocName)); - } - - AssociationMap newAssociations = new AssociationMap(); - newAssociations.putAll(this.associations); - newAssociations.remove(assocName); - this.associations = newAssociations; - - this.store(); - - for (ManagementEventListener lstr : managementEventListeners) { - try { - lstr.onAssociationRemoved(association); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationRemoved", ee); - } - } - } - } - - public PayloadData getInitPayloadData() { - return initPayloadData; - } - - public void setInitPayloadData(PayloadData initPayloadData) { - this.initPayloadData = initPayloadData; - } - - /** - * @return the pendingChanges - */ - protected FastList getPendingChanges() { - return pendingChanges; - } - - /** - * @return the socketSelector - */ - protected Selector getSocketSelector() { - return socketSelector; - } - - protected void populateWorkerThread(int workerThreadTable[]) { - for (int count = 0; count < workerThreadTable.length; count++) { - if (this.workerThreadCount == this.workerThreads) { - this.workerThreadCount = 0; - } - - workerThreadTable[count] = this.workerThreadCount; - this.workerThreadCount++; - } - } - - protected ExecutorService getExecutorService(int index) { - return this.executorServices[index]; - } - - /*unimplemented management methods*/ - @Override - public Server addServer(String serverName, String hostAddress, int port) - throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - - @Override - public Server addServer(String serverName, String hostAddress, int port, - IpChannelType ipChannelType, boolean acceptAnonymousConnections, - int maxConcurrentConnectionsCount, String[] extraHostAddresses) - throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public Server addServer(String serverName, String hostAddress, int port, - IpChannelType ipChannelType, String[] extraHostAddresses) - throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public Association addServerAssociation(String peerAddress, int peerPort, - String serverName, String assocName) throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public Association addServerAssociation(String peerAddress, int peerPort, - String serverName, String assocName, IpChannelType ipChannelType) - throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not support server type associations!"); - } - @Override - public ServerListener getServerListener() { - return null; - } - @Override - public List getServers() { - return Collections.emptyList(); - } - @Override - public void removeServer(String serverName) throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public void setServerListener(ServerListener serverListener) { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public void startServer(String serverName) throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } - @Override - public void stopServer(String serverName) throws Exception { - throw new UnsupportedOperationException(MultiManagementImpl.class.getName()+" does not implement server functionality!"); - } + private final TextBuilder persistFile = TextBuilder.newInstance(); + + protected final MultiSctpXMLBinding binding; + protected static final String TAB_INDENT = "\t"; + private static final String CLASS_ATTRIBUTE = "type"; + private static final AtomicInteger WORKER_POOL_INDEX = new AtomicInteger(0); + + private final String name; + + protected String persistDir = null; + + protected AssociationMap associations = new AssociationMap(); + + private FastList pendingChanges = new FastList(); + + // Create a new selector + private Selector socketSelector = null; + + private MultiSelectorThread selectorThread = null; + + static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2; + + private int workerThreads = DEFAULT_IO_THREADS; + + private boolean singleThread = true; + + private int workerThreadCount = 0; + + // Maximum IO Errors tolerated by Socket. After this the Socket will be + // closed and attempt will be made to open again + private int maxIOErrors = 3; + + private int connectDelay = 5000; + + private ExecutorService[] executorServices = null; + + private FastList managementEventListeners = new FastList(); + + private volatile boolean started = false; + + private final MultiChannelController multiChannelController = new MultiChannelController(this); + + private boolean enableBranching; + + // default value of the dummy message sent to initiate the SCTP connection + private PayloadData initPayloadData = new PayloadData(0, new byte[1], true, false, 0, 0); + + public MultiManagementImpl(String name) throws IOException { + this.name = name; + String enableBranchingString = System.getProperty(ENABLE_SCTP_ASSOC_BRANCHING, "true"); + this.enableBranching = Boolean.valueOf(enableBranchingString); + this.binding = new MultiSctpXMLBinding(enableBranching); + this.binding.setClassAttribute(CLASS_ATTRIBUTE); + if (enableBranching) { + this.binding.setAlias(OneToManyAssociationImpl.class, "association"); + } else { + this.binding.setAlias(OneToOneAssociationImpl.class, "association"); + } + this.binding.setAlias(String.class, "string"); + this.socketSelector = SelectorProvider.provider().openSelector(); + + } + + /** + * @return the name + */ + public String getName() { + return name; + } + + public String getPersistDir() { + return persistDir; + } + + public void setPersistDir(String persistDir) { + this.persistDir = persistDir; + } + + /** + * @return the connectDelay + */ + public int getConnectDelay() { + return connectDelay; + } + + /** + * @param connectDelay the connectDelay to set + */ + public void setConnectDelay(int connectDelay) throws Exception { + if (!this.started) + throw new Exception("ConnectDelay parameter can be updated only when SCTP stack is running"); + + this.connectDelay = connectDelay; + + this.store(); + } + + /** + * @return the workerThreads + */ + public int getWorkerThreads() { + return workerThreads; + } + + /** + * @param workerThreads the workerThreads to set + */ + public void setWorkerThreads(int workerThreads) throws Exception { + if (this.started) + throw new Exception("WorkerThreads parameter can be updated only when SCTP stack is NOT running"); + + if (workerThreads < 1) { + workerThreads = DEFAULT_IO_THREADS; + } + this.workerThreads = workerThreads; + } + + /** + * @return the maxIOErrors + */ + public int getMaxIOErrors() { + return maxIOErrors; + } + + /** + * @param maxIOErrors the maxIOErrors to set + */ + public void setMaxIOErrors(int maxIOErrors) { + if (maxIOErrors < 1) { + maxIOErrors = 1; + } + this.maxIOErrors = maxIOErrors; + } + + /** + * @return the singleThread + */ + public boolean isSingleThread() { + return singleThread; + } + + /** + * @param singleThread the singleThread to set + */ + public void setSingleThread(boolean singleThread) throws Exception { + if (this.started) + throw new Exception("SingleThread parameter can be updated only when SCTP stack is NOT running"); + + this.singleThread = singleThread; + } + + protected FastList getManagementEventListeners() { + return managementEventListeners; + } + + public void addManagementEventListener(ManagementEventListener listener) { + synchronized (this) { + if (this.managementEventListeners.contains(listener)) + return; + + FastList newManagementEventListeners = new FastList(); + newManagementEventListeners.addAll(this.managementEventListeners); + newManagementEventListeners.add(listener); + this.managementEventListeners = newManagementEventListeners; + } + } + + public void removeManagementEventListener(ManagementEventListener listener) { + synchronized (this) { + if (!this.managementEventListeners.contains(listener)) + return; + + FastList newManagementEventListeners = new FastList(); + newManagementEventListeners.addAll(this.managementEventListeners); + newManagementEventListeners.remove(listener); + this.managementEventListeners = newManagementEventListeners; + } + } + + protected MultiChannelController getMultiChannelController() { + return multiChannelController; + } + + public boolean isInBranchingMode() { + return enableBranching; + } + + public void start() throws Exception { + + if (this.started) { + logger.warn(String.format("management=%s is already started", this.name)); + return; + } + + synchronized (this) { + this.persistFile.clear(); + + if (persistDir != null) { + this.persistFile.append(persistDir).append(File.separator).append(this.name).append("_") + .append(PERSIST_FILE_NAME); + } else { + persistFile.append(System.getProperty(SCTP_PERSIST_DIR_KEY, System.getProperty(USER_DIR_KEY))) + .append(File.separator).append(this.name).append("_").append(PERSIST_FILE_NAME); + } + + logger.info(String.format("SCTP configuration file path %s", persistFile.toString())); + + try { + this.load(); + } catch (FileNotFoundException e) { + logger.warn(String.format("Failed to load the SCTP configuration file. \n%s", e.getMessage())); + } + + if (!this.singleThread) { + // If not single thread model we create worker threads + this.executorServices = new ExecutorService[this.workerThreads]; + for (int i = 0; i < this.workerThreads; i++) { + this.executorServices[i] = Executors + .newSingleThreadExecutor(new NamingThreadFactory("SCTP-" + WORKER_POOL_INDEX.incrementAndGet())); + } + } + this.socketSelector = SelectorProvider.provider().openSelector(); + this.selectorThread = new MultiSelectorThread(this.socketSelector, this); + this.selectorThread.setStarted(true); + + (new Thread(this.selectorThread, "SCTP-selector")).start(); + + this.started = true; + + if (logger.isInfoEnabled()) { + logger.info(String.format("Started SCTP Management=%s WorkerThreads=%d SingleThread=%s", this.name, + (this.singleThread ? 0 : this.workerThreads), this.singleThread)); + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onServiceStarted(); + } catch (Throwable ee) { + logger.error("Exception while invoking onServiceStarted", ee); + } + } + } + } + + public void stop() throws Exception { + + if (!this.started) { + logger.warn(String.format("management=%s is already stopped", this.name)); + return; + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onServiceStopped(); + } catch (Throwable ee) { + logger.error("Exception while invoking onServiceStopped", ee); + } + } + + // We store the original state first + this.store(); + + multiChannelController.stopAllMultiplexers(); + // Stop all associations + /* + * FastMap associationsTemp = this.associations; for (FastMap.Entry n = + * associationsTemp.head(), end = associationsTemp.tail(); (n = n.getNext()) != end;) { Association associationTemp = + * n.getValue(); if (associationTemp.isStarted()) { ((OneToManyAssociationImpl) associationTemp).stop(); } } + */ + + if (this.executorServices != null) { + for (int i = 0; i < this.executorServices.length; i++) { + this.executorServices[i].shutdown(); + } + } + + this.selectorThread.setStarted(false); + this.socketSelector.wakeup(); // Wakeup selector so SelectorThread dies + + // waiting till stopping associations + for (int i1 = 0; i1 < 20; i1++) { + boolean assConnected = false; + for (FastMap.Entry n = this.associations.head(), end = this.associations + .tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + if (associationTemp.isConnected()) { + assConnected = true; + break; + } + } + if (!assConnected) + break; + Thread.sleep(100); + } + + // Graceful shutdown for each of Executors + if (this.executorServices != null) { + for (int i = 0; i < this.executorServices.length; i++) { + if (!this.executorServices[i].isTerminated()) { + if (logger.isInfoEnabled()) { + logger.info("Waiting for worker thread to die gracefully ...."); + } + try { + this.executorServices[i].awaitTermination(5000, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + // Do we care? + } + } + } + } + + this.started = false; + } + + public boolean isStarted() { + return this.started; + } + + private boolean isConfigPersistanceDisabled() { + String disableConfigPersistanceString = System.getProperty(DISABLE_CONFIG_PERSISTANCE_KEY, "false"); + return Boolean.valueOf(disableConfigPersistanceString); + } + + @SuppressWarnings("unchecked") + public void load() throws FileNotFoundException { + if (isConfigPersistanceDisabled()) { + return; + } + XMLObjectReader reader = null; + try { + reader = XMLObjectReader.newInstance(new FileInputStream(persistFile.toString())); + reader.setBinding(binding); + + try { + this.connectDelay = reader.read(CONNECT_DELAY_PROP, Integer.class); + } catch (java.lang.NullPointerException npe) { + // ignore. + // For backward compatibility we can ignore if these values are not defined + } + + this.associations = reader.read(ASSOCIATIONS, AssociationMap.class); + + for (FastMap.Entry n = this.associations.head(), end = this.associations + .tail(); (n = n.getNext()) != end;) { + n.getValue().setManagement(this); + } + } catch (XMLStreamException ex) { + logger.error("Error while re-creating Linksets from persisted file", ex); + } + } + + public void store() { + if (isConfigPersistanceDisabled()) { + return; + } + try { + XMLObjectWriter writer = XMLObjectWriter.newInstance(new FileOutputStream(persistFile.toString())); + writer.setBinding(binding); + // Enables cross-references. + // writer.setReferenceResolver(new XMLReferenceResolver()); + writer.setIndentation(TAB_INDENT); + + writer.write(this.connectDelay, CONNECT_DELAY_PROP, Integer.class); + + writer.write(this.associations, ASSOCIATIONS, AssociationMap.class); + + writer.close(); + } catch (Exception e) { + logger.error("Error while persisting the Rule state in file", e); + } + } + + public void removeAllResourses() throws Exception { + + synchronized (this) { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (this.associations.size() == 0) + // no resources allocated - nothing to do + return; + + if (logger.isInfoEnabled()) { + logger.info(String.format("Removing allocated resources: Associations=%d", this.associations.size())); + } + + synchronized (this) { + + // Remove all associations + ArrayList lst = new ArrayList(); + for (FastMap.Entry n = this.associations.head(), end = this.associations + .tail(); (n = n.getNext()) != end;) { + lst.add(n.getKey()); + } + for (String n : lst) { + this.stopAssociation(n); + this.removeAssociation(n); + } + + // We store the cleared state + this.store(); + } + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onRemoveAllResources(); + } catch (Throwable ee) { + logger.error("Exception while invoking onRemoveAllResources", ee); + } + } + } + } + + public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, + String assocName) throws Exception { + return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, null); + } + + public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, + String assocName, IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception { + return addAssociation(hostAddress, hostPort, peerAddress, peerPort, assocName, IpChannelType.SCTP, extraHostAddresses, + null); + } + + public ManageableAssociation addAssociation(String hostAddress, int hostPort, String peerAddress, int peerPort, + String assocName, IpChannelType ipChannelType, String[] extraHostAddresses, String secondaryPeerAddress) + throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (hostAddress == null) { + throw new Exception("Host address cannot be null"); + } + + if (hostPort < 0) { + throw new Exception("Host port cannot be less than 0"); + } + + if (peerAddress == null) { + throw new Exception("Peer address cannot be null"); + } + + if (peerPort < 1) { + throw new Exception("Peer port cannot be less than 1"); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + synchronized (this) { + for (FastMap.Entry n = this.associations.head(), end = this.associations + .tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + + if (assocName.equals(associationTemp.getName())) { + throw new Exception(String.format("Already has association=%s", associationTemp.getName())); + } + + } + ManageableAssociation association = null; + if (isInBranchingMode()) { + association = new OneToOneAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, + extraHostAddresses, secondaryPeerAddress); + } else { + association = new OneToManyAssociationImpl(hostAddress, hostPort, peerAddress, peerPort, assocName, + extraHostAddresses, secondaryPeerAddress); + } + + association.setManagement(this); + association.setInitPayloadData(initPayloadData); + + AssociationMap newAssociations = new AssociationMap(); + newAssociations.putAll(this.associations); + newAssociations.put(assocName, association); + this.associations = newAssociations; + + this.store(); + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onAssociationAdded(association); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationAdded", ee); + } + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("Added Associoation=%s of type=%s", association.getName(), + association.getAssociationType())); + } + + return association; + } + } + + public Association getAssociation(String assocName) throws Exception { + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + Association associationTemp = this.associations.get(assocName); + + if (associationTemp == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + return associationTemp; + } + + /** + * @return the associations + */ + public Map getAssociations() { + Map routeTmp = new HashMap(); + routeTmp.putAll(this.associations); + return routeTmp; + } + + public void startAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + ManageableAssociation association = this.associations.get(assocName); + + if (association == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + if (association.isStarted()) { + throw new Exception(String.format("Association=%s is already started", assocName)); + } + + association.start(); + this.store(); + } + + public void stopAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + ManageableAssociation association = this.associations.get(assocName); + + if (association == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + association.stop(); + this.store(); + } + + public void removeAssociation(String assocName) throws Exception { + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + synchronized (this) { + Association association = this.associations.get(assocName); + + if (association == null) { + throw new Exception(String.format("No Association found for name=%s", assocName)); + } + + if (association.isStarted()) { + throw new Exception(String.format("Association name=%s is started. Stop before removing", assocName)); + } + + AssociationMap newAssociations = new AssociationMap(); + newAssociations.putAll(this.associations); + newAssociations.remove(assocName); + this.associations = newAssociations; + + this.store(); + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onAssociationRemoved(association); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationRemoved", ee); + } + } + } + } + + public PayloadData getInitPayloadData() { + return initPayloadData; + } + + public void setInitPayloadData(PayloadData initPayloadData) { + this.initPayloadData = initPayloadData; + } + + /** + * @return the pendingChanges + */ + protected FastList getPendingChanges() { + return pendingChanges; + } + + /** + * @return the socketSelector + */ + protected Selector getSocketSelector() { + return socketSelector; + } + + protected void populateWorkerThread(int workerThreadTable[]) { + for (int count = 0; count < workerThreadTable.length; count++) { + if (this.workerThreadCount == this.workerThreads) { + this.workerThreadCount = 0; + } + + workerThreadTable[count] = this.workerThreadCount; + this.workerThreadCount++; + } + } + + protected ExecutorService getExecutorService(int index) { + return this.executorServices[index]; + } + + /* unimplemented management methods */ + @Override + public Server addServer(String serverName, String hostAddress, int port) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public Server addServer(String serverName, String hostAddress, int port, IpChannelType ipChannelType, + boolean acceptAnonymousConnections, int maxConcurrentConnectionsCount, String[] extraHostAddresses) + throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public Server addServer(String serverName, String hostAddress, int port, IpChannelType ipChannelType, + String[] extraHostAddresses) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName) + throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, + IpChannelType ipChannelType) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not support server type associations!"); + } + + @Override + public ServerListener getServerListener() { + return null; + } + + @Override + public List getServers() { + return Collections.emptyList(); + } + + @Override + public void removeServer(String serverName) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public void setServerListener(ServerListener serverListener) { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public void startServer(String serverName) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } + + @Override + public void stopServer(String serverName) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not implement server functionality!"); + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java index 82fb992..0c219aa 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSctpXMLBinding.java @@ -36,61 +36,67 @@ @SuppressWarnings("serial") public class MultiSctpXMLBinding extends XMLBinding { - private final boolean isBranched; - - public MultiSctpXMLBinding(boolean isBranched) { - this.isBranched = isBranched; - } + private final boolean isBranched; - protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_ONE = new XMLFormat>(null) { + public MultiSctpXMLBinding(boolean isBranched) { + this.isBranched = isBranched; + } - @Override - public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { - for (Entry entry: obj.entrySet()) { - xml.add((String) entry.getKey(), "name", String.class); - xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); - } - } + protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_ONE = new XMLFormat>( + null) { - @Override - public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { - while (xml.hasNext()) { - String key = xml.get("name", String.class); - OneToOneAssociationImpl association = xml.get("association", OneToOneAssociationImpl.class); - obj.put(key, association); - } - } - }; + @Override + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + for (Entry entry : obj.entrySet()) { + xml.add((String) entry.getKey(), "name", String.class); + xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); + } + } - protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_MANY = new XMLFormat>(null) { + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) + throws XMLStreamException { + while (xml.hasNext()) { + String key = xml.get("name", String.class); + OneToOneAssociationImpl association = xml.get("association", OneToOneAssociationImpl.class); + obj.put(key, association); + } + } + }; - @Override - public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) throws XMLStreamException { - for (Entry entry: obj.entrySet()) { - xml.add((String) entry.getKey(), "name", String.class); - xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); - } - } + protected static final XMLFormat> ASSOCIATION_MAP_ONE_TO_MANY = new XMLFormat>( + null) { - @Override - public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) throws XMLStreamException { - while (xml.hasNext()) { - String key = xml.get("name", String.class); - OneToManyAssociationImpl association = null; - association = xml.get("association", OneToManyAssociationImpl.class); - obj.put(key, association); - } - } - }; + @Override + public void write(AssociationMap obj, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + for (Entry entry : obj.entrySet()) { + xml.add((String) entry.getKey(), "name", String.class); + xml.add((ManageableAssociation) entry.getValue(), "association", ManageableAssociation.class); + } + } - @SuppressWarnings("rawtypes") - protected XMLFormat getFormat( Class forClass) throws XMLStreamException { - if (AssociationMap.class.equals(forClass)) { - if (isBranched) { - return ASSOCIATION_MAP_ONE_TO_ONE; - } - return ASSOCIATION_MAP_ONE_TO_MANY; - } - return super.getFormat(forClass); - } + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, AssociationMap obj) + throws XMLStreamException { + while (xml.hasNext()) { + String key = xml.get("name", String.class); + OneToManyAssociationImpl association = null; + association = xml.get("association", OneToManyAssociationImpl.class); + obj.put(key, association); + } + } + }; + + @SuppressWarnings("rawtypes") + protected XMLFormat getFormat(Class forClass) throws XMLStreamException { + if (AssociationMap.class.equals(forClass)) { + if (isBranched) { + return ASSOCIATION_MAP_ONE_TO_ONE; + } + return ASSOCIATION_MAP_ONE_TO_MANY; + } + return super.getFormat(forClass); + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java index 19b7c05..70de8ae 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiSelectorThread.java @@ -42,181 +42,187 @@ public class MultiSelectorThread implements Runnable { - protected static final Logger logger = Logger.getLogger(MultiSelectorThread.class); - - protected Selector selector; - - protected MultiManagementImpl management = null; - - protected volatile boolean started = true; - - /** - * Creates the MultiSelector instance for the given MultiManagementImpl (SCTP stack) and Selector - * - * @param selector - * @param management - */ - protected MultiSelectorThread(Selector selector, MultiManagementImpl management) { - super(); - this.selector = selector; - this.management = management; - } - - /** - * @param started - * the started to set - */ - protected void setStarted(boolean started) { - this.started = started; - } - - @Override - public void run() { - if (logger.isInfoEnabled()) { - logger.info(String.format("SelectorThread for Management=%s started.", this.management.getName())); - } - while (this.started) { - try { - FastList pendingChanges = this.management.getPendingChanges(); - - // Process any pending changes - synchronized (pendingChanges) { - Iterator changes = pendingChanges.iterator(); - while (changes.hasNext()) { - MultiChangeRequest change = changes.next(); - - SelectionKey key = change.getSocketChannel() == null ? null : change.getSocketChannel().keyFor(this.selector); - if (logger.isDebugEnabled()) { - if (key != null && key.isValid()) { - logger.debug("change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + " for selector=" + this.selector - + " key interesOps=" + key.interestOps()); - } - } - switch (change.getType()) { - case MultiChangeRequest.CHANGEOPS: - pendingChanges.remove(change); - if (key == null ) { - logger.warn("change=" + change + ": key is null", new NullPointerException("Selection key is null")); - } else if (!key.isValid()) { - logger.warn("change=" + change + ": key=" + key + " key is invalid", new InternalError("Selection key is invalid")); - } else { - key.interestOps(change.getOps()); - } - break; - case MultiChangeRequest.ADD_OPS : - pendingChanges.remove(change); - if (key == null ) { - logger.warn("change=" + change + ": key is null", new NullPointerException("Selection key is null")); - } else if (!key.isValid()) { - logger.warn("change=" + change + ": key=" + key + " key is invalid", new InternalError("Selection key is invalid")); - } else { - key.interestOps(key.interestOps() | change.getOps()); - } - break; - case MultiChangeRequest.REGISTER: - pendingChanges.remove(change); - - SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); - - if (change.isMultiAssocRequest()) { - key1.attach(change.getAssocMultiplexer()); - if (logger.isDebugEnabled()) { - logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + " of the association=" + change.getAssocMultiplexer()); - } - } else { - key1.attach(change.getAssociation()); - if (logger.isDebugEnabled()) { - logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + " of the association=" + change.getAssociation()); - } - } - break; - case MultiChangeRequest.CONNECT: - if (!change.getAssociation().isStarted()) { - pendingChanges.remove(change); - } else { - if (change.getExecutionTime() <= System.currentTimeMillis()) { - pendingChanges.remove(change); - change.getAssociation().reconnect(); - } - } - break; - case MultiChangeRequest.CLOSE: - pendingChanges.remove(change); - if (!change.isMultiAssocRequest()) { - change.getAssociation().close(); - } - break; - } - } - } - - // Wait for an event one of the registered channels - this.selector.select(500); - - // Iterate over the set of keys for which events are available - Iterator selectedKeys = this.selector.selectedKeys().iterator(); - - while (selectedKeys.hasNext()) { - SelectionKey key = selectedKeys.next(); - selectedKeys.remove(); - - if (!key.isValid()) { - continue; - } - - // Check what event is available and deal with it - if (key.isConnectable()) { - logger.error("Illegal selectionKey state: connectable"); - } - if (key.isAcceptable()) { - logger.error("Illegal selectionKey state: acceptable"); - } - if (key.isReadable()) { - this.read(key); - } - if (key.isWritable()) { - this.write(key); - } - } - } catch (CancelledKeyException cke) { - //having this exception when closing a channel can be normal, but we log it on WARN level - logger.warn("Selecting a cancelled ready key: " + cke.getMessage()); - } catch (Exception e) { - logger.error("Error while selecting the ready keys", e); - e.printStackTrace(); - } - } - - try { - this.selector.close(); - } catch (IOException e) { - logger.error(String.format("Error while closing Selector for SCTP Management=%s", this.management.getName())); - } - - if (logger.isInfoEnabled()) { - logger.info(String.format("SelectorThread for Management=%s stopped.", this.management.getName())); - } - } - - private void read(SelectionKey key) throws IOException { - if (key.attachment() instanceof OneToManyAssocMultiplexer) { - OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); - multiplexer.read(); - } else if (key.attachment() instanceof OneToOneAssociationImpl) { - OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); - association.read(); - } - } - - private void write(SelectionKey key) throws IOException { - if (key.attachment() instanceof OneToManyAssocMultiplexer) { - OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); - multiplexer.write(key); - } else if (key.attachment() instanceof OneToOneAssociationImpl) { - OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); - association.write(key); - } - } + protected static final Logger logger = Logger.getLogger(MultiSelectorThread.class); + + protected Selector selector; + + protected MultiManagementImpl management = null; + + protected volatile boolean started = true; + + /** + * Creates the MultiSelector instance for the given MultiManagementImpl (SCTP stack) and Selector + * + * @param selector + * @param management + */ + protected MultiSelectorThread(Selector selector, MultiManagementImpl management) { + super(); + this.selector = selector; + this.management = management; + } + + /** + * @param started the started to set + */ + protected void setStarted(boolean started) { + this.started = started; + } + + @Override + public void run() { + if (logger.isInfoEnabled()) { + logger.info(String.format("SelectorThread for Management=%s started.", this.management.getName())); + } + while (this.started) { + try { + FastList pendingChanges = this.management.getPendingChanges(); + + // Process any pending changes + synchronized (pendingChanges) { + Iterator changes = pendingChanges.iterator(); + while (changes.hasNext()) { + MultiChangeRequest change = changes.next(); + + SelectionKey key = change.getSocketChannel() == null ? null + : change.getSocketChannel().keyFor(this.selector); + if (logger.isDebugEnabled()) { + if (key != null && key.isValid()) { + logger.debug( + "change=" + change + ": key=" + key + " of socketChannel=" + change.getSocketChannel() + + " for selector=" + this.selector + " key interesOps=" + key.interestOps()); + } + } + switch (change.getType()) { + case MultiChangeRequest.CHANGEOPS: + pendingChanges.remove(change); + if (key == null) { + logger.warn("change=" + change + ": key is null", + new NullPointerException("Selection key is null")); + } else if (!key.isValid()) { + logger.warn("change=" + change + ": key=" + key + " key is invalid", + new InternalError("Selection key is invalid")); + } else { + key.interestOps(change.getOps()); + } + break; + case MultiChangeRequest.ADD_OPS: + pendingChanges.remove(change); + if (key == null) { + logger.warn("change=" + change + ": key is null", + new NullPointerException("Selection key is null")); + } else if (!key.isValid()) { + logger.warn("change=" + change + ": key=" + key + " key is invalid", + new InternalError("Selection key is invalid")); + } else { + key.interestOps(key.interestOps() | change.getOps()); + } + break; + case MultiChangeRequest.REGISTER: + pendingChanges.remove(change); + + SelectionKey key1 = change.getSocketChannel().register(this.selector, change.getOps()); + + if (change.isMultiAssocRequest()) { + key1.attach(change.getAssocMultiplexer()); + if (logger.isDebugEnabled()) { + logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + + " of the association=" + change.getAssocMultiplexer()); + } + } else { + key1.attach(change.getAssociation()); + if (logger.isDebugEnabled()) { + logger.debug("Key=" + key1 + "is registered to channel=" + change.getSocketChannel() + + " of the association=" + change.getAssociation()); + } + } + break; + case MultiChangeRequest.CONNECT: + if (!change.getAssociation().isStarted()) { + pendingChanges.remove(change); + } else { + if (change.getExecutionTime() <= System.currentTimeMillis()) { + pendingChanges.remove(change); + change.getAssociation().reconnect(); + } + } + break; + case MultiChangeRequest.CLOSE: + pendingChanges.remove(change); + if (!change.isMultiAssocRequest()) { + change.getAssociation().close(); + } + break; + } + } + } + + // Wait for an event one of the registered channels + this.selector.select(500); + + // Iterate over the set of keys for which events are available + Iterator selectedKeys = this.selector.selectedKeys().iterator(); + + while (selectedKeys.hasNext()) { + SelectionKey key = selectedKeys.next(); + selectedKeys.remove(); + + if (!key.isValid()) { + continue; + } + + // Check what event is available and deal with it + if (key.isConnectable()) { + logger.error("Illegal selectionKey state: connectable"); + } + if (key.isAcceptable()) { + logger.error("Illegal selectionKey state: acceptable"); + } + if (key.isReadable()) { + this.read(key); + } + if (key.isWritable()) { + this.write(key); + } + } + } catch (CancelledKeyException cke) { + // having this exception when closing a channel can be normal, but we log it on WARN level + logger.warn("Selecting a cancelled ready key: " + cke.getMessage()); + } catch (Exception e) { + logger.error("Error while selecting the ready keys", e); + e.printStackTrace(); + } + } + + try { + this.selector.close(); + } catch (IOException e) { + logger.error(String.format("Error while closing Selector for SCTP Management=%s", this.management.getName())); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("SelectorThread for Management=%s stopped.", this.management.getName())); + } + } + + private void read(SelectionKey key) throws IOException { + if (key.attachment() instanceof OneToManyAssocMultiplexer) { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.read(); + } else if (key.attachment() instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); + association.read(); + } + } + + private void write(SelectionKey key) throws IOException { + if (key.attachment() instanceof OneToManyAssocMultiplexer) { + OneToManyAssocMultiplexer multiplexer = (OneToManyAssocMultiplexer) key.attachment(); + multiplexer.write(key); + } else if (key.attachment() instanceof OneToOneAssociationImpl) { + OneToOneAssociationImpl association = (OneToOneAssociationImpl) key.attachment(); + association.write(key); + } + } } - diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java index c73e95a..01da0fb 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiWorker.java @@ -33,29 +33,29 @@ */ public class MultiWorker implements Runnable { - private Association association; - private AssociationListener associationListener = null; - private PayloadData payloadData = null; + private Association association; + private AssociationListener associationListener = null; + private PayloadData payloadData = null; - /** - * @param association - * @param associationListener - * @param payloadData - */ - protected MultiWorker(Association association, AssociationListener associationListener, PayloadData payloadData) { - super(); - this.association = association; - this.associationListener = associationListener; - this.payloadData = payloadData; - } + /** + * @param association + * @param associationListener + * @param payloadData + */ + protected MultiWorker(Association association, AssociationListener associationListener, PayloadData payloadData) { + super(); + this.association = association; + this.associationListener = associationListener; + this.payloadData = payloadData; + } - @Override - public void run() { - try { - this.associationListener.onPayload(this.association, this.payloadData); - } catch (Exception e) { - e.printStackTrace(); - } - } + @Override + public void run() { + try { + this.associationListener.onPayload(this.association, this.payloadData); + } catch (Exception e) { + e.printStackTrace(); + } + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java index b878487..9c4e4e2 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/NamingThreadFactory.java @@ -5,15 +5,14 @@ import java.util.concurrent.atomic.AtomicInteger; /** - * Thread factory which names threads by "pool--thread-n". - * This is a replacement for Executors.defaultThreadFactory() to be able to identify pools. - * Optionally a delegate thread factory can be given which creates the Thread - * object itself, if no delegate has been given, Executors.defaultThreadFactory is used. + * Thread factory which names threads by "pool--thread-n". This is a replacement for Executors.defaultThreadFactory() + * to be able to identify pools. Optionally a delegate thread factory can be given which creates the Thread object itself, if no + * delegate has been given, Executors.defaultThreadFactory is used. * * @author pocsaji.miklos@alerant.hu */ public class NamingThreadFactory implements ThreadFactory { - + private ThreadFactory delegate; private String baseName; private AtomicInteger index; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 4f1271a..7af6149 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -22,401 +22,434 @@ import com.sun.nio.sctp.MessageInfo; import com.sun.nio.sctp.SctpChannel; import com.sun.nio.sctp.SctpMultiChannel; + /** * Controls the read, write and init operations of SCTP associations of a SctpMultiChannel. * - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu */ @SuppressWarnings("restriction") public class OneToManyAssocMultiplexer { - private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); - - private MultiManagementImpl management; - - private HostAddressInfo hostAddressInfo; - private SctpMultiChannel socketMultiChannel; - - // The buffer into which we'll read data when it's available - private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); - - // Is the multiplexer been started by management? - private AtomicBoolean started = new AtomicBoolean(false); - - - // Queue holds payloads to be transmitted - private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper(new ConcurrentLinkedQueue()); - - private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); - private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); - - protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); - - /* - * Support fast and save queue operations like: swap, conactAsHead. - * - */ - static class ConcurrentLinkedQueueSwapper { - private ReadWriteLock lock = new ReentrantReadWriteLock(); - private ConcurrentLinkedQueue queue; - - public ConcurrentLinkedQueueSwapper(ConcurrentLinkedQueue queue) { - this.queue = queue; - } - - public void add(T e) { - lock.readLock().lock(); - queue.add(e); - lock.readLock().unlock(); - } - - public boolean isEmpty() { - return queue.isEmpty(); - } - - public ConcurrentLinkedQueue swap(ConcurrentLinkedQueue newQueue) { - if (newQueue == null) { - throw new NullPointerException(this.getClass()+".swap(ConcurrentLinkedQueue newQueue): newQueue parameter can not be null!"); - } - ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newQueue); - lock.writeLock().lock(); - ConcurrentLinkedQueue oldQueue = this.queue; - this.queue = newQueueCopy; - lock.writeLock().unlock(); - return oldQueue; - } - - public void concatAsHead(ConcurrentLinkedQueue newHead) { - if (newHead == null) { - throw new NullPointerException(this.getClass()+".concatAsHead(ConcurrentLinkedQueue newHead): newHead parameter can not be null!"); - } - ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newHead); - lock.writeLock().lock(); - for (T e: this.queue) { - newQueueCopy.add(e); - } - this.queue = newQueueCopy; - lock.writeLock().unlock(); - } - - } - public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagementImpl management) throws IOException { - super(); - if (hostAddressInfo == null || management == null) { - throw new IllegalArgumentException("Constructor OneToManyAssocMultiplexer: hostAddressInfo and management parameters can not be null!"); - } - this.hostAddressInfo = hostAddressInfo; - this.management = management; - this.rxBuffer.clear(); - this.rxBuffer.rewind(); - this.rxBuffer.flip(); - initMultiChannel(); - started.set(true); - } - - protected void registerAssociation(ManageableAssociation association) { - if (!started.get()) { - throw new IllegalStateException("OneToManyAssocMultiplexer is stopped!"); - } - - pendingAssocs.add(association); - } - - protected void start() throws IOException { - if (!started.compareAndSet(false, true)) { - return; - } - this.rxBuffer.clear(); - this.rxBuffer.rewind(); - this.rxBuffer.flip(); - initMultiChannel(); - } - - protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation association) { - if (!started.get()) { - throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); - } - if (id == null || association == null) { - return; - } - connectedAssocs.put(id, association); - pendingAssocs.remove(association); - association.assignSctpAssociationId(id); - } - - protected ManageableAssociation findConnectedAssociation(Integer sctpAssocId) { - return connectedAssocs.get(sctpAssocId); - } - - private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation) { - String peerAddresses = ""; - try { - for (SocketAddress sa : getSocketMultiChannel().getRemoteAddresses(sctpAssociation)) { - peerAddresses += ", "+sa.toString(); - } - } catch (IOException e) { } - return peerAddresses; - } - - protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { - String peerAddresses = extractPeerAddresses(sctpAssociation); - if (logger.isDebugEnabled()) { - peerAddresses = peerAddresses.isEmpty() ? peerAddresses : peerAddresses.substring(2); - logger.debug("Association("+sctpAssociation.associationID()+") connected to "+peerAddresses); - } - ManageableAssociation ret = null; - for (ManageableAssociation assocImpl : pendingAssocs) { - if (assocImpl.isConnectedToPeerAddresses(peerAddresses)) { - ret = assocImpl; - break; - } - } - return ret; - } - - protected ManageableAssociation findPendingAssociationByAddress(SocketAddress address) { - String peerAddress = address.toString(); - if (logger.isDebugEnabled()) { - logger.debug("findPendingAssociationByAddress is called with address parameter=" + peerAddress); - } - ManageableAssociation ret = null; - for (ManageableAssociation assocImpl : pendingAssocs) { - if (assocImpl.isConnectedToPeerAddresses(peerAddress)) { - ret = assocImpl; - break; - } - } - return ret; - } - - private void initMultiChannel() throws IOException { - try { - socketMultiChannel = SctpMultiChannel.open(); - socketMultiChannel.configureBlocking(false); - socketMultiChannel.bind(new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); - if (this.hostAddressInfo.getSecondaryHostAddress() != null && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { - socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); - } - } catch (IOException ex) { - logger.warn("Error while init multi channel ", ex); - if (socketMultiChannel != null && socketMultiChannel.isOpen()) { - try { - socketMultiChannel.close(); - } catch (IOException closeEx) {}; - } - throw ex; - } - - if (logger.isDebugEnabled()) { - logger.debug("New socketMultiChanel is created: "+socketMultiChannel+" supported options: "+socketMultiChannel.validOps()+":"+socketMultiChannel.supportedOptions()); - } - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, null, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE|SelectionKey.OP_READ)); - } - } - - public HostAddressInfo getHostAddressInfo() { - return hostAddressInfo; - } - public SctpMultiChannel getSocketMultiChannel() { - return socketMultiChannel; - } - - private ManageableAssociation getAssociationByMessageInfo(MessageInfo msgInfo) { - ManageableAssociation ret = null; - //find connected assoc - if (msgInfo.association() != null) { - ret = findConnectedAssociation(msgInfo.association().associationID()); - } - //find in pending assoc - if (ret == null) { - ret = findPendingAssociation(msgInfo.association()); - } - return ret; - } - - protected void send(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation sender) throws IOException { - if (!started.get()) { - return; - } - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - - // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(this.getSocketMultiChannel(), this, null, MultiChangeRequest.ADD_OPS, - SelectionKey.OP_WRITE)); - - this.txQueueSwapper.add(new SctpMessage(payloadData, messageInfo, sender)); - } - - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); - } - - protected void write(SelectionKey key) { - if (!started.get()) { - return; - } - ConcurrentLinkedQueue txQueueTmp = txQueueSwapper.swap(new ConcurrentLinkedQueue()); - - if (txQueueTmp.isEmpty()) { - // We wrote away all data, so we're no longer interested - // in writing on this socket. Switch back to waiting for - // data. - key.interestOps(SelectionKey.OP_READ); - if (logger.isDebugEnabled()) { - logger.debug("write: txQueue was empty"); - } - return; - } - - while (!txQueueTmp.isEmpty()) { - SctpMessage msg = txQueueTmp.poll(); - msg.getSenderAssoc().writePayload(msg.getPayloadData()); - } - - if (txQueueTmp.isEmpty()) { - // We wrote away all data, so we're no longer interested - // in writing on this socket. Switch back to waiting for - // data. - key.interestOps(SelectionKey.OP_READ); - } - } - - private void doReadSctp() throws IOException { - - rxBuffer.clear(); - MessageInfo messageInfo = null; - messageInfo = this.socketMultiChannel.receive(rxBuffer, this, this.associationHandler); - - if (messageInfo == null) { - if (logger.isDebugEnabled()) { - logger.debug(String.format(" messageInfo is null for AssociationMultiplexer=%s", this)); - } - return; - } - - int len = messageInfo.bytes(); - if (len == -1) { - logger.error(String.format("Rx -1 while trying to read from underlying socket for AssociationMultiplexer=%s ", - this)); - return; - } - - rxBuffer.flip(); - byte[] data = new byte[len]; - rxBuffer.get(data); - rxBuffer.clear(); - - PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), - messageInfo.payloadProtocolID(), messageInfo.streamNumber()); - - ManageableAssociation assoc = getAssociationByMessageInfo(messageInfo); - if (assoc != null) { - assoc.readPayload(payload); - } - - } - - protected void read() { - if (!started.get()) { - return; - } - try { - doReadSctp(); - } catch (IOException e) { - logger.error("Unable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, e); - } catch (Exception ex) { - logger.error("Unexpected exception: unnable to read from socketMultiChannek, hostAddressInfo: "+this.hostAddressInfo, ex); - } - } - - protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Association sctpAssociation) { - if (!started.get()) { - return null; - } - ManageableAssociation association = findConnectedAssociation(sctpAssociation.associationID()); - if (association == null) { - association = findPendingAssociation(sctpAssociation); - assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); - - if (management.isInBranchingMode()) { - if (logger.isInfoEnabled()) { - logger.info("Branching association: " + association.getName()); - } - try { - SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); - if (sctpChannel.isBlocking()) { - sctpChannel.configureBlocking(false); - } - - OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) association; - oneToOneAssoc.branch(sctpChannel, management); - - if (logger.isDebugEnabled()) { - logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); - } - return oneToOneAssoc; - } catch (Exception ex) { - logger.error(ex); - } - } - }; - if (logger.isDebugEnabled()) { - logger.debug("resolveAssociationImpl result for sctpAssocId: "+sctpAssociation.associationID()+" is "+association); - } - return association; - } - - protected void stop() throws IOException { - if (!started.compareAndSet(true, false)) { - return; - } - - for (ManageableAssociation assocImpl: connectedAssocs.values()) { - try { - assocImpl.stop(); - } catch (Exception ex) { - logger.warn(ex); - } - } - connectedAssocs.clear(); - for (ManageableAssociation assocImpl: pendingAssocs) { - try { - assocImpl.stop(); - } catch (Exception e) { - logger.warn(e);; - } - } - pendingAssocs.clear(); - this.socketMultiChannel.close(); - } - - static class SctpMessage { - private final PayloadData payloadData; - private final MessageInfo messageInfo; - private final ManageableAssociation senderAssoc; - protected SctpMessage(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation senderAssoc) { - super(); - this.payloadData = payloadData; - this.messageInfo = messageInfo; - this.senderAssoc = senderAssoc; - } - protected PayloadData getPayloadData() { - return payloadData; - } - protected MessageInfo getMessageInfo() { - return messageInfo; - } - protected ManageableAssociation getSenderAssoc() { - return senderAssoc; - } - @Override - public String toString() { - return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" - + messageInfo + ", senderAssoc=" + senderAssoc + "]"; - } - } + private static final Logger logger = Logger.getLogger(OneToManyAssocMultiplexer.class); + + private MultiManagementImpl management; + + private HostAddressInfo hostAddressInfo; + private SctpMultiChannel socketMultiChannel; + + // The buffer into which we'll read data when it's available + private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); + + // Is the multiplexer been started by management? + private AtomicBoolean started = new AtomicBoolean(false); + + // Queue holds payloads to be transmitted + private ConcurrentLinkedQueueSwapper txQueueSwapper = new ConcurrentLinkedQueueSwapper( + new ConcurrentLinkedQueue()); + + private CopyOnWriteArrayList pendingAssocs = new CopyOnWriteArrayList(); + private ConcurrentHashMap connectedAssocs = new ConcurrentHashMap(); + + protected final MultiAssociationHandler associationHandler = new MultiAssociationHandler(); + + /* + * Support fast and save queue operations like: swap, conactAsHead. + * + */ + static class ConcurrentLinkedQueueSwapper { + private ReadWriteLock lock = new ReentrantReadWriteLock(); + private ConcurrentLinkedQueue queue; + + public ConcurrentLinkedQueueSwapper(ConcurrentLinkedQueue queue) { + this.queue = queue; + } + + public void add(T e) { + lock.readLock().lock(); + queue.add(e); + lock.readLock().unlock(); + } + + public boolean isEmpty() { + return queue.isEmpty(); + } + + public ConcurrentLinkedQueue swap(ConcurrentLinkedQueue newQueue) { + if (newQueue == null) { + throw new NullPointerException( + this.getClass() + ".swap(ConcurrentLinkedQueue newQueue): newQueue parameter can not be null!"); + } + ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newQueue); + lock.writeLock().lock(); + ConcurrentLinkedQueue oldQueue = this.queue; + this.queue = newQueueCopy; + lock.writeLock().unlock(); + return oldQueue; + } + + public void concatAsHead(ConcurrentLinkedQueue newHead) { + if (newHead == null) { + throw new NullPointerException(this.getClass() + + ".concatAsHead(ConcurrentLinkedQueue newHead): newHead parameter can not be null!"); + } + ConcurrentLinkedQueue newQueueCopy = new ConcurrentLinkedQueue(newHead); + lock.writeLock().lock(); + for (T e : this.queue) { + newQueueCopy.add(e); + } + this.queue = newQueueCopy; + lock.writeLock().unlock(); + } + + } + + public OneToManyAssocMultiplexer(HostAddressInfo hostAddressInfo, MultiManagementImpl management) throws IOException { + super(); + if (hostAddressInfo == null || management == null) { + throw new IllegalArgumentException( + "Constructor OneToManyAssocMultiplexer: hostAddressInfo and management parameters can not be null!"); + } + this.hostAddressInfo = hostAddressInfo; + this.management = management; + this.rxBuffer.clear(); + this.rxBuffer.rewind(); + this.rxBuffer.flip(); + initMultiChannel(); + started.set(true); + } + + protected void registerAssociation(ManageableAssociation association) { + if (!started.get()) { + throw new IllegalStateException("OneToManyAssocMultiplexer is stopped!"); + } + + pendingAssocs.add(association); + } + + protected void start() throws IOException { + if (!started.compareAndSet(false, true)) { + return; + } + this.rxBuffer.clear(); + this.rxBuffer.rewind(); + this.rxBuffer.flip(); + initMultiChannel(); + } + + protected void assignSctpAssocIdToAssociation(Integer id, ManageableAssociation association) { + if (!started.get()) { + throw new IllegalStateException("OneToManyAssocMultiplexer is stoped!"); + } + if (id == null || association == null) { + return; + } + connectedAssocs.put(id, association); + pendingAssocs.remove(association); + association.assignSctpAssociationId(id); + } + + protected ManageableAssociation findConnectedAssociation(Integer sctpAssocId) { + return connectedAssocs.get(sctpAssocId); + } + + private String extractPeerAddresses(com.sun.nio.sctp.Association sctpAssociation) { + String peerAddresses = ""; + try { + for (SocketAddress sa : getSocketMultiChannel().getRemoteAddresses(sctpAssociation)) { + peerAddresses += ", " + sa.toString(); + } + } catch (IOException e) { + } + return peerAddresses; + } + + protected ManageableAssociation findPendingAssociation(com.sun.nio.sctp.Association sctpAssociation) { + String peerAddresses = extractPeerAddresses(sctpAssociation); + if (logger.isDebugEnabled()) { + peerAddresses = peerAddresses.isEmpty() ? peerAddresses : peerAddresses.substring(2); + logger.debug("Association(" + sctpAssociation.associationID() + ") connected to " + peerAddresses); + } + ManageableAssociation ret = null; + for (ManageableAssociation assocImpl : pendingAssocs) { + if (assocImpl.isConnectedToPeerAddresses(peerAddresses)) { + ret = assocImpl; + break; + } + } + return ret; + } + + protected ManageableAssociation findPendingAssociationByAddress(SocketAddress address) { + String peerAddress = address.toString(); + if (logger.isDebugEnabled()) { + logger.debug("findPendingAssociationByAddress is called with address parameter=" + peerAddress); + } + ManageableAssociation ret = null; + for (ManageableAssociation assocImpl : pendingAssocs) { + if (assocImpl.isConnectedToPeerAddresses(peerAddress)) { + ret = assocImpl; + break; + } + } + return ret; + } + + private void initMultiChannel() throws IOException { + try { + socketMultiChannel = SctpMultiChannel.open(); + socketMultiChannel.configureBlocking(false); + socketMultiChannel.bind( + new InetSocketAddress(this.hostAddressInfo.getPrimaryHostAddress(), this.hostAddressInfo.getHostPort())); + if (this.hostAddressInfo.getSecondaryHostAddress() != null + && !this.hostAddressInfo.getSecondaryHostAddress().isEmpty()) { + socketMultiChannel.bindAddress(InetAddress.getByName(this.hostAddressInfo.getSecondaryHostAddress())); + } + } catch (IOException ex) { + logger.warn("Error while init multi channel ", ex); + if (socketMultiChannel != null && socketMultiChannel.isOpen()) { + try { + socketMultiChannel.close(); + } catch (IOException closeEx) { + } + ; + } + throw ex; + } + + if (logger.isDebugEnabled()) { + logger.debug("New socketMultiChanel is created: " + socketMultiChannel + " supported options: " + + socketMultiChannel.validOps() + ":" + socketMultiChannel.supportedOptions()); + } + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(this.socketMultiChannel, this, null, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE | SelectionKey.OP_READ)); + } + } + + public HostAddressInfo getHostAddressInfo() { + return hostAddressInfo; + } + + public SctpMultiChannel getSocketMultiChannel() { + return socketMultiChannel; + } + + private ManageableAssociation getAssociationByMessageInfo(MessageInfo msgInfo) { + ManageableAssociation ret = null; + // find connected assoc + if (msgInfo.association() != null) { + ret = findConnectedAssociation(msgInfo.association().associationID()); + } + // find in pending assoc + if (ret == null) { + ret = findPendingAssociation(msgInfo.association()); + } + return ret; + } + + protected void send(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation sender) throws IOException { + send(payloadData, messageInfo, sender, false); + } + + protected void send(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation sender, boolean initMsg) throws IOException { + if (!started.get()) { + return; + } + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(this.getSocketMultiChannel(), this, null, MultiChangeRequest.ADD_OPS, + SelectionKey.OP_WRITE)); + + this.txQueueSwapper.add(new SctpMessage(payloadData, messageInfo, sender, initMsg)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + + protected void write(SelectionKey key) { + if (!started.get()) { + return; + } + ConcurrentLinkedQueue txQueueTmp = txQueueSwapper.swap(new ConcurrentLinkedQueue()); + + if (txQueueTmp.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + if (logger.isDebugEnabled()) { + logger.debug("write: txQueue was empty"); + } + return; + } + + while (!txQueueTmp.isEmpty()) { + SctpMessage msg = txQueueTmp.poll(); + msg.getSenderAssoc().writePayload(msg.getPayloadData(), msg.isInitMsg()); + } + + if (txQueueTmp.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + } + } + + private void doReadSctp() throws IOException { + + rxBuffer.clear(); + MessageInfo messageInfo = null; + messageInfo = this.socketMultiChannel.receive(rxBuffer, this, this.associationHandler); + + if (messageInfo == null) { + if (logger.isDebugEnabled()) { + logger.debug(String.format(" messageInfo is null for AssociationMultiplexer=%s", this)); + } + return; + } + + int len = messageInfo.bytes(); + if (len == -1) { + logger.error( + String.format("Rx -1 while trying to read from underlying socket for AssociationMultiplexer=%s ", this)); + return; + } + + rxBuffer.flip(); + byte[] data = new byte[len]; + rxBuffer.get(data); + rxBuffer.clear(); + + PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), + messageInfo.payloadProtocolID(), messageInfo.streamNumber()); + + ManageableAssociation assoc = getAssociationByMessageInfo(messageInfo); + if (assoc != null) { + assoc.readPayload(payload); + } + + } + + protected void read() { + if (!started.get()) { + return; + } + try { + doReadSctp(); + } catch (IOException e) { + logger.error("Unable to read from socketMultiChannek, hostAddressInfo: " + this.hostAddressInfo, e); + } catch (Exception ex) { + logger.error( + "Unexpected exception: unnable to read from socketMultiChannek, hostAddressInfo: " + this.hostAddressInfo, + ex); + } + } + + protected ManageableAssociation resolveAssociationImpl(com.sun.nio.sctp.Association sctpAssociation) { + if (!started.get()) { + return null; + } + ManageableAssociation association = findConnectedAssociation(sctpAssociation.associationID()); + if (association == null) { + association = findPendingAssociation(sctpAssociation); + assignSctpAssocIdToAssociation(sctpAssociation.associationID(), association); + + if (management.isInBranchingMode()) { + if (logger.isInfoEnabled()) { + logger.info("Branching association: " + association.getName()); + } + try { + SctpChannel sctpChannel = getSocketMultiChannel().branch(sctpAssociation); + if (sctpChannel.isBlocking()) { + sctpChannel.configureBlocking(false); + } + + OneToOneAssociationImpl oneToOneAssoc = (OneToOneAssociationImpl) association; + oneToOneAssoc.branch(sctpChannel, management); + + if (logger.isDebugEnabled()) { + logger.debug("resolveAssociationImpl result for sctpAssocId: " + sctpAssociation.associationID() + + " is " + association); + } + return oneToOneAssoc; + } catch (Exception ex) { + logger.error(ex); + } + } + } + ; + if (logger.isDebugEnabled()) { + logger.debug( + "resolveAssociationImpl result for sctpAssocId: " + sctpAssociation.associationID() + " is " + association); + } + return association; + } + + protected void stop() throws IOException { + if (!started.compareAndSet(true, false)) { + return; + } + + for (ManageableAssociation assocImpl : connectedAssocs.values()) { + try { + assocImpl.stop(); + } catch (Exception ex) { + logger.warn(ex); + } + } + connectedAssocs.clear(); + for (ManageableAssociation assocImpl : pendingAssocs) { + try { + assocImpl.stop(); + } catch (Exception e) { + logger.warn(e); + ; + } + } + pendingAssocs.clear(); + this.socketMultiChannel.close(); + } + + static class SctpMessage { + private final PayloadData payloadData; + private final MessageInfo messageInfo; + private final ManageableAssociation senderAssoc; + private final boolean initMsg; + + protected SctpMessage(PayloadData payloadData, MessageInfo messageInfo, ManageableAssociation senderAssoc, boolean initMsg) { + super(); + this.payloadData = payloadData; + this.messageInfo = messageInfo; + this.senderAssoc = senderAssoc; + this.initMsg = initMsg; + } + + protected PayloadData getPayloadData() { + return payloadData; + } + + protected MessageInfo getMessageInfo() { + return messageInfo; + } + + protected ManageableAssociation getSenderAssoc() { + return senderAssoc; + } + + public boolean isInitMsg() { + return initMsg; + } + + @Override + public String toString() { + return "SctpMessage [payloadData=" + payloadData + ", messageInfo=" + messageInfo + ", senderAssoc=" + senderAssoc + + ", initMsg=" + initMsg + "]"; + } + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java index 37f3b45..6d75f07 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationHandler.java @@ -14,159 +14,172 @@ /** * Handles notifications for OneToManyAssociationImpl objects. * - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu * */ @SuppressWarnings("restriction") public class OneToManyAssociationHandler extends AbstractNotificationHandler { - private static final Logger logger = Logger.getLogger(OneToManyAssociationHandler.class); - - private volatile int maxInboundStreams = 1; - private volatile int maxOutboundStreams = 1; - - - public OneToManyAssociationHandler() { - - } - - /** - * @return the maxInboundStreams - */ - public int getMaxInboundStreams() { - return maxInboundStreams; - } - - /** - * @return the maxOutboundStreams - */ - public int getMaxOutboundStreams() { - return maxOutboundStreams; - } - - - @Override - public HandlerResult handleNotification(Notification arg0, OneToManyAssociationImpl arg1) { - if (arg0 instanceof AssociationChangeNotification) { - return handleNotification((AssociationChangeNotification) arg0, arg1); - } - if (arg0 instanceof ShutdownNotification) { - return handleNotification((ShutdownNotification) arg0, arg1); - } - if (arg0 instanceof SendFailedNotification) { - return handleNotification((SendFailedNotification) arg0, arg1); - } - if (arg0 instanceof PeerAddressChangeNotification) { - return handleNotification((PeerAddressChangeNotification) arg0, arg1); - } - return super.handleNotification(arg0, arg1); - } - - @Override - public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssociationImpl association) { - - switch (not.event()) { - case COMM_UP: - //in case when comm is go online but the association has been already stopped COMM_UP event is sinked. - if (!association.isStarted()) { - return HandlerResult.CONTINUE; - } - if (not.association() != null) { - this.maxOutboundStreams = not.association().maxOutboundStreams(); - this.maxInboundStreams = not.association().maxInboundStreams(); - } - - if (logger.isInfoEnabled()) { - logger.info(String.format("New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", - association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); - } - - association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); - - try { - association.markAssociationUp(); - association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", association.getName()), e); - } - return HandlerResult.CONTINUE; - - case CANT_START: - logger.error(String.format("Can't start for Association=%s", association.getName())); - association.scheduleConnect(); - return HandlerResult.CONTINUE; - case COMM_LOST: - logger.warn(String.format("Communication lost for Association=%s", association.getName())); - - // Close the Socket - association.close(); - association.scheduleConnect(); - try { - association.markAssociationDown(); - association.getAssociationListener().onCommunicationLost(association); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", association.getName()), e); - } - return HandlerResult.RETURN; - case RESTART: - logger.warn(String.format("Restart for Association=%s", association.getName())); - try { - association.getAssociationListener().onCommunicationRestart(association); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", association.getName()), - e); - } - return HandlerResult.CONTINUE; - case SHUTDOWN: - if (logger.isInfoEnabled()) { - logger.info(String.format("Shutdown for Association=%s", association.getName())); - } - try { - association.markAssociationDown(); - association.getAssociationListener().onCommunicationShutdown(association); - association.scheduleConnect(); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), - e); - } - return HandlerResult.RETURN; - default: - logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); - break; - } - - return HandlerResult.CONTINUE; - } - - @Override - public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssociationImpl associtaion) { - if (logger.isInfoEnabled()) { - logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); - } - - try { - associtaion.markAssociationDown(); - associtaion.getAssociationListener().onCommunicationShutdown(associtaion); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), e); - } - - return HandlerResult.RETURN; - } - - @Override - public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssociationImpl associtaion) { - logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + private static final Logger logger = Logger.getLogger(OneToManyAssociationHandler.class); + + private volatile int maxInboundStreams = 1; + private volatile int maxOutboundStreams = 1; + + public OneToManyAssociationHandler() { + + } + + /** + * @return the maxInboundStreams + */ + public int getMaxInboundStreams() { + return maxInboundStreams; + } + + /** + * @return the maxOutboundStreams + */ + public int getMaxOutboundStreams() { + return maxOutboundStreams; + } + + @Override + public HandlerResult handleNotification(Notification arg0, OneToManyAssociationImpl arg1) { + if (arg0 instanceof AssociationChangeNotification) { + return handleNotification((AssociationChangeNotification) arg0, arg1); + } + if (arg0 instanceof ShutdownNotification) { + return handleNotification((ShutdownNotification) arg0, arg1); + } + if (arg0 instanceof SendFailedNotification) { + return handleNotification((SendFailedNotification) arg0, arg1); + } + if (arg0 instanceof PeerAddressChangeNotification) { + return handleNotification((PeerAddressChangeNotification) arg0, arg1); + } + return super.handleNotification(arg0, arg1); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToManyAssociationImpl association) { + + switch (not.event()) { + case COMM_UP: + // in case when comm is go online but the association has been already stopped COMM_UP event is sinked. + if (!association.isStarted()) { + return HandlerResult.CONTINUE; + } + if (not.association() != null) { + this.maxOutboundStreams = not.association().maxOutboundStreams(); + this.maxInboundStreams = not.association().maxInboundStreams(); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format( + "New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", + association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); + } + + association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); + + try { + association.markAssociationUp(); + association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, + this.maxOutboundStreams); + } catch (Exception e) { + logger.error( + String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", + association.getName()), + e); + } + return HandlerResult.CONTINUE; + + case CANT_START: + logger.error(String.format("Can't start for Association=%s", association.getName())); + association.switchInitSocketAddress(); + association.scheduleConnect(); + return HandlerResult.CONTINUE; + case COMM_LOST: + logger.warn(String.format("Communication lost for Association=%s", association.getName())); + + // Close the Socket + association.close(); + association.scheduleConnect(); + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationLost(association); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationLost on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.RETURN; + case RESTART: + logger.warn(String.format("Restart for Association=%s", association.getName())); + try { + association.getAssociationListener().onCommunicationRestart(association); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationRestart on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.CONTINUE; + case SHUTDOWN: + if (logger.isInfoEnabled()) { + logger.info(String.format("Shutdown for Association=%s", association.getName())); + } + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationShutdown(association); + association.scheduleConnect(); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.RETURN; + default: + logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); + break; + } + + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToManyAssociationImpl associtaion) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); + } + + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + } catch (Exception e) { + logger.error( + String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + associtaion.getName()), + e); + } + + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToManyAssociationImpl associtaion) { + logger.error(String.format( + "Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); associtaion.onSendFailed(); - return HandlerResult.RETURN; - } - - @Override - public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssociationImpl associtaion) { - if(logger.isEnabledFor(Priority.INFO)){ - logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); - } - return HandlerResult.CONTINUE; - } + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToManyAssociationImpl associtaion) { + if (logger.isEnabledFor(Priority.INFO)) { + logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), + associtaion.getName())); + } + return HandlerResult.CONTINUE; + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index c020c4f..297c2b4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -22,525 +22,532 @@ import com.sun.nio.sctp.MessageInfo; /** - * Implements a one-to-many type ManagableAssociation. Used when associations is NOT peeled off the sctp multi channel sockets to - * a separate sctp socket channel. + * Implements a one-to-many type ManagableAssociation. Used when associations is NOT peeled off the sctp multi channel sockets + * to a separate sctp socket channel. * - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu */ @SuppressWarnings("restriction") public class OneToManyAssociationImpl extends ManageableAssociation { - protected static final Logger logger = Logger.getLogger(OneToManyAssociationImpl.class); - - private static final String NAME = "name"; - private static final String SERVER_NAME = "serverName"; - private static final String HOST_ADDRESS = "hostAddress"; - private static final String HOST_PORT = "hostPort"; - - private static final String PEER_ADDRESS = "peerAddress"; - private static final String PEER_PORT = "peerPort"; - - private static final String ASSOCIATION_TYPE = "assoctype"; - private static final String IPCHANNEL_TYPE = "ipChannelType"; - private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; - private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; - - - - private AssociationListener associationListener = null; - - private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); - - protected final OneToManyAssociationHandler associationHandler = new OneToManyAssociationHandler(); - - // Is the Association been started by management? - private AtomicBoolean started = new AtomicBoolean(false); - // Is the Association up (connection is established) - protected AtomicBoolean up = new AtomicBoolean(false); - - private int workerThreadTable[] = null; - - private volatile MessageInfo msgInfo; - - private volatile com.sun.nio.sctp.Association sctpAssociation; - private final IpChannelType ipChannelType = IpChannelType.SCTP; - - private OneToManyAssocMultiplexer multiplexer; - - /** - * Count of number of IO Errors occured. - */ - private volatile int ioErrors = 0; - - - public OneToManyAssociationImpl() { - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - } - - /** - * Creating a CLIENT Association - * - * @param hostAddress - * @param hostPort - * @param peerAddress - * @param peerPort - * @param assocName - * @param ipChannelType - * @param extraHostAddresses - * @throws IOException - */ - public OneToManyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, - String[] extraHostAddresses) throws IOException { - super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); - // clean transmission buffer - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - } - - - public void start() throws Exception { - - if (this.associationListener == null) { - throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); - } - - if (started.getAndSet(true)) { - logger.warn("Association: "+this+" has been already STARTED"); - return; - } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationStarted(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationStarted", ee); - } - } - scheduleConnect(); - } - - public void stop() throws Exception { - if (!started.getAndSet(false)) { - logger.warn("Association: "+this+" has been already STOPPED"); - return; - } - - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationStopped(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationStopped", ee); - } - } - - try { - this.associationListener.onCommunicationShutdown(this); - } catch (Exception e) { - logger.error(String.format( - "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", - this.name), e); - } - - } - - public IpChannelType getIpChannelType() { - return IpChannelType.SCTP; - } - - /** - * @return the associationListener - */ - public AssociationListener getAssociationListener() { - return associationListener; - } - - /** - * @param associationListener - * the associationListener to set - */ - public void setAssociationListener(AssociationListener associationListener) { - this.associationListener = associationListener; - } - - /** - * @return the assocName - */ - public String getName() { - return name; - } - - /** - * @return the associationType - */ - public AssociationType getAssociationType() { - return AssociationType.CLIENT; - } - - /** - * @return the started - */ - @Override - public boolean isStarted() { - return started.get(); - } - - @Override - public boolean isConnected() { - return started.get() && up.get(); - } - - @Override - public boolean isUp() { - return up.get(); - } - - protected void markAssociationUp() { - if (up.getAndSet(true)) { - logger.debug("Association: "+this+" has been already marked UP"); - return; - } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationUp(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationUp", ee); - } - } - } - - protected void markAssociationDown() { - if (!up.getAndSet(false)) { - logger.debug("Association: "+this+" has been already marked DOWN"); - return; - } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationDown(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationDown", ee); - } - } - } - - /** - * @return the hostAddress - */ - public String getHostAddress() { - return hostAddress; - } - - /** - * @return the hostPort - */ - public int getHostPort() { - return hostPort; - } - - /** - * @return the peerAddress - */ - public String getPeerAddress() { - return peerAddress; - } - - /** - * @return the peerPort - */ - public int getPeerPort() { - return peerPort; - } - - /** - * @return the serverName - */ - public String getServerName() { - return null; - } - - @Override - public String[] getExtraHostAddresses() { - return extraHostAddresses; - } - - /** - * @param management - * the management to set - */ - public void setManagement(MultiManagementImpl management) { - this.management = management; - } - - - /** - * @param socketChannel - * the socketChannel to set - */ - protected void setSocketChannel(AbstractSelectableChannel socketChannel) { - // - } - - protected void readPayload(PayloadData payload) { - if (payload == null) { - return; - } - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); - } - - if (this.management.isSingleThread()) { - try { - this.associationListener.onPayload(this, payload); - } catch (Exception e) { - logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, - payload), e); - } - } else { - MultiWorker worker = new MultiWorker(this, this.associationListener, payload); - - ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload - .getStreamNumber()]); - try { - executorService.execute(worker); - } catch (RejectedExecutionException e) { - logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); - } catch (NullPointerException e) { - logger.error(String.format("NullPointerException while submitting %s", payload), e); - } catch (Exception e) { - logger.error(String.format("Exception while submitting %s", payload), e); - } - } - } - - public void send(PayloadData payloadData) throws Exception { - if (!started.get()) { - throw new Exception("send failed: Association is not started"); - } - multiplexer.send(payloadData, this.msgInfo, this); - } - - protected boolean writePayload(PayloadData payloadData) { - try { - - if (txBuffer.hasRemaining()) { - // All data wasn't sent in last doWrite. Try to send it now - this.doSend(); - } - // TODO Do we need to synchronize ConcurrentLinkedQueue? - // synchronized (this.txQueue) { - if (!txBuffer.hasRemaining()) { - txBuffer.clear(); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); - } - - // load ByteBuffer - // TODO: BufferOverflowException ? - txBuffer.put(payloadData.getData()); - - int seqControl = payloadData.getStreamNumber(); - - if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { - try { - // TODO : calling in same Thread. Is this ok? or - // dangerous? - this.associationListener.inValidStreamId(payloadData); - } catch (Exception e) { - logger.warn(e); - } - txBuffer.clear(); - txBuffer.flip(); - return false; - } - - if (this.sctpAssociation != null) { - msgInfo =MessageInfo.createOutgoing(sctpAssociation, peerSocketAddress, seqControl); - } else { - msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); - } - msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); - msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); - - logger.debug("write() - msgInfo: "+msgInfo); - txBuffer.flip(); - - this.doSend(); - - if (txBuffer.hasRemaining()) { - // Couldn't send all data. Lets return now and try to - // send - // this message in next cycle - return true; - } - return true; - } - return false; - } catch (IOException e) { - this.ioErrors++; - logger.error(String.format( - "IOException while trying to write to underlying socket for Association=%s IOError count=%d", - this.name, this.ioErrors), e); - logger.error("Internal send failed, retrying."); - this.close(); - onSendFailed(); - return false; - } catch (Exception ex) { - logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", - this.name, ex.getMessage()), ex); - return false; - } - } - - private int doSend() throws IOException { - return multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); - } - - protected void reconnect() { - try { - doInitiateConnectionSctp(); - } catch(Exception ex) { - logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); - scheduleConnect(); - } - } - - protected void close() { - try { - this.markAssociationDown(); - this.associationListener.onCommunicationShutdown(this); - } catch (Exception e) { - logger.error(String.format( - "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", - this.name), e); - } - } - - protected AbstractSelectableChannel getSocketChannel() { - if (this.multiplexer == null) { - return null; - } - return this.multiplexer.getSocketMultiChannel(); - } - - protected void scheduleConnect() { - if (!started.get()) { - logger.info("Association " + name + " is not started, no need to reconnect"); - return; - } - if (up.get()) { - logger.info("Associoation " + name + " is up, no need to reconnect"); - } else { - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() - + this.management.getConnectDelay())); - } - } - } - - private void doInitiateConnectionSctp() throws IOException { - this.multiplexer = management.getMultiChannelController().register(this); - this.multiplexer.send(getInitPayloadData(), null, this); - } - - protected void createworkerThreadTable(int maximumBooundStream) { - this.workerThreadTable = new int[maximumBooundStream]; - this.management.populateWorkerThread(this.workerThreadTable); - } - - @Override - public String toString() { - return "OneToManyAssociationImpl [hostAddress=" + hostAddress - + ", hostPort=" + hostPort + ", peerAddress=" + peerAddress - + ", peerPort=" + peerPort + ", name=" + name - + ", extraHostAddresses=" + Arrays.toString(extraHostAddresses) - + ", type=" + AssociationType.CLIENT + ", started=" + started + ", up=" + up - + ", management=" + management + ", msgInfo=" + msgInfo - + ", sctpAssociation=" + sctpAssociation + ", ipChannelType=" - + ipChannelType + ", assocInfo=" + assocInfo + ", multiplexer=" - + multiplexer + ", ioErrors=" + ioErrors + "]"; - } - - /** - * XML Serialization/Deserialization - */ - protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( - OneToManyAssociationImpl.class) { - - @Override - public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociationImpl association) - throws XMLStreamException { - association.name = xml.getAttribute(NAME, ""); - - association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); - association.hostPort = xml.getAttribute(HOST_PORT, 0); - - association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); - association.peerPort = xml.getAttribute(PEER_PORT, 0); - - //association.serverName = xml.getAttribute(SERVER_NAME, ""); - - int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); - association.extraHostAddresses = new String[extraHostAddressesSize]; - - for (int i = 0; i < extraHostAddressesSize; i++) { - association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); - } - try { - association.initDerivedFields(); - } catch (IOException e) { - logger.error("Unable to load association from XML: error while calculating derived fields", e); - } - } - - @Override - public void write(OneToManyAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) - throws XMLStreamException { - xml.setAttribute(NAME, association.name); - xml.setAttribute(ASSOCIATION_TYPE, AssociationType.CLIENT.getType()); - xml.setAttribute(HOST_ADDRESS, association.hostAddress); - xml.setAttribute(HOST_PORT, association.hostPort); - - xml.setAttribute(PEER_ADDRESS, association.peerAddress); - xml.setAttribute(PEER_PORT, association.peerPort); - - xml.setAttribute(SERVER_NAME, null); - xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode()); - - xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, - association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); - if (association.extraHostAddresses != null) { - for (String s : association.extraHostAddresses) { - xml.add(s, EXTRA_HOST_ADDRESS, String.class); - } - } - } - }; - - protected void onSendFailed() { - //if started and down then it means it is a CANT_START event and scheduleConnect must be called. - if (started.get() && !up.get()) { - logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); - scheduleConnect(); - } - } - - @Override - public void acceptAnonymousAssociation( - AssociationListener associationListener) throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void rejectAnonymousAssociation() { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void stopAnonymousAssociation() throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } + protected static final Logger logger = Logger.getLogger(OneToManyAssociationImpl.class); + + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String ASSOCIATION_TYPE = "assoctype"; + private static final String IPCHANNEL_TYPE = "ipChannelType"; + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + + private AssociationListener associationListener = null; + + private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); + + protected final OneToManyAssociationHandler associationHandler = new OneToManyAssociationHandler(); + + // Is the Association been started by management? + private AtomicBoolean started = new AtomicBoolean(false); + // Is the Association up (connection is established) + protected AtomicBoolean up = new AtomicBoolean(false); + + private int workerThreadTable[] = null; + + private volatile MessageInfo msgInfo; + + private volatile com.sun.nio.sctp.Association sctpAssociation; + private final IpChannelType ipChannelType = IpChannelType.SCTP; + + private OneToManyAssocMultiplexer multiplexer; + + /** + * Count of number of IO Errors occured. + */ + private volatile int ioErrors = 0; + + public OneToManyAssociationImpl() { + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + } + + /** + * Creating a CLIENT Association + * + * @param hostAddress + * @param hostPort + * @param peerAddress + * @param peerPort + * @param assocName + * @param ipChannelType + * @param extraHostAddresses + * @throws IOException + */ + public OneToManyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses) throws IOException { + this(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses, null); + } + + public OneToManyAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses, String secondaryPeerAddress) throws IOException { + super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + } + + public void start() throws Exception { + + if (this.associationListener == null) { + throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); + } + + if (started.getAndSet(true)) { + logger.warn("Association: " + this + " has been already STARTED"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStarted(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStarted", ee); + } + } + scheduleConnect(); + } + + public void stop() throws Exception { + if (!started.getAndSet(false)) { + logger.warn("Association: " + this + " has been already STOPPED"); + return; + } + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStopped(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStopped", ee); + } + } + + try { + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", this.name), e); + } + + } + + public IpChannelType getIpChannelType() { + return IpChannelType.SCTP; + } + + /** + * @return the associationListener + */ + public AssociationListener getAssociationListener() { + return associationListener; + } + + /** + * @param associationListener the associationListener to set + */ + public void setAssociationListener(AssociationListener associationListener) { + this.associationListener = associationListener; + } + + /** + * @return the assocName + */ + public String getName() { + return name; + } + + /** + * @return the associationType + */ + public AssociationType getAssociationType() { + return AssociationType.CLIENT; + } + + /** + * @return the started + */ + @Override + public boolean isStarted() { + return started.get(); + } + + @Override + public boolean isConnected() { + return started.get() && up.get(); + } + + @Override + public boolean isUp() { + return up.get(); + } + + protected void markAssociationUp() { + if (up.getAndSet(true)) { + if (logger.isDebugEnabled()) { + logger.debug("Association: " + this + " has been already marked UP"); + } + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationUp(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationUp", ee); + } + } + } + + protected void markAssociationDown() { + if (!up.getAndSet(false)) { + if (logger.isDebugEnabled()) { + logger.debug("Association: " + this + " has been already marked DOWN"); + } + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationDown(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationDown", ee); + } + } + } + + /** + * @return the hostAddress + */ + public String getHostAddress() { + return hostAddress; + } + + /** + * @return the hostPort + */ + public int getHostPort() { + return hostPort; + } + + /** + * @return the peerAddress + */ + public String getPeerAddress() { + return peerAddress; + } + + /** + * @return the peerPort + */ + public int getPeerPort() { + return peerPort; + } + + /** + * @return the serverName + */ + public String getServerName() { + return null; + } + + @Override + public String[] getExtraHostAddresses() { + return extraHostAddresses; + } + + /** + * @param management the management to set + */ + public void setManagement(MultiManagementImpl management) { + this.management = management; + } + + /** + * @param socketChannel the socketChannel to set + */ + protected void setSocketChannel(AbstractSelectableChannel socketChannel) { + // + } + + protected void readPayload(PayloadData payload) { + if (payload == null) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, payload), + e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + + ExecutorService executorService = this.management + .getExecutorService(this.workerThreadTable[payload.getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } + + public void send(PayloadData payloadData) throws Exception { + if (!started.get()) { + throw new Exception("send failed: Association is not started"); + } + multiplexer.send(payloadData, this.msgInfo, this); + } + + protected boolean writePayload(PayloadData payloadData, boolean initMsg) { + try { + + if (txBuffer.hasRemaining()) { + // All data wasn't sent in last doWrite. Try to send it now + this.doSend(); + } + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txBuffer.hasRemaining()) { + txBuffer.clear(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + logger.warn(e); + } + txBuffer.clear(); + txBuffer.flip(); + return false; + } + + if (initMsg) { + if (this.sctpAssociation != null) { + msgInfo = MessageInfo.createOutgoing(sctpAssociation, initSocketAddress, seqControl); + } else { + msgInfo = MessageInfo.createOutgoing(this.initSocketAddress, seqControl); + } + + } else { + if (this.sctpAssociation != null) { + msgInfo = MessageInfo.createOutgoing(sctpAssociation, peerSocketAddress, seqControl); + } else { + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + } + } + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + + logger.debug("write() - msgInfo: " + msgInfo); + txBuffer.flip(); + + this.doSend(); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return true; + } + return true; + } + return false; + } catch (IOException e) { + this.ioErrors++; + logger.error( + String.format("IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), + e); + logger.error("Internal send failed, retrying."); + this.close(); + onSendFailed(); + return false; + } catch (Exception ex) { + logger.error(String.format( + "Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", + this.name, ex.getMessage()), ex); + return false; + } + } + + private int doSend() throws IOException { + return multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + } + + protected void reconnect() { + try { + doInitiateConnectionSctp(); + } catch (Exception ex) { + logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); + scheduleConnect(); + } + } + + protected void close() { + try { + this.markAssociationDown(); + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", this.name), e); + } + } + + protected AbstractSelectableChannel getSocketChannel() { + if (this.multiplexer == null) { + return null; + } + return this.multiplexer.getSocketMultiChannel(); + } + + protected void scheduleConnect() { + if (!started.get()) { + logger.info("Association " + name + " is not started, no need to reconnect"); + return; + } + if (up.get()) { + logger.info("Associoation " + name + " is up, no need to reconnect"); + } else { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, + System.currentTimeMillis() + this.management.getConnectDelay())); + } + } + } + + private void doInitiateConnectionSctp() throws IOException { + this.multiplexer = management.getMultiChannelController().register(this); + this.multiplexer.send(getInitPayloadData(), null, this, true); + } + + protected void createworkerThreadTable(int maximumBooundStream) { + this.workerThreadTable = new int[maximumBooundStream]; + this.management.populateWorkerThread(this.workerThreadTable); + } + + @Override + public String toString() { + return "OneToManyAssociationImpl [hostAddress=" + hostAddress + ", hostPort=" + hostPort + ", peerAddress=" + + peerAddress + ", peerPort=" + peerPort + ", name=" + name + ", extraHostAddresses=" + + Arrays.toString(extraHostAddresses) + ", secondaryPeerAddress=" + this.secondaryPeerAddress + ", type=" + + AssociationType.CLIENT + ", started=" + started + ", up=" + up + ", management=" + management + ", msgInfo=" + + msgInfo + ", sctpAssociation=" + sctpAssociation + ", ipChannelType=" + ipChannelType + ", assocInfo=" + + assocInfo + ", multiplexer=" + multiplexer + ", ioErrors=" + ioErrors + "]"; + } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + OneToManyAssociationImpl.class) { + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, OneToManyAssociationImpl association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + + // association.serverName = xml.getAttribute(SERVER_NAME, ""); + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + try { + association.initDerivedFields(); + } catch (IOException e) { + logger.error("Unable to load association from XML: error while calculating derived fields", e); + } + } + + @Override + public void write(OneToManyAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + xml.setAttribute(ASSOCIATION_TYPE, AssociationType.CLIENT.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME, null); + xml.setAttribute(IPCHANNEL_TYPE, association.ipChannelType.getCode()); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; + + protected void onSendFailed() { + // if started and down then it means it is a CANT_START event and scheduleConnect must be called. + if (started.get() && !up.get()) { + logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); + switchInitSocketAddress(); + scheduleConnect(); + } + } + + @Override + public void acceptAnonymousAssociation(AssociationListener associationListener) throws Exception { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } + + @Override + public void rejectAnonymousAssociation() { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } + + @Override + public void stopAnonymousAssociation() throws Exception { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java index fbaf6eb..645a9f1 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationHandler.java @@ -14,156 +14,173 @@ /** * Handles notifications for OneToOneAssociationImpl objects. * - * @author balogh.gabor@alerant.hu + * @author balogh.gabor@alerant.hu * */ @SuppressWarnings("restriction") public class OneToOneAssociationHandler extends AbstractNotificationHandler { - private static final Logger logger = Logger.getLogger(OneToOneAssociationHandler.class); - - private volatile int maxInboundStreams = 1; - private volatile int maxOutboundStreams = 1; - - public OneToOneAssociationHandler() { - - } - - /** - * @return the maxInboundStreams - */ - public int getMaxInboundStreams() { - return maxInboundStreams; - } - - /** - * @return the maxOutboundStreams - */ - public int getMaxOutboundStreams() { - return maxOutboundStreams; - } - - @Override - public HandlerResult handleNotification(Notification arg0, OneToOneAssociationImpl arg1) { - if (arg0 instanceof AssociationChangeNotification) { - return handleNotification((AssociationChangeNotification) arg0, arg1); - } - if (arg0 instanceof ShutdownNotification) { - return handleNotification((ShutdownNotification) arg0, arg1); - } - if (arg0 instanceof SendFailedNotification) { - return handleNotification((SendFailedNotification) arg0, arg1); - } - if (arg0 instanceof PeerAddressChangeNotification) { - return handleNotification((PeerAddressChangeNotification) arg0, arg1); - } - return super.handleNotification(arg0, arg1); - } - - @Override - public HandlerResult handleNotification(AssociationChangeNotification not, OneToOneAssociationImpl association) { - - switch (not.event()) { - case COMM_UP: - //in case when comm is go online but the association has been already stopped COMM_UP event is sinked. - if (!association.isStarted()) { - association.silentlyShutdown(); - return HandlerResult.CONTINUE; - } - if (not.association() != null) { - this.maxOutboundStreams = not.association().maxOutboundStreams(); - this.maxInboundStreams = not.association().maxInboundStreams(); - } - - if (logger.isInfoEnabled()) { - logger.info(String.format("New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", - association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); - } - - association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); - - try { - association.markAssociationUp(); - association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, this.maxOutboundStreams); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", association.getName()), e); - } - return HandlerResult.CONTINUE; - case CANT_START: - logger.error(String.format("Can't start for Association=%s", association.getName())); - return HandlerResult.CONTINUE; - case COMM_LOST: - logger.warn(String.format("Communication lost for Association=%s", association.getName())); - - // Close the Socket - association.close(); - - association.scheduleConnect(); - try { - association.markAssociationDown(); - association.getAssociationListener().onCommunicationLost(association); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationLost on AssociationListener for Association=%s", association.getName()), e); - } - return HandlerResult.RETURN; - case RESTART: - logger.warn(String.format("Restart for Association=%s", association.getName())); - try { - association.getAssociationListener().onCommunicationRestart(association); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationRestart on AssociationListener for Association=%s", association.getName()), - e); - } - return HandlerResult.CONTINUE; - case SHUTDOWN: - if (logger.isInfoEnabled()) { - logger.info(String.format("Shutdown for Association=%s", association.getName())); - } - try { - association.markAssociationDown(); - association.getAssociationListener().onCommunicationShutdown(association); - association.scheduleConnect(); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", association.getName()), - e); - } - return HandlerResult.RETURN; - default: - logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); - break; - } - return HandlerResult.CONTINUE; - } - - @Override - public HandlerResult handleNotification(ShutdownNotification not, OneToOneAssociationImpl associtaion) { - if (logger.isInfoEnabled()) { - logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); - } - - try { - associtaion.markAssociationDown(); - associtaion.getAssociationListener().onCommunicationShutdown(associtaion); - } catch (Exception e) { - logger.error(String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", associtaion.getName()), e); - } - - return HandlerResult.RETURN; - } - - @Override - public HandlerResult handleNotification(SendFailedNotification notification, OneToOneAssociationImpl associtaion) { - logger.error(String.format("Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); + private static final Logger logger = Logger.getLogger(OneToOneAssociationHandler.class); + + private volatile int maxInboundStreams = 1; + private volatile int maxOutboundStreams = 1; + + public OneToOneAssociationHandler() { + + } + + /** + * @return the maxInboundStreams + */ + public int getMaxInboundStreams() { + return maxInboundStreams; + } + + /** + * @return the maxOutboundStreams + */ + public int getMaxOutboundStreams() { + return maxOutboundStreams; + } + + @Override + public HandlerResult handleNotification(Notification arg0, OneToOneAssociationImpl arg1) { + if (arg0 instanceof AssociationChangeNotification) { + return handleNotification((AssociationChangeNotification) arg0, arg1); + } + if (arg0 instanceof ShutdownNotification) { + return handleNotification((ShutdownNotification) arg0, arg1); + } + if (arg0 instanceof SendFailedNotification) { + return handleNotification((SendFailedNotification) arg0, arg1); + } + if (arg0 instanceof PeerAddressChangeNotification) { + return handleNotification((PeerAddressChangeNotification) arg0, arg1); + } + return super.handleNotification(arg0, arg1); + } + + @Override + public HandlerResult handleNotification(AssociationChangeNotification not, OneToOneAssociationImpl association) { + + switch (not.event()) { + case COMM_UP: + // in case when comm is go online but the association has been already stopped COMM_UP event is sinked. + if (!association.isStarted()) { + association.silentlyShutdown(); + return HandlerResult.CONTINUE; + } + if (not.association() != null) { + this.maxOutboundStreams = not.association().maxOutboundStreams(); + this.maxInboundStreams = not.association().maxInboundStreams(); + } + + if (logger.isInfoEnabled()) { + logger.info(String.format( + "New association setup for Association=%s with %d outbound streams, and %d inbound streams, sctp assoc is %s.\n", + association.getName(), this.maxOutboundStreams, this.maxInboundStreams, not.association())); + } + + association.createworkerThreadTable(Math.max(this.maxInboundStreams, this.maxOutboundStreams)); + + try { + association.markAssociationUp(); + association.getAssociationListener().onCommunicationUp(association, this.maxInboundStreams, + this.maxOutboundStreams); + } catch (Exception e) { + logger.error( + String.format("Exception while calling onCommunicationUp on AssociationListener for Association=%s", + association.getName()), + e); + } + return HandlerResult.CONTINUE; + case CANT_START: + logger.error(String.format("Can't start for Association=%s", association.getName())); + // Close the Socket + association.close(); + association.switchInitSocketAddress(); + association.scheduleConnect(); + return HandlerResult.CONTINUE; + case COMM_LOST: + logger.warn(String.format("Communication lost for Association=%s", association.getName())); + + // Close the Socket + association.close(); + + association.scheduleConnect(); + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationLost(association); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationLost on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.RETURN; + case RESTART: + logger.warn(String.format("Restart for Association=%s", association.getName())); + try { + association.getAssociationListener().onCommunicationRestart(association); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationRestart on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.CONTINUE; + case SHUTDOWN: + if (logger.isInfoEnabled()) { + logger.info(String.format("Shutdown for Association=%s", association.getName())); + } + try { + association.markAssociationDown(); + association.getAssociationListener().onCommunicationShutdown(association); + association.scheduleConnect(); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + association.getName()), e); + } + return HandlerResult.RETURN; + default: + logger.warn(String.format("Received unkown Event=%s for Association=%s", not.event(), association.getName())); + break; + } + return HandlerResult.CONTINUE; + } + + @Override + public HandlerResult handleNotification(ShutdownNotification not, OneToOneAssociationImpl associtaion) { + if (logger.isInfoEnabled()) { + logger.info(String.format("Association=%s SHUTDOWN", associtaion.getName())); + } + + try { + associtaion.markAssociationDown(); + associtaion.getAssociationListener().onCommunicationShutdown(associtaion); + } catch (Exception e) { + logger.error( + String.format("Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", + associtaion.getName()), + e); + } + + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(SendFailedNotification notification, OneToOneAssociationImpl associtaion) { + logger.error(String.format( + "Association=" + associtaion.getName() + " SendFailedNotification, errorCode=" + notification.errorCode())); associtaion.onSendFailed(); - return HandlerResult.RETURN; - } - - @Override - public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToOneAssociationImpl associtaion) { - if(logger.isEnabledFor(Priority.INFO)){ - logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), associtaion.getName())); - } - return HandlerResult.CONTINUE; - } + return HandlerResult.RETURN; + } + + @Override + public HandlerResult handleNotification(PeerAddressChangeNotification notification, OneToOneAssociationImpl associtaion) { + if (logger.isEnabledFor(Priority.INFO)) { + logger.info(String.format("Peer Address changed to=%s for Association=%s", notification.address(), + associtaion.getName())); + } + return HandlerResult.CONTINUE; + } } - diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 31a666b..0eb9414 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -1,6 +1,5 @@ package org.mobicents.protocols.sctp.multiclient; - import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; @@ -25,8 +24,8 @@ import com.sun.nio.sctp.SctpChannel; /** - * Implements a one-to-one type ManagableAssociation. Used when associations is peeled off the sctp multi channel sockets to - * a separate sctp socket channel. + * Implements a one-to-one type ManagableAssociation. Used when associations is peeled off the sctp multi channel sockets to a + * separate sctp socket channel. * * @author amit bhayani * @author balogh.gabor@alerant.hu @@ -35,805 +34,816 @@ @SuppressWarnings("restriction") public class OneToOneAssociationImpl extends ManageableAssociation { - protected static final Logger logger = Logger.getLogger(OneToOneAssociationImpl.class.getName()); - - private static final String NAME = "name"; - private static final String SERVER_NAME = "serverName"; - private static final String HOST_ADDRESS = "hostAddress"; - private static final String HOST_PORT = "hostPort"; - - private static final String PEER_ADDRESS = "peerAddress"; - private static final String PEER_PORT = "peerPort"; - - private static final String ASSOCIATION_TYPE = "assoctype"; - private static final String IPCHANNEL_TYPE = "ipChannelType"; - private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; - private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; - - private AssociationType type; - - private AssociationListener associationListener = null; - - protected final OneToOneAssociationHandler associationHandler = new OneToOneAssociationHandler(); - - // Is the Association been started by management? - private AtomicBoolean started = new AtomicBoolean(false); - // Is the Association up (connection is established) - protected AtomicBoolean up = new AtomicBoolean(false); - - private int workerThreadTable[] = null; - - private ConcurrentLinkedQueue txQueue = new ConcurrentLinkedQueue(); - - private SctpChannel socketChannelSctp; - - // The buffer into which we'll read data when it's available - private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); - private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); - - private volatile MessageInfo msgInfo; - - private OneToManyAssocMultiplexer multiplexer; - - /** - * Count of number of IO Errors occured. If this exceeds the maxIOErrors set - * in Management, socket will be closed and request to reopen the cosket - * will be initiated - */ - private volatile int ioErrors = 0; - - public OneToOneAssociationImpl() { - this.type = AssociationType.CLIENT; - // clean transmission buffer - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - - // clean receiver buffer - rxBuffer.clear(); - rxBuffer.rewind(); - rxBuffer.flip(); - } - - /** - * Creating a CLIENT Association - * - * @param hostAddress - * @param hostPort - * @param peerAddress - * @param peerPort - * @param assocName - * @param ipChannelType - * @param extraHostAddresses - * @throws IOException - */ - public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, String[] extraHostAddresses) throws IOException { - super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses); - - this.type = AssociationType.CLIENT; - // clean transmission buffer - txBuffer.clear(); - txBuffer.rewind(); - txBuffer.flip(); - - // clean receiver buffer - rxBuffer.clear(); - rxBuffer.rewind(); - rxBuffer.flip(); - } - - protected void start() throws Exception { - - if (this.associationListener == null) { - throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); - } - - if (started.getAndSet(true)) { - logger.warn("Association: "+this+" has been already STARTED"); - return; - } - - - scheduleConnect(); - - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationStarted(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationStarted", ee); - } - } - } - - /** - * Stops this Association. If the underlying SctpChannel is open, marks the - * channel for close - */ - protected void stop() throws Exception { - if (!started.getAndSet(false)) { - logger.warn("Association: "+this+" has been already STOPPED"); - return; - } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationStopped(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationStopped", ee); - } - } - - if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) { - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(getSocketChannel(), null, this, MultiChangeRequest.CLOSE, -1)); - } - - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); - } - } - - public IpChannelType getIpChannelType() { - return IpChannelType.SCTP; - } - - /** - * @return the associationListener - */ - public AssociationListener getAssociationListener() { - return associationListener; - } - - /** - * @param associationListener - * the associationListener to set - */ - public void setAssociationListener(AssociationListener associationListener) { - this.associationListener = associationListener; - } - - /** - * @return the assocName - */ - public String getName() { - return name; - } - - /** - * @return the associationType - */ - public AssociationType getAssociationType() { - return AssociationType.CLIENT; - } - - - /** - * @return the started - */ - @Override - public boolean isStarted() { - return started.get(); - } - - @Override - public boolean isConnected() { - return started.get() && up.get(); - } - - @Override - public boolean isUp() { - return up.get(); - } - - protected void markAssociationUp() { - if (up.getAndSet(true)) { - logger.debug("Association: "+this+" has been already marked UP"); - return; - } - - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationUp(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationUp", ee); - } - } - } - - protected void markAssociationDown() { - if (!up.getAndSet(false)) { - logger.debug("Association: "+this+" has been already marked DOWN"); - return; - } - for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { - try { - lstr.onAssociationDown(this); - } catch (Throwable ee) { - logger.error("Exception while invoking onAssociationDown", ee); - } - } - } - - /** - * @return the hostAddress - */ - public String getHostAddress() { - return hostAddress; - } - - /** - * @return the hostPort - */ - public int getHostPort() { - return hostPort; - } - - /** - * @return the peerAddress - */ - public String getPeerAddress() { - return peerAddress; - } - - /** - * @return the peerPort - */ - public int getPeerPort() { - return peerPort; - } - - /** - * @return the serverName - */ - public String getServerName() { - return null; - } - - @Override - public String[] getExtraHostAddresses() { - return extraHostAddresses; - } - - /** - * @param management - * the management to set - */ - protected void setManagement(MultiManagementImpl management) { - this.management = management; - } - - protected AbstractSelectableChannel getSocketChannel() { - return this.socketChannelSctp; - } - - protected void reconnect() { - try { - doInitiateConnectionSctp(); - } catch(Exception ex) { - logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); - scheduleConnect(); - } - } - - /** - * @param socketChannel - * the socketChannel to set - */ - protected void setSocketChannel(AbstractSelectableChannel socketChannel) { - this.socketChannelSctp = (SctpChannel) socketChannel; - } - - public void send(PayloadData payloadData) throws Exception { - try { - this.checkSocketIsOpen(); - - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - // Indicate we want the interest ops set changed - pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.CHANGEOPS, - SelectionKey.OP_WRITE)); - // And queue the data we want written - // TODO Do we need to synchronize ConcurrentLinkedQueue ? - // synchronized (this.txQueue) { - this.txQueue.add(payloadData); - } - // Finally, wake up our selecting thread so it can make the required - // changes - this.management.getSocketSelector().wakeup(); - } catch (Exception ex) { - logger.error("Error while sending payload data: " + ex.getMessage(), ex); - } - } - - private void checkSocketIsOpen() throws Exception { - if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() - || this.socketChannelSctp.association() == null) { - logger.warn(String.format( - "Underlying sctp channel doesn't open or doesn't have association for Association=%s", - this.name)); - throw new Exception(String.format( - "Underlying sctp channel doesn't open or doesn't have association for Association=%s", - this.name)); - } - } - - protected void read() { - try { - PayloadData payload; - - payload = this.doReadSctp(); - - if (payload == null) - return; - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); - } - - if (this.management.isSingleThread()) { - // If single thread model the listener should be called in the - // selector thread itself - try { - this.associationListener.onPayload(this, payload); - } catch (Exception e) { - logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, - payload), e); - } - } else { - - MultiWorker worker = new MultiWorker(this, this.associationListener, payload); - - ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload - .getStreamNumber()]); - try { - executorService.execute(worker); - } catch (RejectedExecutionException e) { - logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); - } catch (NullPointerException e) { - logger.error(String.format("NullPointerException while submitting %s", payload), e); - } catch (Exception e) { - logger.error(String.format("Exception while submitting %s", payload), e); - } - } - } catch (IOException e) { - this.ioErrors++; - logger.error(String.format( - "IOException while trying to read from underlying socket for Association=%s IOError count=%d", - this.name, this.ioErrors), e); - - if (this.ioErrors > this.management.getMaxIOErrors()) { - // Close this socket - this.close(); - - // retry to connect after delay - this.scheduleConnect(); - } - } - } - - private PayloadData doReadSctp() throws IOException { - rxBuffer.clear(); - MessageInfo messageInfo = this.socketChannelSctp.receive(rxBuffer, this, this.associationHandler); - - if (messageInfo == null) { - if (logger.isDebugEnabled()) { - logger.debug(String.format(" messageInfo is null for Association=%s", this.name)); - } - return null; - } - - int len = messageInfo.bytes(); - if (len == -1) { - logger.error(String.format("Rx -1 while trying to read from underlying socket for Association=%s ", - this.name)); - this.close(); - this.scheduleConnect(); - return null; - } - - rxBuffer.flip(); - byte[] data = new byte[len]; - rxBuffer.get(data); - rxBuffer.clear(); - - PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), - messageInfo.payloadProtocolID(), messageInfo.streamNumber()); - - return payload; - } - - protected void write(SelectionKey key) { - try { - - if (txBuffer.hasRemaining()) { - // All data wasn't sent in last doWrite. Try to send it now - this.doSend(); - } - - // TODO Do we need to synchronize ConcurrentLinkedQueue? - // synchronized (this.txQueue) { - if (!txQueue.isEmpty() && !txBuffer.hasRemaining()) { - while (!txQueue.isEmpty()) { - // Lets read all the messages in txQueue and send - - txBuffer.clear(); - PayloadData payloadData = txQueue.poll(); - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); - } - - // load ByteBuffer - // TODO: BufferOverflowException ? - txBuffer.put(payloadData.getData()); - - int seqControl = payloadData.getStreamNumber(); - - if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { - try { - // TODO : calling in same Thread. Is this ok? or - // dangerous? - this.associationListener.inValidStreamId(payloadData); - } catch (Exception e) { - - } - txBuffer.clear(); - txBuffer.flip(); - continue; - } - - msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); - msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); - msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); - - txBuffer.flip(); - - this.doSend(); - - if (txBuffer.hasRemaining()) { - // Couldn't send all data. Lets return now and try to - // send - // this message in next cycle - return; - } - - }// end of while - } - - if (txQueue.isEmpty()) { - // We wrote away all data, so we're no longer interested - // in writing on this socket. Switch back to waiting for - // data. - key.interestOps(SelectionKey.OP_READ); - } - - } catch (IOException e) { - this.ioErrors++; - logger.error(String.format( - "IOException while trying to write to underlying socket for Association=%s IOError count=%d", - this.name, this.ioErrors), e); - - if (this.ioErrors > this.management.getMaxIOErrors()) { - // Close this socket - this.close(); - - // retry to connect after delay - this.scheduleConnect(); - } - }// try-catch - } - - private int doSend() throws IOException { - return this.socketChannelSctp.send(txBuffer, msgInfo); - } - - protected boolean isOpen() { - return this.getSocketChannel() != null && this.getSocketChannel().isOpen(); - } - - protected void close() { - if (this.getSocketChannel() != null) { - try { - this.getSocketChannel().close(); - if (logger.isDebugEnabled()) { - logger.debug("close() - socketChannel is closed for association=" + getName()); - } - } catch (Exception e) { - logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); - } - } - if (this.up.get()) { - try { - this.markAssociationDown(); - this.associationListener.onCommunicationShutdown(this); - } catch (Exception e) { - logger.error(String.format( - "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", - this.name), e); - } - } - // Finally clear the txQueue - if (this.txQueue.size() > 0) { - logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", - this.name, this.txQueue.size())); - } - this.txQueue.clear(); - } - - protected void scheduleConnect() { - if (this.getAssociationType() == AssociationType.CLIENT) { - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, System.currentTimeMillis() - + this.management.getConnectDelay())); - } - } - } - - protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { - //if association is stopped, channel wont be registered. - if (!started.get()) { - if (logger.isInfoEnabled()) { - logger.info("Branching a stopped association, channel wont be registered to the selector."); - } - //set channel to able to close later - this.socketChannelSctp = sctpChannel; - this.management = management; - } else { - FastList pendingChanges = this.management.getPendingChanges(); - synchronized (pendingChanges) { - //setting the channel must be synchronized - this.socketChannelSctp = sctpChannel; - this.management = management; - pendingChanges.add(new MultiChangeRequest(sctpChannel, null, this, MultiChangeRequest.REGISTER, - SelectionKey.OP_WRITE|SelectionKey.OP_READ)); - } - }; - } - - private void doInitiateConnectionSctp() throws IOException { - // reset the ioErrors - this.ioErrors = 0; - this.multiplexer = management.getMultiChannelController().register(this); - this.multiplexer.send(getInitPayloadData(), null, this); - } - - protected void createworkerThreadTable(int maximumBooundStream) { - this.workerThreadTable = new int[maximumBooundStream]; - this.management.populateWorkerThread(this.workerThreadTable); - } - - /* - * (non-Javadoc) - * - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - - StringBuilder sb = new StringBuilder(); - sb.append("Association [name=").append(this.name).append(", started=").append(started.get()).append(", up=").append(up) - .append(", associationType=").append(this.type) - .append(", ipChannelType=").append("SCTP").append(", hostAddress=") - .append(this.hostAddress).append(", hostPort=").append(this.hostPort).append(", peerAddress=") - .append(this.peerAddress).append(", peerPort=").append(this.peerPort).append(", serverName=") - .append(""); - - sb.append(", extraHostAddress=["); - - if (this.extraHostAddresses != null) { - for (int i = 0; i < this.extraHostAddresses.length; i++) { - String extraHostAddress = this.extraHostAddresses[i]; - sb.append(extraHostAddress); - sb.append(", "); - } - } - - sb.append("]]"); - - return sb.toString(); - } - - /** - * XML Serialization/Deserialization - */ - protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( - OneToOneAssociationImpl.class) { - - @Override - public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationImpl association) - throws XMLStreamException { - association.name = xml.getAttribute(NAME, ""); - association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); - association.hostPort = xml.getAttribute(HOST_PORT, 0); - - association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); - association.peerPort = xml.getAttribute(PEER_PORT, 0); - - - int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); - association.extraHostAddresses = new String[extraHostAddressesSize]; - - for (int i = 0; i < extraHostAddressesSize; i++) { - association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); - } - try { - association.initDerivedFields(); - } catch (IOException e) { - logger.error("Unable to load association from XML: error while calculating derived fields", e); - } - } - - @Override - public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) - throws XMLStreamException { - xml.setAttribute(NAME, association.name); - xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); - xml.setAttribute(HOST_ADDRESS, association.hostAddress); - xml.setAttribute(HOST_PORT, association.hostPort); - - xml.setAttribute(PEER_ADDRESS, association.peerAddress); - xml.setAttribute(PEER_PORT, association.peerPort); - - xml.setAttribute(SERVER_NAME,""); - xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); - - xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, - association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); - if (association.extraHostAddresses != null) { - for (String s : association.extraHostAddresses) { - xml.add(s, EXTRA_HOST_ADDRESS, String.class); - } - } - } - }; - - @Override - protected void readPayload(PayloadData payload) { - if (payload == null) { - return; - } - - if (logger.isDebugEnabled()) { - logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); - } - - if (this.management.isSingleThread()) { - try { - this.associationListener.onPayload(this, payload); - } catch (Exception e) { - logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, - payload), e); - } - } else { - MultiWorker worker = new MultiWorker(this, this.associationListener, payload); - ExecutorService executorService = this.management.getExecutorService(this.workerThreadTable[payload - .getStreamNumber()]); - try { - executorService.execute(worker); - } catch (RejectedExecutionException e) { - logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); - } catch (NullPointerException e) { - logger.error(String.format("NullPointerException while submitting %s", payload), e); - } catch (Exception e) { - logger.error(String.format("Exception while submitting %s", payload), e); - } - } - } - - @Override - protected boolean writePayload(PayloadData payloadData) { - try { - - if (txBuffer.hasRemaining()) { - multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); - } - // TODO Do we need to synchronize ConcurrentLinkedQueue? - // synchronized (this.txQueue) { - if (!txBuffer.hasRemaining()) { - txBuffer.clear(); - if (logger.isDebugEnabled()) { - logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); - } - - // load ByteBuffer - // TODO: BufferOverflowException ? - txBuffer.put(payloadData.getData()); - - int seqControl = payloadData.getStreamNumber(); - - if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { - try { - // TODO : calling in same Thread. Is this ok? or - // dangerous? - this.associationListener.inValidStreamId(payloadData); - } catch (Exception e) { - logger.warn(e); - } - txBuffer.clear(); - txBuffer.flip(); - return false; - } - - msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); - - msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); - msgInfo.complete(payloadData.isComplete()); - msgInfo.unordered(payloadData.isUnordered()); - - logger.debug("write() - msgInfo: "+msgInfo); - txBuffer.flip(); - - multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); - - if (txBuffer.hasRemaining()) { - // Couldn't send all data. Lets return now and try to - // send - // this message in next cycle - return true; - } - return true; - } - return false; - } catch (IOException e) { - this.ioErrors++; - logger.error(String.format( - "IOException while trying to write to underlying socket for Association=%s IOError count=%d", - this.name, this.ioErrors), e); - logger.error("Internal send failed, retrying."); - this.close(); - onSendFailed(); - return false; - } catch (Exception ex) { - logger.error(String.format("Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", - this.name, ex.getMessage()), ex); - return false; - } - } - - protected void onSendFailed() { - //if started and down then it means it is a CANT_START event and scheduleConnect must be called. - if (started.get() && !up.get()) { - logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); - scheduleConnect(); - } - } - - //called when COMM_UP event arrived after association was stopped. - protected void silentlyShutdown() { - if (!started.get()) { - if (logger.isInfoEnabled()) { - logger.info("Association=" + getName() + " has been already stopped when COMM_UP event arrived, closing sctp association without notifying any listeners."); - } - if (this.getSocketChannel() != null) { - try { - this.getSocketChannel().close(); - if (logger.isDebugEnabled()) { - logger.debug("close() - socketChannel is closed for association=" + getName()); - } - } catch (Exception e) { - logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); - } - } - } - } - - @Override - public void acceptAnonymousAssociation( - AssociationListener associationListener) throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void rejectAnonymousAssociation() { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } - - @Override - public void stopAnonymousAssociation() throws Exception { - throw new UnsupportedOperationException(this.getClass()+" class does not implement SERVER type Associations!"); - } + protected static final Logger logger = Logger.getLogger(OneToOneAssociationImpl.class.getName()); + + private static final String NAME = "name"; + private static final String SERVER_NAME = "serverName"; + private static final String HOST_ADDRESS = "hostAddress"; + private static final String HOST_PORT = "hostPort"; + + private static final String PEER_ADDRESS = "peerAddress"; + private static final String PEER_PORT = "peerPort"; + + private static final String ASSOCIATION_TYPE = "assoctype"; + private static final String IPCHANNEL_TYPE = "ipChannelType"; + private static final String EXTRA_HOST_ADDRESS = "extraHostAddress"; + private static final String EXTRA_HOST_ADDRESS_SIZE = "extraHostAddresseSize"; + + private AssociationType type; + + private AssociationListener associationListener = null; + + protected final OneToOneAssociationHandler associationHandler = new OneToOneAssociationHandler(); + + // Is the Association been started by management? + private AtomicBoolean started = new AtomicBoolean(false); + // Is the Association up (connection is established) + protected AtomicBoolean up = new AtomicBoolean(false); + + private int workerThreadTable[] = null; + + private ConcurrentLinkedQueue txQueue = new ConcurrentLinkedQueue(); + + private SctpChannel socketChannelSctp; + + // The buffer into which we'll read data when it's available + private ByteBuffer rxBuffer = ByteBuffer.allocateDirect(8192); + private ByteBuffer txBuffer = ByteBuffer.allocateDirect(8192); + + private volatile MessageInfo msgInfo; + + private OneToManyAssocMultiplexer multiplexer; + + /** + * Count of number of IO Errors occured. If this exceeds the maxIOErrors set in Management, socket will be closed and + * request to reopen the cosket will be initiated + */ + private volatile int ioErrors = 0; + + public OneToOneAssociationImpl() { + this.type = AssociationType.CLIENT; + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + + // clean receiver buffer + rxBuffer.clear(); + rxBuffer.rewind(); + rxBuffer.flip(); + } + + /** + * Creating a CLIENT Association + * + * @param hostAddress + * @param hostPort + * @param peerAddress + * @param peerPort + * @param assocName + * @param ipChannelType + * @param extraHostAddresses + * @throws IOException + */ + public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses) throws IOException { + this(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses, null); + } + + public OneToOneAssociationImpl(String hostAddress, int hostPort, String peerAddress, int peerPort, String assocName, + String[] extraHostAddresses, String secondaryPeerAddress) throws IOException { + super(hostAddress, hostPort, peerAddress, peerPort, assocName, extraHostAddresses, secondaryPeerAddress); + this.type = AssociationType.CLIENT; + // clean transmission buffer + txBuffer.clear(); + txBuffer.rewind(); + txBuffer.flip(); + + // clean receiver buffer + rxBuffer.clear(); + rxBuffer.rewind(); + rxBuffer.flip(); + } + + protected void start() throws Exception { + + if (this.associationListener == null) { + throw new NullPointerException(String.format("AssociationListener is null for Associatoion=%s", this.name)); + } + + if (started.getAndSet(true)) { + logger.warn("Association: " + this + " has been already STARTED"); + return; + } + + scheduleConnect(); + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStarted(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStarted", ee); + } + } + } + + /** + * Stops this Association. If the underlying SctpChannel is open, marks the channel for close + */ + protected void stop() throws Exception { + if (logger.isDebugEnabled()) { + logger.debug("stopped called on association=" + this); + } + if (!started.getAndSet(false)) { + logger.warn("Association: " + this + " has been already STOPPED"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationStopped(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationStopped", ee); + } + } + + if (this.getSocketChannel() != null && this.getSocketChannel().isOpen()) { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(getSocketChannel(), null, this, MultiChangeRequest.CLOSE, -1)); + } + + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } + } + + public IpChannelType getIpChannelType() { + return IpChannelType.SCTP; + } + + /** + * @return the associationListener + */ + public AssociationListener getAssociationListener() { + return associationListener; + } + + /** + * @param associationListener the associationListener to set + */ + public void setAssociationListener(AssociationListener associationListener) { + this.associationListener = associationListener; + } + + /** + * @return the assocName + */ + public String getName() { + return name; + } + + /** + * @return the associationType + */ + public AssociationType getAssociationType() { + return AssociationType.CLIENT; + } + + /** + * @return the started + */ + @Override + public boolean isStarted() { + return started.get(); + } + + @Override + public boolean isConnected() { + return started.get() && up.get(); + } + + @Override + public boolean isUp() { + return up.get(); + } + + protected void markAssociationUp() { + if (up.getAndSet(true)) { + logger.debug("Association: " + this + " has been already marked UP"); + return; + } + + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationUp(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationUp", ee); + } + } + } + + protected void markAssociationDown() { + if (!up.getAndSet(false)) { + logger.debug("Association: " + this + " has been already marked DOWN"); + return; + } + for (ManagementEventListener lstr : this.management.getManagementEventListeners()) { + try { + lstr.onAssociationDown(this); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationDown", ee); + } + } + } + + /** + * @return the hostAddress + */ + public String getHostAddress() { + return hostAddress; + } + + /** + * @return the hostPort + */ + public int getHostPort() { + return hostPort; + } + + /** + * @return the peerAddress + */ + public String getPeerAddress() { + return peerAddress; + } + + /** + * @return the peerPort + */ + public int getPeerPort() { + return peerPort; + } + + /** + * @return the serverName + */ + public String getServerName() { + return null; + } + + @Override + public String[] getExtraHostAddresses() { + return extraHostAddresses; + } + + /** + * @param management the management to set + */ + protected void setManagement(MultiManagementImpl management) { + this.management = management; + } + + protected AbstractSelectableChannel getSocketChannel() { + return this.socketChannelSctp; + } + + protected void reconnect() { + try { + doInitiateConnectionSctp(); + } catch (Exception ex) { + logger.warn("Error while trying to reconnect association[" + this.getName() + "]: " + ex.getMessage(), ex); + scheduleConnect(); + } + } + + /** + * @param socketChannel the socketChannel to set + */ + protected void setSocketChannel(AbstractSelectableChannel socketChannel) { + this.socketChannelSctp = (SctpChannel) socketChannel; + } + + public void send(PayloadData payloadData) throws Exception { + try { + this.checkSocketIsOpen(); + + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + // Indicate we want the interest ops set changed + pendingChanges.add(new MultiChangeRequest(this.getSocketChannel(), null, this, MultiChangeRequest.CHANGEOPS, + SelectionKey.OP_WRITE)); + // And queue the data we want written + // TODO Do we need to synchronize ConcurrentLinkedQueue ? + // synchronized (this.txQueue) { + this.txQueue.add(payloadData); + } + // Finally, wake up our selecting thread so it can make the required + // changes + this.management.getSocketSelector().wakeup(); + } catch (Exception ex) { + logger.error("Error while sending payload data: " + ex.getMessage(), ex); + } + } + + private void checkSocketIsOpen() throws Exception { + if (!started.get() || this.socketChannelSctp == null || !this.socketChannelSctp.isOpen() + || this.socketChannelSctp.association() == null) { + logger.warn(String.format("Underlying sctp channel doesn't open or doesn't have association for Association=%s", + this.name)); + throw new Exception(String + .format("Underlying sctp channel doesn't open or doesn't have association for Association=%s", this.name)); + } + } + + protected void read() { + try { + PayloadData payload; + + payload = this.doReadSctp(); + + if (payload == null) + return; + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + // If single thread model the listener should be called in the + // selector thread itself + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error( + String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, payload), e); + } + } else { + + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + + ExecutorService executorService = this.management + .getExecutorService(this.workerThreadTable[payload.getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } catch (IOException e) { + this.ioErrors++; + logger.error( + String.format("IOException while trying to read from underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), + e); + + if (this.ioErrors > this.management.getMaxIOErrors()) { + // Close this socket + this.close(); + + // retry to connect after delay + this.scheduleConnect(); + } + } + } + + private PayloadData doReadSctp() throws IOException { + rxBuffer.clear(); + MessageInfo messageInfo = this.socketChannelSctp.receive(rxBuffer, this, this.associationHandler); + + if (messageInfo == null) { + if (logger.isDebugEnabled()) { + logger.debug(String.format(" messageInfo is null for Association=%s", this.name)); + } + return null; + } + + int len = messageInfo.bytes(); + if (len == -1) { + logger.error(String.format("Rx -1 while trying to read from underlying socket for Association=%s ", this.name)); + this.close(); + this.scheduleConnect(); + return null; + } + + rxBuffer.flip(); + byte[] data = new byte[len]; + rxBuffer.get(data); + rxBuffer.clear(); + + PayloadData payload = new PayloadData(len, data, messageInfo.isComplete(), messageInfo.isUnordered(), + messageInfo.payloadProtocolID(), messageInfo.streamNumber()); + + return payload; + } + + protected void write(SelectionKey key) { + try { + + if (txBuffer.hasRemaining()) { + // All data wasn't sent in last doWrite. Try to send it now + this.doSend(); + } + + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txQueue.isEmpty() && !txBuffer.hasRemaining()) { + while (!txQueue.isEmpty()) { + // Lets read all the messages in txQueue and send + + txBuffer.clear(); + PayloadData payloadData = txQueue.poll(); + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + + } + txBuffer.clear(); + txBuffer.flip(); + continue; + } + + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + + txBuffer.flip(); + + this.doSend(); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return; + } + + } // end of while + } + + if (txQueue.isEmpty()) { + // We wrote away all data, so we're no longer interested + // in writing on this socket. Switch back to waiting for + // data. + key.interestOps(SelectionKey.OP_READ); + } + + } catch (IOException e) { + this.ioErrors++; + logger.error( + String.format("IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), + e); + + if (this.ioErrors > this.management.getMaxIOErrors()) { + // Close this socket + this.close(); + + // retry to connect after delay + this.scheduleConnect(); + } + } // try-catch + } + + private int doSend() throws IOException { + return this.socketChannelSctp.send(txBuffer, msgInfo); + } + + protected boolean isOpen() { + return this.getSocketChannel() != null && this.getSocketChannel().isOpen(); + } + + protected void close() { + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + if (logger.isDebugEnabled()) { + logger.debug("close() - socketChannel is closed for association=" + getName()); + } + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + } + if (this.up.get()) { + try { + this.markAssociationDown(); + this.associationListener.onCommunicationShutdown(this); + } catch (Exception e) { + logger.error(String.format( + "Exception while calling onCommunicationShutdown on AssociationListener for Association=%s", this.name), + e); + } + } + // Finally clear the txQueue + if (this.txQueue.size() > 0) { + logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", + this.name, this.txQueue.size())); + } + this.txQueue.clear(); + } + + protected void scheduleConnect() { + if (this.getAssociationType() == AssociationType.CLIENT) { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + pendingChanges.add(new MultiChangeRequest(null, this, MultiChangeRequest.CONNECT, + System.currentTimeMillis() + this.management.getConnectDelay())); + } + } + } + + protected void branch(SctpChannel sctpChannel, MultiManagementImpl management) { + // if association is stopped, channel wont be registered. + if (!started.get()) { + if (logger.isInfoEnabled()) { + logger.info("Branching a stopped association, channel wont be registered to the selector."); + } + // set channel to able to close later + this.socketChannelSctp = sctpChannel; + this.management = management; + } else { + FastList pendingChanges = this.management.getPendingChanges(); + synchronized (pendingChanges) { + // setting the channel must be synchronized + this.socketChannelSctp = sctpChannel; + this.management = management; + pendingChanges.add(new MultiChangeRequest(sctpChannel, null, this, MultiChangeRequest.REGISTER, + SelectionKey.OP_WRITE | SelectionKey.OP_READ)); + } + } + ; + } + + private void doInitiateConnectionSctp() throws IOException { + // reset the ioErrors + this.ioErrors = 0; + this.multiplexer = management.getMultiChannelController().register(this); + this.multiplexer.send(getInitPayloadData(), null, this, true); + } + + protected void createworkerThreadTable(int maximumBooundStream) { + this.workerThreadTable = new int[maximumBooundStream]; + this.management.populateWorkerThread(this.workerThreadTable); + } + + /* + * (non-Javadoc) + * + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + + StringBuilder sb = new StringBuilder(); + sb.append("Association [name=").append(this.name).append(", started=").append(started.get()).append(", up=").append(up) + .append(", associationType=").append(this.type).append(", ipChannelType=").append("SCTP") + .append(", hostAddress=").append(this.hostAddress).append(", hostPort=").append(this.hostPort) + .append(", peerAddress=").append(this.peerAddress).append(", peerPort=").append(this.peerPort) + .append(", serverName=").append(""); + + sb.append(", extraHostAddress=["); + + if (this.extraHostAddresses != null) { + for (int i = 0; i < this.extraHostAddresses.length; i++) { + String extraHostAddress = this.extraHostAddresses[i]; + sb.append(extraHostAddress); + sb.append(", "); + } + } + sb.append(" secondaryPeerAddress=").append(this.secondaryPeerAddress); + sb.append("]]"); + + return sb.toString(); + } + + /** + * XML Serialization/Deserialization + */ + protected static final XMLFormat ASSOCIATION_XML = new XMLFormat( + OneToOneAssociationImpl.class) { + + @Override + public void read(javolution.xml.XMLFormat.InputElement xml, OneToOneAssociationImpl association) + throws XMLStreamException { + association.name = xml.getAttribute(NAME, ""); + association.hostAddress = xml.getAttribute(HOST_ADDRESS, ""); + association.hostPort = xml.getAttribute(HOST_PORT, 0); + + association.peerAddress = xml.getAttribute(PEER_ADDRESS, ""); + association.peerPort = xml.getAttribute(PEER_PORT, 0); + + int extraHostAddressesSize = xml.getAttribute(EXTRA_HOST_ADDRESS_SIZE, 0); + association.extraHostAddresses = new String[extraHostAddressesSize]; + + for (int i = 0; i < extraHostAddressesSize; i++) { + association.extraHostAddresses[i] = xml.get(EXTRA_HOST_ADDRESS, String.class); + } + try { + association.initDerivedFields(); + } catch (IOException e) { + logger.error("Unable to load association from XML: error while calculating derived fields", e); + } + } + + @Override + public void write(OneToOneAssociationImpl association, javolution.xml.XMLFormat.OutputElement xml) + throws XMLStreamException { + xml.setAttribute(NAME, association.name); + xml.setAttribute(ASSOCIATION_TYPE, association.type.getType()); + xml.setAttribute(HOST_ADDRESS, association.hostAddress); + xml.setAttribute(HOST_PORT, association.hostPort); + + xml.setAttribute(PEER_ADDRESS, association.peerAddress); + xml.setAttribute(PEER_PORT, association.peerPort); + + xml.setAttribute(SERVER_NAME, ""); + xml.setAttribute(IPCHANNEL_TYPE, IpChannelType.SCTP); + + xml.setAttribute(EXTRA_HOST_ADDRESS_SIZE, + association.extraHostAddresses != null ? association.extraHostAddresses.length : 0); + if (association.extraHostAddresses != null) { + for (String s : association.extraHostAddresses) { + xml.add(s, EXTRA_HOST_ADDRESS, String.class); + } + } + } + }; + + @Override + protected void readPayload(PayloadData payload) { + if (payload == null) { + return; + } + + if (logger.isDebugEnabled()) { + logger.debug(String.format("Rx : Ass=%s %s", this.name, payload)); + } + + if (this.management.isSingleThread()) { + try { + this.associationListener.onPayload(this, payload); + } catch (Exception e) { + logger.error(String.format("Error while calling Listener for Association=%s.Payload=%s", this.name, payload), + e); + } + } else { + MultiWorker worker = new MultiWorker(this, this.associationListener, payload); + ExecutorService executorService = this.management + .getExecutorService(this.workerThreadTable[payload.getStreamNumber()]); + try { + executorService.execute(worker); + } catch (RejectedExecutionException e) { + logger.error(String.format("Rejected %s as Executors is shutdown", payload), e); + } catch (NullPointerException e) { + logger.error(String.format("NullPointerException while submitting %s", payload), e); + } catch (Exception e) { + logger.error(String.format("Exception while submitting %s", payload), e); + } + } + } + + @Override + protected boolean writePayload(PayloadData payloadData, boolean initMsg) { + try { + + if (txBuffer.hasRemaining()) { + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + } + // TODO Do we need to synchronize ConcurrentLinkedQueue? + // synchronized (this.txQueue) { + if (!txBuffer.hasRemaining()) { + txBuffer.clear(); + if (logger.isDebugEnabled()) { + logger.debug(String.format("Tx : Ass=%s %s", this.name, payloadData)); + } + + // load ByteBuffer + // TODO: BufferOverflowException ? + txBuffer.put(payloadData.getData()); + + int seqControl = payloadData.getStreamNumber(); + + if (seqControl < 0 || seqControl >= this.associationHandler.getMaxOutboundStreams()) { + try { + // TODO : calling in same Thread. Is this ok? or + // dangerous? + this.associationListener.inValidStreamId(payloadData); + } catch (Exception e) { + logger.warn(e); + } + txBuffer.clear(); + txBuffer.flip(); + return false; + } + + if (initMsg) { + msgInfo = MessageInfo.createOutgoing(this.initSocketAddress, seqControl); + } else { + msgInfo = MessageInfo.createOutgoing(this.peerSocketAddress, seqControl); + } + + msgInfo.payloadProtocolID(payloadData.getPayloadProtocolId()); + msgInfo.complete(payloadData.isComplete()); + msgInfo.unordered(payloadData.isUnordered()); + + logger.debug("write() - msgInfo: " + msgInfo); + txBuffer.flip(); + + multiplexer.getSocketMultiChannel().send(txBuffer, msgInfo); + + if (txBuffer.hasRemaining()) { + // Couldn't send all data. Lets return now and try to + // send + // this message in next cycle + return true; + } + return true; + } + return false; + } catch (IOException e) { + this.ioErrors++; + logger.error( + String.format("IOException while trying to write to underlying socket for Association=%s IOError count=%d", + this.name, this.ioErrors), + e); + logger.error("Internal send failed, retrying."); + this.close(); + onSendFailed(); + return false; + } catch (Exception ex) { + logger.error(String.format( + "Unexpected exception has been caught while trying to write SCTP socketChanel for Association=%s: %s", + this.name, ex.getMessage()), ex); + return false; + } + } + + protected void onSendFailed() { + // if started and down then it means it is a CANT_START event and scheduleConnect must be called. + if (started.get() && !up.get()) { + logger.warn("Association=" + getName() + " CANT_START, trying to reconnect..."); + switchInitSocketAddress(); + scheduleConnect(); + } + } + + // called when COMM_UP event arrived after association was stopped. + protected void silentlyShutdown() { + if (!started.get()) { + if (logger.isInfoEnabled()) { + logger.info("Association=" + getName() + + " has been already stopped when COMM_UP event arrived, closing sctp association without notifying any listeners."); + } + if (this.getSocketChannel() != null) { + try { + this.getSocketChannel().close(); + if (logger.isDebugEnabled()) { + logger.debug("close() - socketChannel is closed for association=" + getName()); + } + } catch (Exception e) { + logger.error(String.format("Exception while closing the SctpScoket for Association=%s", this.name), e); + } + } + } + } + + @Override + public void acceptAnonymousAssociation(AssociationListener associationListener) throws Exception { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } + + @Override + public void rejectAnonymousAssociation() { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } + + @Override + public void stopAnonymousAssociation() throws Exception { + throw new UnsupportedOperationException(this.getClass() + " class does not implement SERVER type Associations!"); + } + + void onCantStart() { + this.close(); + + } } From 770d8f2e08fb1ebbb3dc0744e362eb665452c84c Mon Sep 17 00:00:00 2001 From: Gabor Balogh Date: Tue, 9 Aug 2016 15:44:03 +0200 Subject: [PATCH 25/28] Fix the mem leak caused by the pending association list in OneToManyAssocMultiplexer --- .../multiclient/MultiChannelController.java | 159 +++++++++--------- .../OneToManyAssocMultiplexer.java | 10 ++ .../multiclient/OneToManyAssociationImpl.java | 6 + .../multiclient/OneToOneAssociationImpl.java | 14 +- 4 files changed, 108 insertions(+), 81 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java index c52d9bd..fe0b503 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiChannelController.java @@ -1,4 +1,4 @@ -package org.mobicents.protocols.sctp.multiclient; +package org.mobicents.protocols.sctp.multiclient; import java.io.IOException; import java.util.ArrayList; @@ -8,93 +8,94 @@ import org.apache.log4j.Logger; /** - * Stores and manages OneToManyAssocMultiplexer and ManageableAssociation objects of a SCTP stack (MultiManagementImpl instance). + * Stores and manages OneToManyAssocMultiplexer and ManageableAssociation objects of a SCTP stack (MultiManagementImpl + * instance). * * @author balogh.gabor@alerant.hu * */ public class MultiChannelController { - private static final Logger logger = Logger.getLogger(MultiChannelController.class); - - private MultiManagementImpl management; + private static final Logger logger = Logger.getLogger(MultiChannelController.class); - private final HashMap> multiplexers = new HashMap>(); - - public MultiChannelController(MultiManagementImpl management) { - super(); - this.management = management; - } + private MultiManagementImpl management; - private OneToManyAssocMultiplexer findMultiplexerByHostAddrInfo(OneToManyAssociationImpl.HostAddressInfo hostAddressInfo) { - OneToManyAssocMultiplexer ret = null; - if (logger.isDebugEnabled()) { - logger.debug("Number of multiplexers: "+multiplexers.size()); - } - ArrayList mList = multiplexers.get(hostAddressInfo.getHostPort()); - if (mList == null || mList.isEmpty()) { - if (logger.isDebugEnabled()) { - logger.debug("No multiplexers found for local port: "+hostAddressInfo.getHostPort()); - } - } else { - for (OneToManyAssocMultiplexer am: mList) { - if (am.getHostAddressInfo().matches(hostAddressInfo)) { - ret = am; - } - } - } - if (logger.isDebugEnabled()) { - logger.debug("findMultiplexerByHostAddr: "+hostAddressInfo+" returns: "+ret); - } - return ret; - } + private final HashMap> multiplexers = new HashMap>(); - private void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrInfo, OneToManyAssocMultiplexer multiplexer) { - ArrayList mList = multiplexers.get(hostAddrInfo.getHostPort()); - if (mList == null) { - mList = new ArrayList(); - multiplexers.put(hostAddrInfo.getHostPort(), mList); - } - mList.add(multiplexer); - } + public MultiChannelController(MultiManagementImpl management) { + super(); + this.management = management; + } - /** - * Using the host address information of the given OneToManyAssociationImpl finds the appropriate multiplexer instance and register it. - * If the multiplexer instance does not exists it is created by the method. - * @param assocImpl - OneToManyAssociation instance need to be registered to the appropriate OneToManyAssocMultiplexer - * @return - the OneToManyAssocMultiplexer that is associated to the OneToManyAssociationImpl assocImpl - * @throws IOException - */ - protected OneToManyAssocMultiplexer register(ManageableAssociation assocImpl) throws IOException { - if (assocImpl == null || assocImpl.getAssocInfo() == null || assocImpl.getAssocInfo().getHostInfo() == null) { - logger.error("Unable to register association=" + assocImpl); - return null; - } - if (logger.isDebugEnabled()) { - logger.debug("register: "+assocImpl); - } - OneToManyAssocMultiplexer ret = null; - synchronized (multiplexers) { - ret = findMultiplexerByHostAddrInfo(assocImpl.getAssocInfo().getHostInfo()); - if (ret == null) { - ret = new OneToManyAssocMultiplexer(assocImpl.getAssocInfo().getHostInfo(), management); - storeMultiplexer(assocImpl.getAssocInfo().getHostInfo(), ret); - } - ret.registerAssociation(assocImpl); - } - return ret; - } + private synchronized OneToManyAssocMultiplexer findMultiplexerByHostAddrInfo(OneToManyAssociationImpl.HostAddressInfo hostAddressInfo) { + OneToManyAssocMultiplexer ret = null; + if (logger.isDebugEnabled()) { + logger.debug("Number of multiplexers: " + multiplexers.size()); + } + ArrayList mList = multiplexers.get(hostAddressInfo.getHostPort()); + if (mList == null || mList.isEmpty()) { + if (logger.isDebugEnabled()) { + logger.debug("No multiplexers found for local port: " + hostAddressInfo.getHostPort()); + } + } else { + for (OneToManyAssocMultiplexer am : mList) { + if (am.getHostAddressInfo().matches(hostAddressInfo)) { + ret = am; + } + } + } + if (logger.isDebugEnabled()) { + logger.debug("findMultiplexerByHostAddr: " + hostAddressInfo + " returns: " + ret); + } + return ret; + } - protected void stopAllMultiplexers() { - for (List mList: multiplexers.values()) { - for (OneToManyAssocMultiplexer multiplexer: mList) { - try { - multiplexer.stop(); - } catch (IOException e) { - logger.warn(e);; - } - } - } - multiplexers.clear(); - } + private synchronized void storeMultiplexer(OneToManyAssociationImpl.HostAddressInfo hostAddrInfo, + OneToManyAssocMultiplexer multiplexer) { + ArrayList mList = multiplexers.get(hostAddrInfo.getHostPort()); + if (mList == null) { + mList = new ArrayList(); + multiplexers.put(hostAddrInfo.getHostPort(), mList); + } + mList.add(multiplexer); + } + + /** + * Using the host address information of the given OneToManyAssociationImpl finds the appropriate multiplexer instance and + * register it. If the multiplexer instance does not exists it is created by the method. + * + * @param assocImpl - OneToManyAssociation instance need to be registered to the appropriate OneToManyAssocMultiplexer + * @return - the OneToManyAssocMultiplexer that is associated to the OneToManyAssociationImpl assocImpl + * @throws IOException + */ + protected synchronized OneToManyAssocMultiplexer register(ManageableAssociation assocImpl) throws IOException { + if (assocImpl == null || assocImpl.getAssocInfo() == null || assocImpl.getAssocInfo().getHostInfo() == null) { + logger.error("Unable to register association=" + assocImpl); + return null; + } + if (logger.isDebugEnabled()) { + logger.debug("register: " + assocImpl); + } + OneToManyAssocMultiplexer ret = null; + ret = findMultiplexerByHostAddrInfo(assocImpl.getAssocInfo().getHostInfo()); + if (ret == null) { + ret = new OneToManyAssocMultiplexer(assocImpl.getAssocInfo().getHostInfo(), management); + storeMultiplexer(assocImpl.getAssocInfo().getHostInfo(), ret); + } + ret.registerAssociation(assocImpl); + return ret; + } + + protected synchronized void stopAllMultiplexers() { + for (List mList : multiplexers.values()) { + for (OneToManyAssocMultiplexer multiplexer : mList) { + try { + multiplexer.stop(); + } catch (IOException e) { + logger.warn(e); + } + } + } + multiplexers.clear(); + } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java index 7af6149..534103b 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssocMultiplexer.java @@ -126,6 +126,16 @@ protected void registerAssociation(ManageableAssociation association) { pendingAssocs.add(association); } + protected void unregisterAssociation(ManageableAssociation association) { + if (!started.get()) { + throw new IllegalStateException("OneToManyAssocMultiplexer is stopped!"); + } + + if (!pendingAssocs.remove(association)) { + connectedAssocs.remove(association); + } + } + protected void start() throws IOException { if (!started.compareAndSet(false, true)) { return; diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java index 297c2b4..098d88f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToManyAssociationImpl.java @@ -419,6 +419,12 @@ protected void reconnect() { } protected void close() { + if (multiplexer != null) { + multiplexer.unregisterAssociation(this); + if (logger.isDebugEnabled()) { + logger.debug("close() - association=" + getName() + " is unregistered from the multiplexer"); + } + } try { this.markAssociationDown(); this.associationListener.onCommunicationShutdown(this); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java index 0eb9414..c8ffece 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/OneToOneAssociationImpl.java @@ -176,9 +176,13 @@ protected void stop() throws Exception { // Finally, wake up our selecting thread so it can make the required // changes this.management.getSocketSelector().wakeup(); - } + } else if (multiplexer != null) { + multiplexer.unregisterAssociation(this); + if (logger.isDebugEnabled()) { + logger.debug("close() - association=" + getName() + " is unregistered from the multiplexer"); + } + } } - public IpChannelType getIpChannelType() { return IpChannelType.SCTP; } @@ -554,6 +558,12 @@ protected void close() { e); } } + if (multiplexer != null) { + multiplexer.unregisterAssociation(this); + if (logger.isDebugEnabled()) { + logger.debug("close() - association=" + getName() + " is unregistered from the multiplexer"); + } + } // Finally clear the txQueue if (this.txQueue.size() > 0) { logger.warn(String.format("Clearig txQueue for Association=%s. %d messages still pending will be cleared", From dbfa44435b2230a4d7c548b833307a6877cdd667 Mon Sep 17 00:00:00 2001 From: Gabor Balogh Date: Thu, 11 Aug 2016 16:49:50 +0200 Subject: [PATCH 26/28] Fix an error in MultiAssociationHandler, where secondaryPeerAddress wasn't used to match pending associations --- .../sctp/multiclient/ManageableAssociation.java | 10 +++++++++- .../sctp/multiclient/MultiAssociationHandler.java | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java index a2532b6..97477d4 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/ManageableAssociation.java @@ -133,7 +133,15 @@ protected void assignSctpAssociationId(int id) { } protected boolean isConnectedToPeerAddresses(String peerAddresses) { - return peerAddresses.contains(getAssocInfo().getPeerInfo().getPeerSocketAddress().toString()); + PeerAddressInfo peer = getAssocInfo().getPeerInfo(); + if (peerAddresses.contains(peer.getPeerSocketAddress().toString())) { + return true; + } else if (peer.getSecondaryPeerSocketAddress() != null) { + if (peerAddresses.contains(peer.getSecondaryPeerSocketAddress().toString())) { + return true; + } + } + return false; } protected PayloadData getInitPayloadData() { diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java index c334221..fbfe19f 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiAssociationHandler.java @@ -53,7 +53,7 @@ public MultiAssociationHandler() { * not be resolved the method returns the value of the defaultResult parameter. * * @param not - Notification that need to be delegated - * @param defaultResult - Default return value used when Association instance can not be resolved + * @param defaultResult - Default return value used when Association instance cannot be resolved * @param multiplexer - The OneToManyAssocMultiplexer that is attached to this MultiAssociationHandler instance * @return - If delegation is successful it returns the return result of the handler method of the corresponding * OneToManyAssociationImpl instance otherwise ű returns the value of the defaultResult parameter. @@ -121,7 +121,7 @@ public HandlerResult handleNotification(SendFailedNotification notification, One } ManageableAssociation assoc = multiplexer.findPendingAssociationByAddress(notification.address()); if (assoc == null) { - logger.warn("Can not handle sendfailed notification: no pending manageable association found for address=" + logger.warn("Cannot handle sendfailed notification: no pending manageable association found for address=" + notification.address() + " by the multiplexer"); return HandlerResult.RETURN; } From 280555c653e34a410dd194f82c1944d5ca55b68c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 15 Apr 2020 11:31:51 +0200 Subject: [PATCH 27/28] ASC SL related changes for correct server side multihoming behaviour --- .../mobicents/protocols/api/Management.java | 26 ++++++ .../protocols/sctp/AssociationImpl.java | 24 +++++ .../protocols/sctp/ManagementImpl.java | 91 +++++++++++++++++++ .../protocols/sctp/SelectorThread.java | 13 +++ .../mobicents/protocols/sctp/ServerImpl.java | 3 + .../sctp/multiclient/MultiManagementImpl.java | 7 ++ 6 files changed, 164 insertions(+) diff --git a/sctp-api/src/main/java/org/mobicents/protocols/api/Management.java b/sctp-api/src/main/java/org/mobicents/protocols/api/Management.java index 560adf2..07154c7 100644 --- a/sctp-api/src/main/java/org/mobicents/protocols/api/Management.java +++ b/sctp-api/src/main/java/org/mobicents/protocols/api/Management.java @@ -274,6 +274,32 @@ public Server addServer(String serverName, String hostAddress, int port, IpChann public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, IpChannelType ipChannelType) throws Exception; + /** + * Add server Association. IP channel type is SCTP. + * + * @param peerAddress + * the peer IP address that this association will accept + * connection from + * @param peerPort + * the peer port that this association will accept connection + * from + * @param serverName + * the Server that this association belongs to + * @param assocName + * unique name of Association + * @param ipChannelType + * IP channel type: SCTP or TCP + * + * @param extraHostAddresses + * When SCTP multi-homing configuration extra IP addresses can be put here + * If multi-homing absence this parameter can be null + * * @return + * @throws Exception + */ + public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, IpChannelType ipChannelType, String[] extraHostAddresses) + throws Exception; + + /** * Add Association. IP channel type is SCTP. * diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/AssociationImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/AssociationImpl.java index c1c6166..0e77629 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/AssociationImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/AssociationImpl.java @@ -182,6 +182,30 @@ public AssociationImpl(String peerAddress, int peerPort, String serverName, Stri this.type = AssociationType.SERVER; } + + /** + * Creating a SERVER Association + * + * @param peerAddress + * @param peerPort + * @param serverName + * @param assocName + * @param ipChannelType + * @param extraHostAddresses + */ + public AssociationImpl(String peerAddress, int peerPort, String serverName, String assocName, + IpChannelType ipChannelType, String[] extraHostAddresses) { + this(); + this.peerAddress = peerAddress; + this.peerPort = peerPort; + this.serverName = serverName; + this.name = assocName; + this.ipChannelType = ipChannelType; + this.extraHostAddresses = extraHostAddresses; + + this.type = AssociationType.SERVER; + + } /** * Creating an ANONYMOUS_SERVER Association diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java index df812e5..6a1e778 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java @@ -695,6 +695,8 @@ public AssociationImpl addServerAssociation(String peerAddress, int peerPort, St public AssociationImpl addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, IpChannelType ipChannelType) throws Exception { + return (AssociationImpl)addServerAssociation(peerAddress, peerPort, serverName, assocName, ipChannelType, null); + /* if (!this.started) { throw new Exception(String.format("Management=%s not started", this.name)); @@ -775,6 +777,95 @@ public AssociationImpl addServerAssociation(String peerAddress, int peerPort, St logger.info(String.format("Added Associoation=%s of type=%s", association.getName(), association.getAssociationType())); } + return association; + } + */ + } + + @Override + public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, + IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception { + // MADE BY KB + + if (!this.started) { + throw new Exception(String.format("Management=%s not started", this.name)); + } + + if (peerAddress == null) { + throw new Exception("Peer address cannot be null"); + } + + if (peerPort < 1) { + throw new Exception("Peer port cannot be less than 1"); + } + + if (serverName == null) { + throw new Exception("Server name cannot be null"); + } + + if (assocName == null) { + throw new Exception("Association name cannot be null"); + } + + synchronized (this) { + if (this.associations.get(assocName) != null) { + throw new Exception(String.format("Already has association=%s", assocName)); + } + + Server server = null; + + for (FastList.Node n = this.servers.head(), end = this.servers.tail(); (n = n.getNext()) != end;) { + Server serverTemp = n.getValue(); + if (serverTemp.getName().equals(serverName)) { + server = serverTemp; + } + } + + if (server == null) { + throw new Exception(String.format("No Server found for name=%s", serverName)); + } + + for (FastMap.Entry n = this.associations.head(), end = this.associations.tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + + if (peerAddress.equals(associationTemp.getPeerAddress()) && associationTemp.getPeerPort() == peerPort) { + throw new Exception(String.format("Already has association=%s with same peer address=%s and port=%d", associationTemp.getName(), + peerAddress, peerPort)); + } + } + + if (server.getIpChannelType() != ipChannelType) + throw new Exception(String.format("Server and Accociation has different IP channel type")); + + AssociationImpl association = new AssociationImpl(peerAddress, peerPort, serverName, assocName, ipChannelType, extraHostAddresses); + association.setManagement(this); + + AssociationMap newAssociations = new AssociationMap(); + newAssociations.putAll(this.associations); + newAssociations.put(assocName, association); + this.associations = newAssociations; + // this.associations.put(assocName, association); + + FastList newAssociations2 = new FastList(); + newAssociations2.addAll(((ServerImpl) server).associations); + newAssociations2.add(assocName); + ((ServerImpl) server).associations = newAssociations2; + // ((ServerImpl) server).associations.add(assocName); + + this.store(); + + for (ManagementEventListener lstr : managementEventListeners) { + try { + lstr.onAssociationAdded(association); + } catch (Throwable ee) { + logger.error("Exception while invoking onAssociationAdded", ee); + } + } + + if (logger.isInfoEnabled()) { + logger.info(String.format("Added Associoation=%s of type=%s", association.getName(), association.getAssociationType())); + } + return association; } } diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java index a4af7ca..25891ab 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java @@ -318,6 +318,19 @@ private void doAccept(AbstractSelectableChannel serverSocketChannel, AbstractSel } return; } + // changes begin + // get anonymAssociation created + try { + AssociationImpl tmpAssociation = (AssociationImpl)this.management.getAssociation(anonymAssociation.getPeerAddress()+":"+anonymAssociation.getPeerPort()); + if (tmpAssociation != null) { + tmpAssociation.setSocketChannel(socketChannel); + tmpAssociation.setManagement(this.management); + anonymAssociation = tmpAssociation; + } + } catch (Exception e) { + logger.error(String.format("Rejected anonymous %s", anonymAssociation), e); + } + // changes - end if (!anonymAssociation.isStarted()) { // connection is rejected diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ServerImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ServerImpl.java index 54c0390..2d23e11 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ServerImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ServerImpl.java @@ -40,6 +40,7 @@ import org.mobicents.protocols.api.Server; import com.sun.nio.sctp.SctpServerChannel; +import com.sun.nio.sctp.SctpStandardSocketOptions; /** * @author amit bhayani @@ -177,6 +178,8 @@ private void doInitSocketSctp() throws IOException { // Create a new non-blocking server socket channel this.serverChannelSctp = SctpServerChannel.open(); this.serverChannelSctp.configureBlocking(false); + //KB: + this.serverChannelSctp.setOption(SctpStandardSocketOptions.SCTP_INIT_MAXSTREAMS, SctpStandardSocketOptions.InitMaxStreams.create(17, 17)); // Bind the server socket to the specified address and port InetSocketAddress isa = new InetSocketAddress(this.hostAddress, this.hostport); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java index a6da521..8329465 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/multiclient/MultiManagementImpl.java @@ -749,6 +749,13 @@ public Association addServerAssociation(String peerAddress, int peerPort, String throw new UnsupportedOperationException( MultiManagementImpl.class.getName() + " does not support server type associations!"); } + + @Override + public Association addServerAssociation(String peerAddress, int peerPort, String serverName, String assocName, + IpChannelType ipChannelType, String[] extraHostAddresses) throws Exception { + throw new UnsupportedOperationException( + MultiManagementImpl.class.getName() + " does not support server type associations!"); + } @Override public ServerListener getServerListener() { From ade718be6c0faab6a92d02b0b2d5356fbb3b65dd Mon Sep 17 00:00:00 2001 From: "alerant.appngin@gmail.com" Date: Tue, 12 May 2020 12:37:39 +0200 Subject: [PATCH 28/28] Server side association handling improvement --- .../protocols/sctp/ManagementImpl.java | 20 +++++++++++++++++++ .../protocols/sctp/SelectorThread.java | 18 ++++++++++++++--- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java index 6a1e778..53bd067 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/ManagementImpl.java @@ -948,6 +948,26 @@ public AssociationImpl addAssociation(String hostAddress, int hostPort, String p } } + public Association getAssociation(String peerAddress, int peerPort) throws Exception { + if (peerAddress == null || peerPort == 0) { + throw new Exception("PeerIp is null or peerPort is zero!"); + } + Association associationToFind = null; + for (FastMap.Entry n = associations.head(), end = associations.tail(); (n = n.getNext()) != end;) { + Association associationTemp = n.getValue(); + if (associationTemp.getPeerAddress().equals(peerAddress) && associationTemp.getPeerPort() == peerPort) { + associationToFind = associationTemp; + } + } + + + if (associationToFind == null) { + throw new Exception(String.format("No Association found based on PeerAddress=%s and PeerPort=%2", peerAddress, peerPort)); + } + return associationToFind; + } + + public Association getAssociation(String assocName) throws Exception { if (assocName == null) { throw new Exception("Association name cannot be null"); diff --git a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java index 25891ab..189c9e9 100644 --- a/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java +++ b/sctp-impl/src/main/java/org/mobicents/protocols/sctp/SelectorThread.java @@ -222,8 +222,10 @@ private void doAccept(AbstractSelectableChannel serverSocketChannel, AbstractSel // server selection for (Server srv : this.management.servers) { ServerImpl srvv = (ServerImpl) srv; + logger.debug(String.format("*** Server: %s", srvv)); if (srvv.getIpChannel() == serverSocketChannel) { // we have found a server for (SocketAddress sockAdd : peerAddresses) { + logger.debug(String.format("****** Socket Address: %s", sockAdd)); inetAddress = ((InetSocketAddress) sockAdd).getAddress(); port = ((InetSocketAddress) sockAdd).getPort(); @@ -239,7 +241,7 @@ private void doAccept(AbstractSelectableChannel serverSocketChannel, AbstractSel for (FastMap.Entry n = associations.head(), end = associations.tail(); (n = n.getNext()) != end && !provisioned;) { AssociationImpl association = (AssociationImpl)n.getValue(); - + logger.debug(String.format("********* Association: %s", association)); // check if an association binds to the found server if (srv.getName().equals(association.getServerName())) { @@ -321,7 +323,16 @@ private void doAccept(AbstractSelectableChannel serverSocketChannel, AbstractSel // changes begin // get anonymAssociation created try { - AssociationImpl tmpAssociation = (AssociationImpl)this.management.getAssociation(anonymAssociation.getPeerAddress()+":"+anonymAssociation.getPeerPort()); + AssociationImpl tmpAssociation = null; + String assocByName = anonymAssociation.getPeerAddress()+":"+anonymAssociation.getPeerPort(); + try { + tmpAssociation = (AssociationImpl)this.management.getAssociation(assocByName); + } catch (Exception ex) { + logger.debug(String.format("Could not find association based on name: %s. We will try to find based on address and port...", assocByName)); + } + if (tmpAssociation == null) { + tmpAssociation = (AssociationImpl)this.management.getAssociation(anonymAssociation.getPeerAddress(), anonymAssociation.getPeerPort()); + } if (tmpAssociation != null) { tmpAssociation.setSocketChannel(socketChannel); tmpAssociation.setManagement(this.management); @@ -351,7 +362,8 @@ private void doAccept(AbstractSelectableChannel serverSocketChannel, AbstractSel if (logger.isInfoEnabled()) { logger.info(String.format("Accepted anonymous %s", anonymAssociation)); } - + + if (anonymAssociation.getIpChannelType() == IpChannelType.TCP) { AssocChangeEvent ace = AssocChangeEvent.COMM_UP; AssociationChangeNotification2 acn = new AssociationChangeNotification2(ace);