Merge pull request #31 from jestan/master
merged the sctp transport implementation with upstream master
This commit is contained in:
commit
2f6c4383d6
@ -0,0 +1,189 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictorFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelException;
|
||||||
|
import org.jboss.netty.channel.ReceiveBufferSizePredictor;
|
||||||
|
import org.jboss.netty.channel.ReceiveBufferSizePredictorFactory;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
import org.jboss.netty.util.internal.ConversionUtil;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link org.jboss.netty.channel.socket.nio.NioSocketChannelConfig} implementation for SCTP.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class DefaultNioSctpChannelConfig extends DefaultSctpChannelConfig implements NioSctpChannelConfig {
|
||||||
|
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(DefaultNioSctpChannelConfig.class);
|
||||||
|
|
||||||
|
private static final ReceiveBufferSizePredictorFactory DEFAULT_PREDICTOR_FACTORY =
|
||||||
|
new AdaptiveReceiveBufferSizePredictorFactory();
|
||||||
|
|
||||||
|
private volatile int writeBufferHighWaterMark = 64 * 1024;
|
||||||
|
private volatile int writeBufferLowWaterMark = 32 * 1024;
|
||||||
|
private volatile ReceiveBufferSizePredictor predictor;
|
||||||
|
private volatile ReceiveBufferSizePredictorFactory predictorFactory = DEFAULT_PREDICTOR_FACTORY;
|
||||||
|
private volatile int writeSpinCount = 16;
|
||||||
|
|
||||||
|
DefaultNioSctpChannelConfig(SctpChannel channel) {
|
||||||
|
super(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOptions(Map<String, Object> options) {
|
||||||
|
super.setOptions(options);
|
||||||
|
if (getWriteBufferHighWaterMark() < getWriteBufferLowWaterMark()) {
|
||||||
|
// Recover the integrity of the configuration with a sensible value.
|
||||||
|
setWriteBufferLowWaterMark0(getWriteBufferHighWaterMark() >>> 1);
|
||||||
|
// Notify the user about misconfiguration.
|
||||||
|
logger.warn(
|
||||||
|
"writeBufferLowWaterMark cannot be greater than " +
|
||||||
|
"writeBufferHighWaterMark; setting to the half of the " +
|
||||||
|
"writeBufferHighWaterMark.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setOption(String key, Object value) {
|
||||||
|
if (super.setOption(key, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.equals("writeBufferHighWaterMark")) {
|
||||||
|
setWriteBufferHighWaterMark0(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("writeBufferLowWaterMark")) {
|
||||||
|
setWriteBufferLowWaterMark0(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("writeSpinCount")) {
|
||||||
|
setWriteSpinCount(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("receiveBufferSizePredictorFactory")) {
|
||||||
|
setReceiveBufferSizePredictorFactory((ReceiveBufferSizePredictorFactory) value);
|
||||||
|
} else if (key.equals("receiveBufferSizePredictor")) {
|
||||||
|
setReceiveBufferSizePredictor((ReceiveBufferSizePredictor) value);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWriteBufferHighWaterMark() {
|
||||||
|
return writeBufferHighWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteBufferHighWaterMark(int writeBufferHighWaterMark) {
|
||||||
|
if (writeBufferHighWaterMark < getWriteBufferLowWaterMark()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"writeBufferHighWaterMark cannot be less than " +
|
||||||
|
"writeBufferLowWaterMark (" + getWriteBufferLowWaterMark() + "): " +
|
||||||
|
writeBufferHighWaterMark);
|
||||||
|
}
|
||||||
|
setWriteBufferHighWaterMark0(writeBufferHighWaterMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setWriteBufferHighWaterMark0(int writeBufferHighWaterMark) {
|
||||||
|
if (writeBufferHighWaterMark < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"writeBufferHighWaterMark: " + writeBufferHighWaterMark);
|
||||||
|
}
|
||||||
|
this.writeBufferHighWaterMark = writeBufferHighWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWriteBufferLowWaterMark() {
|
||||||
|
return writeBufferLowWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteBufferLowWaterMark(int writeBufferLowWaterMark) {
|
||||||
|
if (writeBufferLowWaterMark > getWriteBufferHighWaterMark()) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"writeBufferLowWaterMark cannot be greater than " +
|
||||||
|
"writeBufferHighWaterMark (" + getWriteBufferHighWaterMark() + "): " +
|
||||||
|
writeBufferLowWaterMark);
|
||||||
|
}
|
||||||
|
setWriteBufferLowWaterMark0(writeBufferLowWaterMark);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setWriteBufferLowWaterMark0(int writeBufferLowWaterMark) {
|
||||||
|
if (writeBufferLowWaterMark < 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"writeBufferLowWaterMark: " + writeBufferLowWaterMark);
|
||||||
|
}
|
||||||
|
this.writeBufferLowWaterMark = writeBufferLowWaterMark;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWriteSpinCount() {
|
||||||
|
return writeSpinCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setWriteSpinCount(int writeSpinCount) {
|
||||||
|
if (writeSpinCount <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"writeSpinCount must be a positive integer.");
|
||||||
|
}
|
||||||
|
this.writeSpinCount = writeSpinCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReceiveBufferSizePredictor getReceiveBufferSizePredictor() {
|
||||||
|
ReceiveBufferSizePredictor predictor = this.predictor;
|
||||||
|
if (predictor == null) {
|
||||||
|
try {
|
||||||
|
this.predictor = predictor = getReceiveBufferSizePredictorFactory().getPredictor();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new ChannelException(
|
||||||
|
"Failed to create a new " +
|
||||||
|
ReceiveBufferSizePredictor.class.getSimpleName() + '.',
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return predictor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReceiveBufferSizePredictor(
|
||||||
|
ReceiveBufferSizePredictor predictor) {
|
||||||
|
if (predictor == null) {
|
||||||
|
throw new NullPointerException("predictor");
|
||||||
|
}
|
||||||
|
this.predictor = predictor;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReceiveBufferSizePredictorFactory getReceiveBufferSizePredictorFactory() {
|
||||||
|
return predictorFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReceiveBufferSizePredictorFactory(ReceiveBufferSizePredictorFactory predictorFactory) {
|
||||||
|
if (predictorFactory == null) {
|
||||||
|
throw new NullPointerException("predictorFactory");
|
||||||
|
}
|
||||||
|
this.predictorFactory = predictorFactory;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
|
||||||
|
import org.jboss.netty.channel.ChannelException;
|
||||||
|
import org.jboss.netty.channel.DefaultChannelConfig;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioSocketChannelConfig;
|
||||||
|
import org.jboss.netty.util.internal.ConversionUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link NioSocketChannelConfig} implementation for SCTP.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class DefaultSctpChannelConfig extends DefaultChannelConfig implements SctpChannelConfig {
|
||||||
|
|
||||||
|
private SctpChannel channel;
|
||||||
|
|
||||||
|
DefaultSctpChannelConfig(SctpChannel channel) {
|
||||||
|
if (channel == null) {
|
||||||
|
throw new NullPointerException("channel");
|
||||||
|
}
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setOption(String key, Object value) {
|
||||||
|
if (super.setOption(key, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.equals("receiveBufferSize")) {
|
||||||
|
setReceiveBufferSize(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("sendBufferSize")) {
|
||||||
|
setSendBufferSize(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("sctpNoDelay")) {
|
||||||
|
setSctpNoDelay(ConversionUtil.toBoolean(value));
|
||||||
|
} else if (key.equals("soLinger")) {
|
||||||
|
setSoLinger(ConversionUtil.toInt(value));
|
||||||
|
} else if (key.equals("sctpInitMaxStreams")) {
|
||||||
|
setInitMaxStreams((InitMaxStreams) value);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isSctpNoDelay() {
|
||||||
|
try {
|
||||||
|
return channel.getOption(SCTP_NODELAY);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSctpNoDelay(boolean sctpNoDelay) {
|
||||||
|
try {
|
||||||
|
channel.setOption(SCTP_NODELAY, sctpNoDelay);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSoLinger() {
|
||||||
|
try {
|
||||||
|
return channel.getOption(SO_LINGER);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSoLinger(int soLinger) {
|
||||||
|
try {
|
||||||
|
channel.setOption(SO_LINGER, soLinger);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSendBufferSize() {
|
||||||
|
try {
|
||||||
|
return channel.getOption(SO_SNDBUF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSendBufferSize(int sendBufferSize) {
|
||||||
|
try {
|
||||||
|
channel.setOption(SO_SNDBUF, sendBufferSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getReceiveBufferSize() {
|
||||||
|
try {
|
||||||
|
return channel.getOption(SO_RCVBUF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReceiveBufferSize(int receiveBufferSize) {
|
||||||
|
try {
|
||||||
|
channel.setOption(SO_RCVBUF, receiveBufferSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InitMaxStreams getInitMaxStreams() {
|
||||||
|
try {
|
||||||
|
return channel.getOption(SCTP_INIT_MAXSTREAMS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInitMaxStreams(InitMaxStreams initMaxStreams) {
|
||||||
|
try {
|
||||||
|
channel.setOption(SCTP_INIT_MAXSTREAMS, initMaxStreams);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelException;
|
||||||
|
import org.jboss.netty.channel.DefaultServerChannelConfig;
|
||||||
|
import org.jboss.netty.util.internal.ConversionUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default {@link org.jboss.netty.channel.socket.ServerSocketChannelConfig} implementation for SCTP.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class DefaultSctpServerChannelConfig extends DefaultServerChannelConfig
|
||||||
|
implements SctpServerChannelConfig {
|
||||||
|
|
||||||
|
private final com.sun.nio.sctp.SctpServerChannel serverChannel;
|
||||||
|
private volatile int backlog;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*/
|
||||||
|
public DefaultSctpServerChannelConfig(com.sun.nio.sctp.SctpServerChannel serverChannel) {
|
||||||
|
if (serverChannel == null) {
|
||||||
|
throw new NullPointerException("serverChannel");
|
||||||
|
}
|
||||||
|
this.serverChannel = serverChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean setOption(String key, Object value) {
|
||||||
|
if (super.setOption(key, value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key.equals("sctpInitMaxStreams")) {
|
||||||
|
setInitMaxStreams((InitMaxStreams) value);
|
||||||
|
} else if (key.equals("backlog")) {
|
||||||
|
setBacklog(ConversionUtil.toInt(value));
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getSendBufferSize() {
|
||||||
|
try {
|
||||||
|
return serverChannel.getOption(SO_SNDBUF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setSendBufferSize(int sendBufferSize) {
|
||||||
|
try {
|
||||||
|
serverChannel.setOption(SO_SNDBUF, sendBufferSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getReceiveBufferSize() {
|
||||||
|
try {
|
||||||
|
return serverChannel.getOption(SO_RCVBUF);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setReceiveBufferSize(int receiveBufferSize) {
|
||||||
|
try {
|
||||||
|
serverChannel.setOption(SO_RCVBUF, receiveBufferSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InitMaxStreams getInitMaxStreams() {
|
||||||
|
try {
|
||||||
|
return serverChannel.getOption(SCTP_INIT_MAXSTREAMS);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setInitMaxStreams(InitMaxStreams initMaxStreams) {
|
||||||
|
try {
|
||||||
|
serverChannel.setOption(SCTP_INIT_MAXSTREAMS, initMaxStreams);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getBacklog() {
|
||||||
|
return backlog;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setBacklog(int backlog) {
|
||||||
|
if (backlog < 0) {
|
||||||
|
throw new IllegalArgumentException("backlog: " + backlog);
|
||||||
|
}
|
||||||
|
this.backlog = backlog;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ReceiveBufferSizePredictor;
|
||||||
|
import org.jboss.netty.channel.ReceiveBufferSizePredictorFactory;
|
||||||
|
import org.jboss.netty.channel.socket.SocketChannelConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.jboss.netty.channel.socket.sctp.SctpChannelConfig} for a NIO SCTP/IP {@link org.jboss.netty.channel.socket.sctp.SctpChannel}.
|
||||||
|
*
|
||||||
|
* <h3>Available options</h3>
|
||||||
|
*
|
||||||
|
* In addition to the options provided by {@link org.jboss.netty.channel.ChannelConfig} and
|
||||||
|
* {@link org.jboss.netty.channel.socket.sctp.SctpChannelConfig}, {@link org.jboss.netty.channel.socket.sctp.NioSctpChannelConfig} allows the
|
||||||
|
* following options in the option map:
|
||||||
|
*
|
||||||
|
* <table border="1" cellspacing="0" cellpadding="6">
|
||||||
|
* <tr>
|
||||||
|
* <th>Name</th><th>Associated setter method</th>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "writeBufferHighWaterMark"}</td><td>{@link #setWriteBufferHighWaterMark(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "writeBufferLowWaterMark"}</td><td>{@link #setWriteBufferLowWaterMark(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "writeSpinCount"}</td><td>{@link #setWriteSpinCount(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "receiveBufferSizePredictor"}</td><td>{@link #setReceiveBufferSizePredictor(org.jboss.netty.channel.ReceiveBufferSizePredictor)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "receiveBufferSizePredictorFactory"}</td><td>{@link #setReceiveBufferSizePredictorFactory(org.jboss.netty.channel.ReceiveBufferSizePredictorFactory)}</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public interface NioSctpChannelConfig extends SctpChannelConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the high water mark of the write buffer. If the number of bytes
|
||||||
|
* queued in the write buffer exceeds this value, {@link org.jboss.netty.channel.Channel#isWritable()}
|
||||||
|
* will start to return {@code false}.
|
||||||
|
*/
|
||||||
|
int getWriteBufferHighWaterMark();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the high water mark of the write buffer. If the number of bytes
|
||||||
|
* queued in the write buffer exceeds this value, {@link org.jboss.netty.channel.Channel#isWritable()}
|
||||||
|
* will start to return {@code false}.
|
||||||
|
*/
|
||||||
|
void setWriteBufferHighWaterMark(int writeBufferHighWaterMark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the low water mark of the write buffer. Once the number of bytes
|
||||||
|
* queued in the write buffer exceeded the
|
||||||
|
* {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then
|
||||||
|
* dropped down below this value, {@link org.jboss.netty.channel.Channel#isWritable()} will return
|
||||||
|
* {@code true} again.
|
||||||
|
*/
|
||||||
|
int getWriteBufferLowWaterMark();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the low water mark of the write buffer. Once the number of bytes
|
||||||
|
* queued in the write buffer exceeded the
|
||||||
|
* {@linkplain #setWriteBufferHighWaterMark(int) high water mark} and then
|
||||||
|
* dropped down below this value, {@link org.jboss.netty.channel.Channel#isWritable()} will return
|
||||||
|
* {@code true} again.
|
||||||
|
*/
|
||||||
|
void setWriteBufferLowWaterMark(int writeBufferLowWaterMark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the maximum loop count for a write operation until
|
||||||
|
* {@link java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)} returns a non-zero value.
|
||||||
|
* It is similar to what a spin lock is used for in concurrency programming.
|
||||||
|
* It improves memory utilization and write throughput depending on
|
||||||
|
* the platform that JVM runs on. The default value is {@code 16}.
|
||||||
|
*/
|
||||||
|
int getWriteSpinCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum loop count for a write operation until
|
||||||
|
* {@link java.nio.channels.WritableByteChannel#write(java.nio.ByteBuffer)} returns a non-zero value.
|
||||||
|
* It is similar to what a spin lock is used for in concurrency programming.
|
||||||
|
* It improves memory utilization and write throughput depending on
|
||||||
|
* the platform that JVM runs on. The default value is {@code 16}.
|
||||||
|
*
|
||||||
|
* @throws IllegalArgumentException
|
||||||
|
* if the specified value is {@code 0} or less than {@code 0}
|
||||||
|
*/
|
||||||
|
void setWriteSpinCount(int writeSpinCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} which predicts the
|
||||||
|
* number of readable bytes in the socket receive buffer. The default
|
||||||
|
* predictor is <tt>{@link org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictor}(64, 1024, 65536)</tt>.
|
||||||
|
*/
|
||||||
|
ReceiveBufferSizePredictor getReceiveBufferSizePredictor();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} which predicts the
|
||||||
|
* number of readable bytes in the socket receive buffer. The default
|
||||||
|
* predictor is <tt>{@link org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictor}(64, 1024, 65536)</tt>.
|
||||||
|
*/
|
||||||
|
void setReceiveBufferSizePredictor(ReceiveBufferSizePredictor predictor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link org.jboss.netty.channel.ReceiveBufferSizePredictorFactory} which creates a new
|
||||||
|
* {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} when a new channel is created and
|
||||||
|
* no {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} was set. If no predictor was set
|
||||||
|
* for the channel, {@link #setReceiveBufferSizePredictor(org.jboss.netty.channel.ReceiveBufferSizePredictor)}
|
||||||
|
* will be called with the new predictor. The default factory is
|
||||||
|
* <tt>{@link org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictorFactory}(64, 1024, 65536)</tt>.
|
||||||
|
*/
|
||||||
|
ReceiveBufferSizePredictorFactory getReceiveBufferSizePredictorFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} which creates a new
|
||||||
|
* {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} when a new channel is created and
|
||||||
|
* no {@link org.jboss.netty.channel.ReceiveBufferSizePredictor} was set. If no predictor was set
|
||||||
|
* for the channel, {@link #setReceiveBufferSizePredictor(org.jboss.netty.channel.ReceiveBufferSizePredictor)}
|
||||||
|
* will be called with the new predictor. The default factory is
|
||||||
|
* <tt>{@link org.jboss.netty.channel.AdaptiveReceiveBufferSizePredictorFactory}(64, 1024, 65536)</tt>.
|
||||||
|
*/
|
||||||
|
void setReceiveBufferSizePredictorFactory(
|
||||||
|
ReceiveBufferSizePredictorFactory predictorFactory);
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelSink;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
final class SctpAcceptedChannel extends SctpChannelImpl {
|
||||||
|
|
||||||
|
final Thread bossThread;
|
||||||
|
|
||||||
|
SctpAcceptedChannel(
|
||||||
|
ChannelFactory factory, ChannelPipeline pipeline,
|
||||||
|
Channel parent, ChannelSink sink,
|
||||||
|
SctpChannel socket, SctpWorker worker, Thread bossThread) {
|
||||||
|
|
||||||
|
super(parent, factory, pipeline, sink, socket, worker);
|
||||||
|
|
||||||
|
this.bossThread = bossThread;
|
||||||
|
|
||||||
|
setConnected();
|
||||||
|
fireChannelOpen(this);
|
||||||
|
fireChannelBound(this, getLocalAddress());
|
||||||
|
fireChannelConnected(this, getRemoteAddress());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.Association;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.socket.SocketChannel;
|
||||||
|
import org.jboss.netty.channel.socket.SocketChannelConfig;
|
||||||
|
import org.jboss.netty.channel.socket.nio.NioSocketChannelConfig;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public interface SctpChannel extends Channel {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the primary local address of the SCTP channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
InetSocketAddress getLocalAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all local addresses of the SCTP channel.
|
||||||
|
*/
|
||||||
|
Set<InetSocketAddress> getAllLocalAddresses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the configuration of this channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
NioSctpChannelConfig getConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the primary remote address of the SCTP channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
InetSocketAddress getRemoteAddress();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all remote addresses of the SCTP channel.
|
||||||
|
*/
|
||||||
|
Set<InetSocketAddress> getAllRemoteAddresses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the underlying SCTP association
|
||||||
|
*/
|
||||||
|
Association association();
|
||||||
|
}
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
|
||||||
|
import org.jboss.netty.channel.ChannelConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.jboss.netty.channel.ChannelConfig} for a {@link org.jboss.netty.channel.socket.sctp.SctpChannel}.
|
||||||
|
* <p/>
|
||||||
|
* <h3>Available options</h3>
|
||||||
|
* <p/>
|
||||||
|
* In addition to the options provided by {@link org.jboss.netty.channel.ChannelConfig},
|
||||||
|
* {@link org.jboss.netty.channel.socket.sctp.SctpChannelConfig} allows the following options in the option map:
|
||||||
|
* <p/>
|
||||||
|
* <table border="1" cellspacing="0" cellpadding="6">
|
||||||
|
* <tr>
|
||||||
|
* <th>Name</th><th>Associated setter method</th>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "sctpNoDelay"}</td><td>{@link #setSctpNoDelay(boolean)}}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "soLinger"}</td><td>{@link #setSoLinger(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "receiveBufferSize"}</td><td>{@link #setReceiveBufferSize(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "sendBufferSize"}</td><td>{@link #setSendBufferSize(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "sctpInitMaxStreams"}</td><td>{@link #setInitMaxStreams(InitMaxStreams)}</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public interface SctpChannelConfig extends ChannelConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_NODELAY}</a> option.
|
||||||
|
*/
|
||||||
|
boolean isSctpNoDelay();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_NODELAY}</a> option.
|
||||||
|
*/
|
||||||
|
void setSctpNoDelay(boolean sctpNoDelay);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_LINGER}</a> option.
|
||||||
|
*/
|
||||||
|
int getSoLinger();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_LINGER}</a> option.
|
||||||
|
*/
|
||||||
|
void setSoLinger(int soLinger);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
|
||||||
|
*/
|
||||||
|
int getSendBufferSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
|
||||||
|
*/
|
||||||
|
void setSendBufferSize(int sendBufferSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
|
||||||
|
*/
|
||||||
|
int getReceiveBufferSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
|
||||||
|
*/
|
||||||
|
void setReceiveBufferSize(int receiveBufferSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
|
||||||
|
*/
|
||||||
|
InitMaxStreams getInitMaxStreams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
|
||||||
|
*/
|
||||||
|
void setInitMaxStreams(InitMaxStreams initMaxStreams);
|
||||||
|
}
|
@ -0,0 +1,312 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.Association;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.socket.sctp.SctpSendBufferPool.SendBuffer;
|
||||||
|
import org.jboss.netty.util.internal.ThreadLocalBoolean;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.fireChannelInterestChanged;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class SctpChannelImpl extends AbstractChannel implements SctpChannel {
|
||||||
|
|
||||||
|
private static final int ST_OPEN = 0;
|
||||||
|
private static final int ST_BOUND = 1;
|
||||||
|
private static final int ST_CONNECTED = 2;
|
||||||
|
private static final int ST_CLOSED = -1;
|
||||||
|
volatile int state = ST_OPEN;
|
||||||
|
|
||||||
|
final com.sun.nio.sctp.SctpChannel channel;
|
||||||
|
final SctpWorker worker;
|
||||||
|
private final NioSctpChannelConfig config;
|
||||||
|
private volatile InetSocketAddress localAddress;
|
||||||
|
private volatile InetSocketAddress remoteAddress;
|
||||||
|
|
||||||
|
final Object interestOpsLock = new Object();
|
||||||
|
final Object writeLock = new Object();
|
||||||
|
|
||||||
|
final Runnable writeTask = new WriteTask();
|
||||||
|
final AtomicBoolean writeTaskInTaskQueue = new AtomicBoolean();
|
||||||
|
|
||||||
|
final Queue<MessageEvent> writeBuffer = new WriteRequestQueue();
|
||||||
|
final AtomicInteger writeBufferSize = new AtomicInteger();
|
||||||
|
final AtomicInteger highWaterMarkCounter = new AtomicInteger();
|
||||||
|
boolean inWriteNowLoop;
|
||||||
|
boolean writeSuspended;
|
||||||
|
|
||||||
|
MessageEvent currentWriteEvent;
|
||||||
|
SendBuffer currentWriteBuffer;
|
||||||
|
|
||||||
|
public SctpChannelImpl(Channel parent, ChannelFactory factory, ChannelPipeline pipeline, ChannelSink sink,
|
||||||
|
com.sun.nio.sctp.SctpChannel channel, SctpWorker worker) {
|
||||||
|
super(parent, factory, pipeline, sink);
|
||||||
|
|
||||||
|
this.channel = channel;
|
||||||
|
this.worker = worker;
|
||||||
|
config = new DefaultNioSctpChannelConfig(channel);
|
||||||
|
|
||||||
|
getCloseFuture().addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
state = ST_CLOSED;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public NioSctpChannelConfig getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getLocalAddress() {
|
||||||
|
InetSocketAddress localAddress = this.localAddress;
|
||||||
|
if (localAddress == null) {
|
||||||
|
try {
|
||||||
|
final Iterator<SocketAddress> iterator = channel.getAllLocalAddresses().iterator();
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
this.localAddress = localAddress = (InetSocketAddress) iterator.next();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return localAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<InetSocketAddress> getAllLocalAddresses() {
|
||||||
|
try {
|
||||||
|
final Set<SocketAddress> allLocalAddresses = channel.getAllLocalAddresses();
|
||||||
|
final Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>(allLocalAddresses.size());
|
||||||
|
for(SocketAddress socketAddress: allLocalAddresses) {
|
||||||
|
addresses.add((InetSocketAddress) socketAddress);
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
InetSocketAddress remoteAddress = this.remoteAddress;
|
||||||
|
if (remoteAddress == null) {
|
||||||
|
try {
|
||||||
|
final Iterator<SocketAddress> iterator = channel.getRemoteAddresses().iterator();
|
||||||
|
if (iterator.hasNext()) {
|
||||||
|
this.remoteAddress = remoteAddress = (InetSocketAddress) iterator.next();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return remoteAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<InetSocketAddress> getAllRemoteAddresses() {
|
||||||
|
try {
|
||||||
|
final Set<SocketAddress> allLocalAddresses = channel.getRemoteAddresses();
|
||||||
|
final Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>(allLocalAddresses.size());
|
||||||
|
for(SocketAddress socketAddress: allLocalAddresses) {
|
||||||
|
addresses.add((InetSocketAddress) socketAddress);
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Association association() {
|
||||||
|
try {
|
||||||
|
return channel.association();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isOpen() {
|
||||||
|
return state >= ST_OPEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBound() {
|
||||||
|
return state >= ST_BOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return state == ST_CONNECTED;
|
||||||
|
}
|
||||||
|
|
||||||
|
final void setBound() {
|
||||||
|
assert state == ST_OPEN : "Invalid state: " + state;
|
||||||
|
state = ST_BOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
final void setConnected() {
|
||||||
|
if (state != ST_CLOSED) {
|
||||||
|
state = ST_CONNECTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean setClosed() {
|
||||||
|
return super.setClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getInterestOps() {
|
||||||
|
if (!isOpen()) {
|
||||||
|
return Channel.OP_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int interestOps = getRawInterestOps();
|
||||||
|
int writeBufferSize = this.writeBufferSize.get();
|
||||||
|
if (writeBufferSize != 0) {
|
||||||
|
if (highWaterMarkCounter.get() > 0) {
|
||||||
|
int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
|
||||||
|
if (writeBufferSize >= lowWaterMark) {
|
||||||
|
interestOps |= Channel.OP_WRITE;
|
||||||
|
} else {
|
||||||
|
interestOps &= ~Channel.OP_WRITE;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int highWaterMark = getConfig().getWriteBufferHighWaterMark();
|
||||||
|
if (writeBufferSize >= highWaterMark) {
|
||||||
|
interestOps |= Channel.OP_WRITE;
|
||||||
|
} else {
|
||||||
|
interestOps &= ~Channel.OP_WRITE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
interestOps &= ~Channel.OP_WRITE;
|
||||||
|
}
|
||||||
|
|
||||||
|
return interestOps;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRawInterestOps() {
|
||||||
|
return super.getInterestOps();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setRawInterestOpsNow(int interestOps) {
|
||||||
|
super.setInterestOpsNow(interestOps);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture write(Object message, SocketAddress remoteAddress) {
|
||||||
|
if (remoteAddress == null || remoteAddress.equals(getRemoteAddress())) {
|
||||||
|
return super.write(message, null);
|
||||||
|
} else {
|
||||||
|
return getUnsupportedOperationFuture();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class WriteRequestQueue extends LinkedTransferQueue<MessageEvent> {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = -246694024103520626L;
|
||||||
|
|
||||||
|
private final ThreadLocalBoolean notifying = new ThreadLocalBoolean();
|
||||||
|
|
||||||
|
WriteRequestQueue() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean offer(MessageEvent e) {
|
||||||
|
boolean success = super.offer(e);
|
||||||
|
assert success;
|
||||||
|
|
||||||
|
int messageSize = getMessageSize(e);
|
||||||
|
int newWriteBufferSize = writeBufferSize.addAndGet(messageSize);
|
||||||
|
int highWaterMark = getConfig().getWriteBufferHighWaterMark();
|
||||||
|
|
||||||
|
if (newWriteBufferSize >= highWaterMark) {
|
||||||
|
if (newWriteBufferSize - messageSize < highWaterMark) {
|
||||||
|
highWaterMarkCounter.incrementAndGet();
|
||||||
|
if (!notifying.get()) {
|
||||||
|
notifying.set(Boolean.TRUE);
|
||||||
|
fireChannelInterestChanged(SctpChannelImpl.this);
|
||||||
|
notifying.set(Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MessageEvent poll() {
|
||||||
|
MessageEvent e = super.poll();
|
||||||
|
if (e != null) {
|
||||||
|
int messageSize = getMessageSize(e);
|
||||||
|
int newWriteBufferSize = writeBufferSize.addAndGet(-messageSize);
|
||||||
|
int lowWaterMark = getConfig().getWriteBufferLowWaterMark();
|
||||||
|
|
||||||
|
if (newWriteBufferSize == 0 || newWriteBufferSize < lowWaterMark) {
|
||||||
|
if (newWriteBufferSize + messageSize >= lowWaterMark) {
|
||||||
|
highWaterMarkCounter.decrementAndGet();
|
||||||
|
if (isConnected() && !notifying.get()) {
|
||||||
|
notifying.set(Boolean.TRUE);
|
||||||
|
fireChannelInterestChanged(SctpChannelImpl.this);
|
||||||
|
notifying.set(Boolean.FALSE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getMessageSize(MessageEvent e) {
|
||||||
|
Object m = e.getMessage();
|
||||||
|
if (m instanceof ChannelBuffer) {
|
||||||
|
return ((ChannelBuffer) m).readableBytes();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class WriteTask implements Runnable {
|
||||||
|
|
||||||
|
WriteTask() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
writeTaskInTaskQueue.set(false);
|
||||||
|
worker.writeFromTaskLoop(SctpChannelImpl.this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.fireChannelOpen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
final class SctpClientChannel extends SctpChannelImpl {
|
||||||
|
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpClientChannel.class);
|
||||||
|
|
||||||
|
private static SctpChannel newChannael() {
|
||||||
|
SctpChannel underlayingChannel;
|
||||||
|
try {
|
||||||
|
underlayingChannel = SctpChannel.open();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException("Failed to open a socket.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
underlayingChannel.configureBlocking(false);
|
||||||
|
success = true;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException("Failed to enter non-blocking mode.", e);
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
try {
|
||||||
|
underlayingChannel.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to close a partially initialized socket.",
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return underlayingChannel;
|
||||||
|
}
|
||||||
|
|
||||||
|
volatile ChannelFuture connectFuture;
|
||||||
|
volatile boolean boundManually;
|
||||||
|
|
||||||
|
// Does not need to be volatile as it's accessed by only one thread.
|
||||||
|
long connectDeadlineNanos;
|
||||||
|
|
||||||
|
SctpClientChannel(
|
||||||
|
ChannelFactory factory, ChannelPipeline pipeline,
|
||||||
|
ChannelSink sink, SctpWorker worker) {
|
||||||
|
|
||||||
|
super(null, factory, pipeline, sink, newChannael(), worker);
|
||||||
|
fireChannelOpen(this);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,413 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
import org.jboss.netty.util.internal.DeadLockProofWorker;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.ConnectException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.channels.ClosedChannelException;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class SctpClientPipelineSink extends AbstractChannelSink {
|
||||||
|
|
||||||
|
static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpClientPipelineSink.class);
|
||||||
|
|
||||||
|
final Executor bossExecutor;
|
||||||
|
private final Boss boss = new Boss();
|
||||||
|
private final SctpWorker[] workers;
|
||||||
|
private final AtomicInteger workerIndex = new AtomicInteger();
|
||||||
|
|
||||||
|
SctpClientPipelineSink(
|
||||||
|
Executor bossExecutor, Executor workerExecutor, int workerCount) {
|
||||||
|
this.bossExecutor = bossExecutor;
|
||||||
|
workers = new SctpWorker[workerCount];
|
||||||
|
for (int i = 0; i < workers.length; i ++) {
|
||||||
|
workers[i] = new SctpWorker(workerExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventSunk(
|
||||||
|
ChannelPipeline pipeline, ChannelEvent e) throws Exception {
|
||||||
|
if (e instanceof ChannelStateEvent) {
|
||||||
|
ChannelStateEvent event = (ChannelStateEvent) e;
|
||||||
|
SctpClientChannel channel =
|
||||||
|
(SctpClientChannel) event.getChannel();
|
||||||
|
ChannelFuture future = event.getFuture();
|
||||||
|
ChannelState state = event.getState();
|
||||||
|
Object value = event.getValue();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
if (Boolean.FALSE.equals(value)) {
|
||||||
|
channel.worker.close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BOUND:
|
||||||
|
if (value != null) {
|
||||||
|
bind(channel, future, (SocketAddress) value);
|
||||||
|
} else {
|
||||||
|
channel.worker.close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case CONNECTED:
|
||||||
|
if (value != null) {
|
||||||
|
connect(channel, future, (SocketAddress) value);
|
||||||
|
} else {
|
||||||
|
channel.worker.close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INTEREST_OPS:
|
||||||
|
channel.worker.setInterestOps(channel, future, ((Integer) value).intValue());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (e instanceof MessageEvent) {
|
||||||
|
MessageEvent event = (MessageEvent) e;
|
||||||
|
SctpChannelImpl channel = (SctpChannelImpl) event.getChannel();
|
||||||
|
boolean offered = channel.writeBuffer.offer(event);
|
||||||
|
assert offered;
|
||||||
|
channel.worker.writeFromUserCode(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bind(
|
||||||
|
SctpClientChannel channel, ChannelFuture future,
|
||||||
|
SocketAddress localAddress) {
|
||||||
|
try {
|
||||||
|
channel.channel.bind(localAddress);
|
||||||
|
channel.boundManually = true;
|
||||||
|
channel.setBound();
|
||||||
|
future.setSuccess();
|
||||||
|
fireChannelBound(channel, channel.getLocalAddress());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(
|
||||||
|
final SctpClientChannel channel, final ChannelFuture cf,
|
||||||
|
SocketAddress remoteAddress) {
|
||||||
|
try {
|
||||||
|
if (channel.channel.connect(remoteAddress)) {
|
||||||
|
channel.worker.register(channel, cf);
|
||||||
|
} else {
|
||||||
|
channel.getCloseFuture().addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture f)
|
||||||
|
throws Exception {
|
||||||
|
if (!cf.isDone()) {
|
||||||
|
cf.setFailure(new ClosedChannelException());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cf.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
|
||||||
|
channel.connectFuture = cf;
|
||||||
|
boss.register(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
cf.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
channel.worker.close(channel, succeededFuture(channel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SctpWorker nextWorker() {
|
||||||
|
return workers[Math.abs(
|
||||||
|
workerIndex.getAndIncrement() % workers.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Boss implements Runnable {
|
||||||
|
|
||||||
|
volatile Selector selector;
|
||||||
|
private boolean started;
|
||||||
|
private final AtomicBoolean wakenUp = new AtomicBoolean();
|
||||||
|
private final Object startStopLock = new Object();
|
||||||
|
private final Queue<Runnable> registerTaskQueue = new LinkedTransferQueue<Runnable>();
|
||||||
|
|
||||||
|
Boss() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(SctpClientChannel channel) {
|
||||||
|
Runnable registerTask = new RegisterTask(this, channel);
|
||||||
|
Selector selector;
|
||||||
|
|
||||||
|
synchronized (startStopLock) {
|
||||||
|
if (!started) {
|
||||||
|
// Open a selector if this worker didn't start yet.
|
||||||
|
try {
|
||||||
|
this.selector = selector = Selector.open();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ChannelException(
|
||||||
|
"Failed to create a selector.", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the worker thread with the new Selector.
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
DeadLockProofWorker.start(bossExecutor, this);
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
// Release the Selector if the execution fails.
|
||||||
|
try {
|
||||||
|
selector.close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn("Failed to close a selector.", t);
|
||||||
|
}
|
||||||
|
this.selector = selector = null;
|
||||||
|
// The method will return to the caller at this point.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use the existing selector if this worker has been started.
|
||||||
|
selector = this.selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert selector != null && selector.isOpen();
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
boolean offered = registerTaskQueue.offer(registerTask);
|
||||||
|
assert offered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
boolean shutdown = false;
|
||||||
|
Selector selector = this.selector;
|
||||||
|
long lastConnectTimeoutCheckTimeNanos = System.nanoTime();
|
||||||
|
for (;;) {
|
||||||
|
wakenUp.set(false);
|
||||||
|
|
||||||
|
try {
|
||||||
|
int selectedKeyCount = selector.select(500);
|
||||||
|
|
||||||
|
// 'wakenUp.compareAndSet(false, true)' is always evaluated
|
||||||
|
// before calling 'selector.wakeup()' to reduce the wake-up
|
||||||
|
// overhead. (Selector.wakeup() is an expensive operation.)
|
||||||
|
//
|
||||||
|
// However, there is a race condition in this approach.
|
||||||
|
// The race condition is triggered when 'wakenUp' is set to
|
||||||
|
// true too early.
|
||||||
|
//
|
||||||
|
// 'wakenUp' is set to true too early if:
|
||||||
|
// 1) Selector is waken up between 'wakenUp.set(false)' and
|
||||||
|
// 'selector.select(...)'. (BAD)
|
||||||
|
// 2) Selector is waken up between 'selector.select(...)' and
|
||||||
|
// 'if (wakenUp.get()) { ... }'. (OK)
|
||||||
|
//
|
||||||
|
// In the first case, 'wakenUp' is set to true and the
|
||||||
|
// following 'selector.select(...)' will wake up immediately.
|
||||||
|
// Until 'wakenUp' is set to false again in the next round,
|
||||||
|
// 'wakenUp.compareAndSet(false, true)' will fail, and therefore
|
||||||
|
// any attempt to wake up the Selector will fail, too, causing
|
||||||
|
// the following 'selector.select(...)' call to block
|
||||||
|
// unnecessarily.
|
||||||
|
//
|
||||||
|
// To fix this problem, we wake up the selector again if wakenUp
|
||||||
|
// is true immediately after selector.select(...).
|
||||||
|
// It is inefficient in that it wakes up the selector for both
|
||||||
|
// the first case (BAD - wake-up required) and the second case
|
||||||
|
// (OK - no wake-up required).
|
||||||
|
|
||||||
|
if (wakenUp.get()) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
|
processRegisterTaskQueue();
|
||||||
|
|
||||||
|
if (selectedKeyCount > 0) {
|
||||||
|
processSelectedKeys(selector.selectedKeys());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle connection timeout every 0.5 seconds approximately.
|
||||||
|
long currentTimeNanos = System.nanoTime();
|
||||||
|
if (currentTimeNanos - lastConnectTimeoutCheckTimeNanos >= 500 * 1000000L) {
|
||||||
|
lastConnectTimeoutCheckTimeNanos = currentTimeNanos;
|
||||||
|
processConnectTimeout(selector.keys(), currentTimeNanos);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exit the loop when there's nothing to handle.
|
||||||
|
// The shutdown flag is used to delay the shutdown of this
|
||||||
|
// loop to avoid excessive Selector creation when
|
||||||
|
// connection attempts are made in a one-by-one manner
|
||||||
|
// instead of concurrent manner.
|
||||||
|
if (selector.keys().isEmpty()) {
|
||||||
|
if (shutdown ||
|
||||||
|
bossExecutor instanceof ExecutorService && ((ExecutorService) bossExecutor).isShutdown()) {
|
||||||
|
|
||||||
|
synchronized (startStopLock) {
|
||||||
|
if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
|
||||||
|
started = false;
|
||||||
|
try {
|
||||||
|
selector.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to close a selector.", e);
|
||||||
|
} finally {
|
||||||
|
this.selector = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
shutdown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Give one more second.
|
||||||
|
shutdown = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shutdown = false;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn(
|
||||||
|
"Unexpected exception in the selector loop.", t);
|
||||||
|
|
||||||
|
// Prevent possible consecutive immediate failures.
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRegisterTaskQueue() {
|
||||||
|
for (;;) {
|
||||||
|
final Runnable task = registerTaskQueue.poll();
|
||||||
|
if (task == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSelectedKeys(Set<SelectionKey> selectedKeys) {
|
||||||
|
for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
|
||||||
|
SelectionKey k = i.next();
|
||||||
|
i.remove();
|
||||||
|
|
||||||
|
if (!k.isValid()) {
|
||||||
|
close(k);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (k.isConnectable()) {
|
||||||
|
connect(k);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processConnectTimeout(Set<SelectionKey> keys, long currentTimeNanos) {
|
||||||
|
ConnectException cause = null;
|
||||||
|
for (SelectionKey k: keys) {
|
||||||
|
if (!k.isValid()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
SctpClientChannel ch = (SctpClientChannel) k.attachment();
|
||||||
|
if (ch.connectDeadlineNanos > 0 &&
|
||||||
|
currentTimeNanos >= ch.connectDeadlineNanos) {
|
||||||
|
|
||||||
|
if (cause == null) {
|
||||||
|
cause = new ConnectException("connection timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
ch.connectFuture.setFailure(cause);
|
||||||
|
fireExceptionCaught(ch, cause);
|
||||||
|
ch.worker.close(ch, succeededFuture(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void connect(SelectionKey k) {
|
||||||
|
SctpClientChannel ch = (SctpClientChannel) k.attachment();
|
||||||
|
try {
|
||||||
|
if (ch.channel.finishConnect()) {
|
||||||
|
k.cancel();
|
||||||
|
ch.worker.register(ch, ch.connectFuture);
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
ch.connectFuture.setFailure(t);
|
||||||
|
fireExceptionCaught(ch, t);
|
||||||
|
k.cancel(); // Some JDK implementations run into an infinite loop without this.
|
||||||
|
ch.worker.close(ch, succeededFuture(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(SelectionKey k) {
|
||||||
|
SctpClientChannel ch = (SctpClientChannel) k.attachment();
|
||||||
|
ch.worker.close(ch, succeededFuture(ch));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class RegisterTask implements Runnable {
|
||||||
|
private final Boss boss;
|
||||||
|
private final SctpClientChannel channel;
|
||||||
|
|
||||||
|
RegisterTask(Boss boss, SctpClientChannel channel) {
|
||||||
|
this.boss = boss;
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
channel.channel.register(
|
||||||
|
boss.selector, SelectionKey.OP_CONNECT, channel);
|
||||||
|
} catch (ClosedChannelException e) {
|
||||||
|
channel.worker.close(channel, succeededFuture(channel));
|
||||||
|
}
|
||||||
|
|
||||||
|
int connectTimeout = channel.getConfig().getConnectTimeoutMillis();
|
||||||
|
if (connectTimeout > 0) {
|
||||||
|
channel.connectDeadlineNanos = System.nanoTime() + connectTimeout * 1000000L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelFactory;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.util.internal.ExecutorUtil;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.jboss.netty.channel.socket.ClientSocketChannelFactory} which creates a client-side NIO-based
|
||||||
|
* {@link org.jboss.netty.channel.socket.SocketChannel}. It utilizes the non-blocking I/O mode which was
|
||||||
|
* introduced with NIO to serve many number of concurrent connections
|
||||||
|
* efficiently.
|
||||||
|
*
|
||||||
|
* <h3>How threads work</h3>
|
||||||
|
* <p>
|
||||||
|
* There are two types of threads in a {@link SctpClientSocketChannelFactory};
|
||||||
|
* one is boss thread and the other is worker thread.
|
||||||
|
*
|
||||||
|
* <h4>Boss thread</h4>
|
||||||
|
* <p>
|
||||||
|
* One {@link SctpClientSocketChannelFactory} has one boss thread. It makes
|
||||||
|
* a connection attempt on request. Once a connection attempt succeeds,
|
||||||
|
* the boss thread passes the connected {@link org.jboss.netty.channel.Channel} to one of the worker
|
||||||
|
* threads that the {@link SctpClientSocketChannelFactory} manages.
|
||||||
|
*
|
||||||
|
* <h4>Worker threads</h4>
|
||||||
|
* <p>
|
||||||
|
* One {@link SctpClientSocketChannelFactory} can have one or more worker
|
||||||
|
* threads. A worker thread performs non-blocking read and write for one or
|
||||||
|
* more {@link org.jboss.netty.channel.Channel}s in a non-blocking mode.
|
||||||
|
*
|
||||||
|
* <h3>Life cycle of threads and graceful shutdown</h3>
|
||||||
|
* <p>
|
||||||
|
* All threads are acquired from the {@link java.util.concurrent.Executor}s which were specified
|
||||||
|
* when a {@link SctpClientSocketChannelFactory} was created. A boss thread is
|
||||||
|
* acquired from the {@code bossExecutor}, and worker threads are acquired from
|
||||||
|
* the {@code workerExecutor}. Therefore, you should make sure the specified
|
||||||
|
* {@link java.util.concurrent.Executor}s are able to lend the sufficient number of threads.
|
||||||
|
* It is the best bet to specify {@linkplain java.util.concurrent.Executors#newCachedThreadPool() a cached thread pool}.
|
||||||
|
* <p>
|
||||||
|
* Both boss and worker threads are acquired lazily, and then released when
|
||||||
|
* there's nothing left to process. All the related resources such as
|
||||||
|
* {@link java.nio.channels.Selector} are also released when the boss and worker threads are
|
||||||
|
* released. Therefore, to shut down a service gracefully, you should do the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>close all channels created by the factory usually using
|
||||||
|
* {@link org.jboss.netty.channel.group.ChannelGroup#close()}, and</li>
|
||||||
|
* <li>call {@link #releaseExternalResources()}.</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* Please make sure not to shut down the executor until all channels are
|
||||||
|
* closed. Otherwise, you will end up with a {@link java.util.concurrent.RejectedExecutionException}
|
||||||
|
* and the related resources might not be released properly.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
* @apiviz.landmark
|
||||||
|
*/
|
||||||
|
public class SctpClientSocketChannelFactory implements ChannelFactory {
|
||||||
|
|
||||||
|
private final Executor bossExecutor;
|
||||||
|
private final Executor workerExecutor;
|
||||||
|
private final SctpClientPipelineSink sink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance. Calling this constructor is same with calling
|
||||||
|
* {@link #SctpClientSocketChannelFactory(java.util.concurrent.Executor, java.util.concurrent.Executor, int)} with 2 *
|
||||||
|
* the number of available processors in the machine. The number of
|
||||||
|
* available processors is obtained by {@link Runtime#availableProcessors()}.
|
||||||
|
*
|
||||||
|
* @param bossExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the boss thread
|
||||||
|
* @param workerExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
|
||||||
|
*/
|
||||||
|
public SctpClientSocketChannelFactory(
|
||||||
|
Executor bossExecutor, Executor workerExecutor) {
|
||||||
|
this(bossExecutor, workerExecutor, SelectorUtil.DEFAULT_IO_THREADS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param bossExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the boss thread
|
||||||
|
* @param workerExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
|
||||||
|
* @param workerCount
|
||||||
|
* the maximum number of I/O worker threads
|
||||||
|
*/
|
||||||
|
public SctpClientSocketChannelFactory(
|
||||||
|
Executor bossExecutor, Executor workerExecutor,
|
||||||
|
int workerCount) {
|
||||||
|
if (bossExecutor == null) {
|
||||||
|
throw new NullPointerException("bossExecutor");
|
||||||
|
}
|
||||||
|
if (workerExecutor == null) {
|
||||||
|
throw new NullPointerException("workerExecutor");
|
||||||
|
}
|
||||||
|
if (workerCount <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"workerCount (" + workerCount + ") " +
|
||||||
|
"must be a positive integer.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bossExecutor = bossExecutor;
|
||||||
|
this.workerExecutor = workerExecutor;
|
||||||
|
sink = new SctpClientPipelineSink(bossExecutor, workerExecutor, workerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SctpChannel newChannel(ChannelPipeline pipeline) {
|
||||||
|
return new SctpClientChannel(this, pipeline, sink, sink.nextWorker());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseExternalResources() {
|
||||||
|
ExecutorUtil.terminate(bossExecutor, workerExecutor);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.Notification;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.ChannelEvent;
|
||||||
|
import org.jboss.netty.channel.ChannelFuture;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class SctpNotificationEvent implements ChannelEvent {
|
||||||
|
private Channel channel;
|
||||||
|
private Notification notification;
|
||||||
|
private Object value;
|
||||||
|
|
||||||
|
public SctpNotificationEvent(Channel channel, Notification notification, Object value) {
|
||||||
|
if (channel == null) {
|
||||||
|
throw new NullPointerException("channel");
|
||||||
|
}
|
||||||
|
if (notification == null) {
|
||||||
|
throw new NullPointerException("notification");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.channel = channel;
|
||||||
|
this.notification = notification;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Channel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChannelFuture getFuture() {
|
||||||
|
return Channels.succeededFuture(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Notification getNotification() {
|
||||||
|
return notification;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the attachment comes with SCTP notification
|
||||||
|
* Please note that, it may be null
|
||||||
|
*/
|
||||||
|
public Object getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.*;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
|
||||||
|
class SctpNotificationHandler extends AbstractNotificationHandler {
|
||||||
|
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpNotificationHandler.class);
|
||||||
|
|
||||||
|
private final SctpChannelImpl sctpChannel;
|
||||||
|
private final SctpWorker sctpWorker;
|
||||||
|
|
||||||
|
public SctpNotificationHandler(SctpChannelImpl sctpChannel, SctpWorker sctpWorker) {
|
||||||
|
this.sctpChannel = sctpChannel;
|
||||||
|
this.sctpWorker = sctpWorker;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerResult handleNotification(AssociationChangeNotification notification, Object o) {
|
||||||
|
fireNotificationReceived(notification, o);
|
||||||
|
return HandlerResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerResult handleNotification(Notification notification, Object o) {
|
||||||
|
fireNotificationReceived(notification, o);
|
||||||
|
return HandlerResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerResult handleNotification(PeerAddressChangeNotification notification, Object o) {
|
||||||
|
fireNotificationReceived(notification, o);
|
||||||
|
return HandlerResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerResult handleNotification(SendFailedNotification notification, Object o) {
|
||||||
|
fireNotificationReceived(notification, o);
|
||||||
|
return HandlerResult.CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HandlerResult handleNotification(ShutdownNotification notification, Object o) {
|
||||||
|
sctpWorker.close(sctpChannel, Channels.succeededFuture(sctpChannel));
|
||||||
|
return HandlerResult.RETURN;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fireNotificationReceived(Notification notification, Object o) {
|
||||||
|
sctpChannel.getPipeline().sendUpstream(new SctpNotificationEvent(sctpChannel, notification, o));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public final class SctpPayload {
|
||||||
|
private final int streamIdentifier;
|
||||||
|
private final int protocolIdentifier;
|
||||||
|
private final ChannelBuffer payloadBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Essential data that is being carried within SCTP Data Chunk
|
||||||
|
* @param streamIdentifier that you want to send the payload
|
||||||
|
* @param protocolIdentifier of payload
|
||||||
|
* @param payloadBuffer channel buffer
|
||||||
|
*/
|
||||||
|
public SctpPayload(int streamIdentifier, int protocolIdentifier, ChannelBuffer payloadBuffer) {
|
||||||
|
this.streamIdentifier = streamIdentifier;
|
||||||
|
this.protocolIdentifier = protocolIdentifier;
|
||||||
|
this.payloadBuffer = payloadBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getStreamIdentifier() {
|
||||||
|
return streamIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getProtocolIdentifier() {
|
||||||
|
return protocolIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ChannelBuffer getPayloadBuffer() {
|
||||||
|
if (payloadBuffer.readable()) {
|
||||||
|
return payloadBuffer.slice();
|
||||||
|
} else {
|
||||||
|
return ChannelBuffers.EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return new StringBuilder().
|
||||||
|
append("SctpPayload{").
|
||||||
|
append("streamIdentifier=").
|
||||||
|
append(streamIdentifier).
|
||||||
|
append(", protocolIdentifier=").
|
||||||
|
append(protocolIdentifier).
|
||||||
|
append(", payloadBuffer=").
|
||||||
|
append(ChannelBuffers.hexDump(getPayloadBuffer())).
|
||||||
|
append('}').toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,433 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpServerChannel;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
import org.jboss.netty.util.internal.SystemPropertyUtil;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.nio.channels.SelectionKey;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.nio.channels.spi.SelectorProvider;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides information which is specific to a NIO service provider
|
||||||
|
* implementation.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class SctpProviderMetadata {
|
||||||
|
static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpProviderMetadata.class);
|
||||||
|
|
||||||
|
private static final String CONSTRAINT_LEVEL_PROPERTY =
|
||||||
|
"org.jboss.netty.channel.socket.sctp.constraintLevel";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 0 - no need to wake up to get / set interestOps (most cases)
|
||||||
|
* 1 - no need to wake up to get interestOps, but need to wake up to set.
|
||||||
|
* 2 - need to wake up to get / set interestOps (old providers)
|
||||||
|
*/
|
||||||
|
static final int CONSTRAINT_LEVEL;
|
||||||
|
|
||||||
|
static {
|
||||||
|
int constraintLevel = -1;
|
||||||
|
|
||||||
|
// Use the system property if possible.
|
||||||
|
constraintLevel = SystemPropertyUtil.get(CONSTRAINT_LEVEL_PROPERTY, -1);
|
||||||
|
if (constraintLevel < 0 || constraintLevel > 2) {
|
||||||
|
constraintLevel = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraintLevel >= 0) {
|
||||||
|
logger.debug(
|
||||||
|
"Setting the NIO constraint level to: " + constraintLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (constraintLevel < 0) {
|
||||||
|
constraintLevel = detectConstraintLevelFromSystemProperties();
|
||||||
|
|
||||||
|
if (constraintLevel < 0) {
|
||||||
|
constraintLevel = 2;
|
||||||
|
logger.debug(
|
||||||
|
"Couldn't determine the NIO constraint level from " +
|
||||||
|
"the system properties; using the safest level (2)");
|
||||||
|
} else if (constraintLevel != 0) {
|
||||||
|
logger.info(
|
||||||
|
"Using the autodetected NIO constraint level: " +
|
||||||
|
constraintLevel +
|
||||||
|
" (Use better NIO provider for better performance)");
|
||||||
|
} else {
|
||||||
|
logger.debug(
|
||||||
|
"Using the autodetected NIO constraint level: " +
|
||||||
|
constraintLevel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CONSTRAINT_LEVEL = constraintLevel;
|
||||||
|
|
||||||
|
if (CONSTRAINT_LEVEL < 0 || CONSTRAINT_LEVEL > 2) {
|
||||||
|
throw new Error(
|
||||||
|
"Unexpected NIO constraint level: " +
|
||||||
|
CONSTRAINT_LEVEL + ", please report this error.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int detectConstraintLevelFromSystemProperties() {
|
||||||
|
String version = SystemPropertyUtil.get("java.specification.version");
|
||||||
|
String vminfo = SystemPropertyUtil.get("java.vm.info", "");
|
||||||
|
String os = SystemPropertyUtil.get("os.name");
|
||||||
|
String vendor = SystemPropertyUtil.get("java.vm.vendor");
|
||||||
|
String provider;
|
||||||
|
try {
|
||||||
|
provider = SelectorProvider.provider().getClass().getName();
|
||||||
|
} catch (Exception e) {
|
||||||
|
// Perhaps security exception.
|
||||||
|
provider = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version == null || os == null || vendor == null || provider == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
os = os.toLowerCase();
|
||||||
|
vendor = vendor.toLowerCase();
|
||||||
|
|
||||||
|
// System.out.println(version);
|
||||||
|
// System.out.println(vminfo);
|
||||||
|
// System.out.println(os);
|
||||||
|
// System.out.println(vendor);
|
||||||
|
// System.out.println(provider);
|
||||||
|
|
||||||
|
// Sun JVM
|
||||||
|
if (vendor.indexOf("sun") >= 0) {
|
||||||
|
// Linux
|
||||||
|
if (os.indexOf("linux") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
|
||||||
|
provider.equals("sun.nio.ch.PollSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
} else if (os.indexOf("windows") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Solaris
|
||||||
|
} else if (os.indexOf("sun") >= 0 || os.indexOf("solaris") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.DevPollSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apple JVM
|
||||||
|
} else if (vendor.indexOf("apple") >= 0) {
|
||||||
|
// Mac OS
|
||||||
|
if (os.indexOf("mac") >= 0 && os.indexOf("os") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.KQueueSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// IBM
|
||||||
|
} else if (vendor.indexOf("ibm") >= 0) {
|
||||||
|
// Linux or AIX
|
||||||
|
if (os.indexOf("linux") >= 0 || os.indexOf("aix") >= 0) {
|
||||||
|
if (version.equals("1.5") || version.matches("^1\\.5\\D.*$")) {
|
||||||
|
if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
} else if (version.equals("1.6") || version.matches("^1\\.6\\D.*$")) {
|
||||||
|
// IBM JDK 1.6 has different constraint level for different
|
||||||
|
// version. The exact version can be determined only by its
|
||||||
|
// build date.
|
||||||
|
Pattern datePattern = Pattern.compile(
|
||||||
|
"(?:^|[^0-9])(" +
|
||||||
|
"[2-9][0-9]{3}" + // year
|
||||||
|
"(?:0[1-9]|1[0-2])" + // month
|
||||||
|
"(?:0[1-9]|[12][0-9]|3[01])" + // day of month
|
||||||
|
")(?:$|[^0-9])");
|
||||||
|
|
||||||
|
Matcher dateMatcher = datePattern.matcher(vminfo);
|
||||||
|
if (dateMatcher.find()) {
|
||||||
|
long dateValue = Long.parseLong(dateMatcher.group(1));
|
||||||
|
if (dateValue < 20081105L) {
|
||||||
|
// SR0, 1, and 2
|
||||||
|
return 2;
|
||||||
|
} else {
|
||||||
|
// SR3 and later
|
||||||
|
if (provider.equals("sun.nio.ch.EPollSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
} else if (provider.equals("sun.nio.ch.PollSelectorProvider")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// BEA
|
||||||
|
} else if (vendor.indexOf("bea") >= 0 || vendor.indexOf("oracle") >= 0) {
|
||||||
|
// Linux
|
||||||
|
if (os.indexOf("linux") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.EPollSelectorProvider") ||
|
||||||
|
provider.equals("sun.nio.ch.PollSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
} else if (os.indexOf("windows") >= 0) {
|
||||||
|
if (provider.equals("sun.nio.ch.WindowsSelectorProvider")) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Apache Software Foundation
|
||||||
|
} else if (vendor.indexOf("apache") >= 0) {
|
||||||
|
if (provider.equals("org.apache.harmony.nio.internal.SelectorProviderImpl")) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Others (untested)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ConstraintLevelAutodetector {
|
||||||
|
|
||||||
|
ConstraintLevelAutodetector() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
int autodetect() {
|
||||||
|
final int constraintLevel;
|
||||||
|
ExecutorService executor = Executors.newCachedThreadPool();
|
||||||
|
boolean success;
|
||||||
|
long startTime;
|
||||||
|
int interestOps;
|
||||||
|
|
||||||
|
SctpServerChannel ch = null;
|
||||||
|
SelectorLoop loop = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Open a channel.
|
||||||
|
ch = com.sun.nio.sctp.SctpServerChannel.open();
|
||||||
|
|
||||||
|
// Configure the channel
|
||||||
|
try {
|
||||||
|
ch.bind(new InetSocketAddress(0));
|
||||||
|
ch.configureBlocking(false);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Failed to configure a temporary socket.", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepare the selector loop.
|
||||||
|
try {
|
||||||
|
loop = new SelectorLoop();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Failed to open a temporary selector.", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the channel
|
||||||
|
try {
|
||||||
|
ch.register(loop.selector, 0);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Failed to register a temporary selector.", e);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectionKey key = ch.keyFor(loop.selector);
|
||||||
|
|
||||||
|
// Start the selector loop.
|
||||||
|
executor.execute(loop);
|
||||||
|
|
||||||
|
// Level 0
|
||||||
|
success = true;
|
||||||
|
for (int i = 0; i < 10; i ++) {
|
||||||
|
|
||||||
|
// Increase the probability of calling interestOps
|
||||||
|
// while select() is running.
|
||||||
|
do {
|
||||||
|
while (!loop.selecting) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little bit more.
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
} while (!loop.selecting);
|
||||||
|
|
||||||
|
startTime = System.nanoTime();
|
||||||
|
key.interestOps(key.interestOps() | SelectionKey.OP_ACCEPT);
|
||||||
|
key.interestOps(key.interestOps() & ~SelectionKey.OP_ACCEPT);
|
||||||
|
|
||||||
|
if (System.nanoTime() - startTime >= 500000000L) {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
constraintLevel = 0;
|
||||||
|
} else {
|
||||||
|
// Level 1
|
||||||
|
success = true;
|
||||||
|
for (int i = 0; i < 10; i ++) {
|
||||||
|
|
||||||
|
// Increase the probability of calling interestOps
|
||||||
|
// while select() is running.
|
||||||
|
do {
|
||||||
|
while (!loop.selecting) {
|
||||||
|
Thread.yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait a little bit more.
|
||||||
|
try {
|
||||||
|
Thread.sleep(50);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
} while (!loop.selecting);
|
||||||
|
|
||||||
|
startTime = System.nanoTime();
|
||||||
|
interestOps = key.interestOps();
|
||||||
|
synchronized (loop) {
|
||||||
|
loop.selector.wakeup();
|
||||||
|
key.interestOps(interestOps | SelectionKey.OP_ACCEPT);
|
||||||
|
key.interestOps(interestOps & ~SelectionKey.OP_ACCEPT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (System.nanoTime() - startTime >= 500000000L) {
|
||||||
|
success = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (success) {
|
||||||
|
constraintLevel = 1;
|
||||||
|
} else {
|
||||||
|
constraintLevel = 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
return -1;
|
||||||
|
} finally {
|
||||||
|
if (ch != null) {
|
||||||
|
try {
|
||||||
|
ch.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Failed to close a temporary socket.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loop != null) {
|
||||||
|
loop.done = true;
|
||||||
|
try {
|
||||||
|
executor.shutdownNow();
|
||||||
|
} catch (NullPointerException ex) {
|
||||||
|
// Some JDK throws NPE here, but shouldn't.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
loop.selector.wakeup();
|
||||||
|
try {
|
||||||
|
if (executor.awaitTermination(1, TimeUnit.SECONDS)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable e) {
|
||||||
|
// Perhaps security exception.
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loop.selector.close();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn("Failed to close a temporary selector.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return constraintLevel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class SelectorLoop implements Runnable {
|
||||||
|
final Selector selector;
|
||||||
|
volatile boolean done;
|
||||||
|
volatile boolean selecting; // Just an approximation
|
||||||
|
|
||||||
|
SelectorLoop() throws IOException {
|
||||||
|
selector = Selector.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
while (!done) {
|
||||||
|
synchronized (this) {
|
||||||
|
// Guard
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
selecting = true;
|
||||||
|
try {
|
||||||
|
selector.select(1000);
|
||||||
|
} finally {
|
||||||
|
selecting = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<SelectionKey> keys = selector.selectedKeys();
|
||||||
|
for (SelectionKey k: keys) {
|
||||||
|
k.interestOps(0);
|
||||||
|
}
|
||||||
|
keys.clear();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn("Failed to wait for a temporary selector.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
for (Entry<Object, Object> e: System.getProperties().entrySet()) {
|
||||||
|
System.out.println(e.getKey() + ": " + e.getValue());
|
||||||
|
}
|
||||||
|
System.out.println();
|
||||||
|
System.out.println("Hard-coded Constraint Level: " + CONSTRAINT_LEVEL);
|
||||||
|
System.out.println(
|
||||||
|
"Auto-detected Constraint Level: " +
|
||||||
|
new ConstraintLevelAutodetector().autodetect());
|
||||||
|
}
|
||||||
|
|
||||||
|
private SctpProviderMetadata() {
|
||||||
|
// Unused
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,102 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
final class SctpReceiveBufferPool {
|
||||||
|
|
||||||
|
private static final int POOL_SIZE = 8;
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private final SoftReference<ByteBuffer>[] pool = new SoftReference[POOL_SIZE];
|
||||||
|
|
||||||
|
SctpReceiveBufferPool() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
final ByteBuffer acquire(int size) {
|
||||||
|
final SoftReference<ByteBuffer>[] pool = this.pool;
|
||||||
|
for (int i = 0; i < POOL_SIZE; i ++) {
|
||||||
|
SoftReference<ByteBuffer> ref = pool[i];
|
||||||
|
if (ref == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buf = ref.get();
|
||||||
|
if (buf == null) {
|
||||||
|
pool[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.capacity() < size) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
pool[i] = null;
|
||||||
|
|
||||||
|
buf.clear();
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer buf = ByteBuffer.allocateDirect(normalizeCapacity(size));
|
||||||
|
buf.clear();
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
final void release(ByteBuffer buffer) {
|
||||||
|
final SoftReference<ByteBuffer>[] pool = this.pool;
|
||||||
|
for (int i = 0; i < POOL_SIZE; i ++) {
|
||||||
|
SoftReference<ByteBuffer> ref = pool[i];
|
||||||
|
if (ref == null || ref.get() == null) {
|
||||||
|
pool[i] = new SoftReference<ByteBuffer>(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pool is full - replace one
|
||||||
|
final int capacity = buffer.capacity();
|
||||||
|
for (int i = 0; i< POOL_SIZE; i ++) {
|
||||||
|
SoftReference<ByteBuffer> ref = pool[i];
|
||||||
|
ByteBuffer pooled = ref.get();
|
||||||
|
if (pooled == null) {
|
||||||
|
pool[i] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pooled.capacity() < capacity) {
|
||||||
|
pool[i] = new SoftReference<ByteBuffer>(buffer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int normalizeCapacity(int capacity) {
|
||||||
|
// Normalize to multiple of 1024
|
||||||
|
int q = capacity >>> 10;
|
||||||
|
int r = capacity & 1023;
|
||||||
|
if (r != 0) {
|
||||||
|
q ++;
|
||||||
|
}
|
||||||
|
return q << 10;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,300 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.MessageInfo;
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.ref.SoftReference;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
final class SctpSendBufferPool {
|
||||||
|
|
||||||
|
private static final SendBuffer EMPTY_BUFFER = new EmptySendBuffer();
|
||||||
|
|
||||||
|
private static final int DEFAULT_PREALLOCATION_SIZE = 65536;
|
||||||
|
private static final int ALIGN_SHIFT = 4;
|
||||||
|
private static final int ALIGN_MASK = 15;
|
||||||
|
|
||||||
|
PreallocationRef poolHead = null;
|
||||||
|
Preallocation current = new Preallocation(DEFAULT_PREALLOCATION_SIZE);
|
||||||
|
|
||||||
|
SctpSendBufferPool() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
final SendBuffer acquire(Object message) {
|
||||||
|
if (message instanceof SctpPayload) {
|
||||||
|
return acquire((SctpPayload) message);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"unsupported message type: " + message.getClass());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final SendBuffer acquire(SctpPayload message) {
|
||||||
|
final ChannelBuffer src = message.getPayloadBuffer();
|
||||||
|
final int streamNo = message.getStreamIdentifier();
|
||||||
|
final int protocolId = message.getProtocolIdentifier();
|
||||||
|
|
||||||
|
final int size = src.readableBytes();
|
||||||
|
if (size == 0) {
|
||||||
|
return EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.isDirect()) {
|
||||||
|
return new UnpooledSendBuffer(streamNo, protocolId, src.toByteBuffer());
|
||||||
|
}
|
||||||
|
if (src.readableBytes() > DEFAULT_PREALLOCATION_SIZE) {
|
||||||
|
return new UnpooledSendBuffer(streamNo, protocolId, src.toByteBuffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
Preallocation current = this.current;
|
||||||
|
ByteBuffer buffer = current.buffer;
|
||||||
|
int remaining = buffer.remaining();
|
||||||
|
PooledSendBuffer dst;
|
||||||
|
|
||||||
|
if (size < remaining) {
|
||||||
|
int nextPos = buffer.position() + size;
|
||||||
|
ByteBuffer slice = buffer.duplicate();
|
||||||
|
buffer.position(align(nextPos));
|
||||||
|
slice.limit(nextPos);
|
||||||
|
current.refCnt++;
|
||||||
|
dst = new PooledSendBuffer(streamNo, protocolId, current, slice);
|
||||||
|
} else if (size > remaining) {
|
||||||
|
this.current = current = getPreallocation();
|
||||||
|
buffer = current.buffer;
|
||||||
|
ByteBuffer slice = buffer.duplicate();
|
||||||
|
buffer.position(align(size));
|
||||||
|
slice.limit(size);
|
||||||
|
current.refCnt++;
|
||||||
|
dst = new PooledSendBuffer(streamNo, protocolId, current, slice);
|
||||||
|
} else { // size == remaining
|
||||||
|
current.refCnt++;
|
||||||
|
this.current = getPreallocation0();
|
||||||
|
dst = new PooledSendBuffer(streamNo, protocolId, current, current.buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer dstbuf = dst.buffer;
|
||||||
|
dstbuf.mark();
|
||||||
|
src.getBytes(src.readerIndex(), dstbuf);
|
||||||
|
dstbuf.reset();
|
||||||
|
return dst;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Preallocation getPreallocation() {
|
||||||
|
Preallocation current = this.current;
|
||||||
|
if (current.refCnt == 0) {
|
||||||
|
current.buffer.clear();
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPreallocation0();
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Preallocation getPreallocation0() {
|
||||||
|
PreallocationRef ref = poolHead;
|
||||||
|
if (ref != null) {
|
||||||
|
do {
|
||||||
|
Preallocation p = ref.get();
|
||||||
|
ref = ref.next;
|
||||||
|
|
||||||
|
if (p != null) {
|
||||||
|
poolHead = ref;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
} while (ref != null);
|
||||||
|
|
||||||
|
poolHead = ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Preallocation(DEFAULT_PREALLOCATION_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int align(int pos) {
|
||||||
|
int q = pos >>> ALIGN_SHIFT;
|
||||||
|
int r = pos & ALIGN_MASK;
|
||||||
|
if (r != 0) {
|
||||||
|
q++;
|
||||||
|
}
|
||||||
|
return q << ALIGN_SHIFT;
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Preallocation {
|
||||||
|
final ByteBuffer buffer;
|
||||||
|
int refCnt;
|
||||||
|
|
||||||
|
Preallocation(int capacity) {
|
||||||
|
buffer = ByteBuffer.allocateDirect(capacity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class PreallocationRef extends SoftReference<Preallocation> {
|
||||||
|
final PreallocationRef next;
|
||||||
|
|
||||||
|
PreallocationRef(Preallocation prealloation, PreallocationRef next) {
|
||||||
|
super(prealloation);
|
||||||
|
this.next = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SendBuffer {
|
||||||
|
boolean finished();
|
||||||
|
|
||||||
|
long writtenBytes();
|
||||||
|
|
||||||
|
long totalBytes();
|
||||||
|
|
||||||
|
long transferTo(SctpChannel ch) throws IOException;
|
||||||
|
|
||||||
|
void release();
|
||||||
|
}
|
||||||
|
|
||||||
|
class UnpooledSendBuffer implements SendBuffer {
|
||||||
|
|
||||||
|
final ByteBuffer buffer;
|
||||||
|
final int initialPos;
|
||||||
|
final int streamNo;
|
||||||
|
final int protocolId;
|
||||||
|
|
||||||
|
UnpooledSendBuffer(int streamNo, int protocolId, ByteBuffer buffer) {
|
||||||
|
this.streamNo = streamNo;
|
||||||
|
this.protocolId = protocolId;
|
||||||
|
this.buffer = buffer;
|
||||||
|
initialPos = buffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean finished() {
|
||||||
|
return !buffer.hasRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final long writtenBytes() {
|
||||||
|
return buffer.position() - initialPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final long totalBytes() {
|
||||||
|
return buffer.limit() - initialPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long transferTo(SctpChannel ch) throws IOException {
|
||||||
|
final MessageInfo messageInfo = MessageInfo.createOutgoing(ch.association(), null, streamNo);
|
||||||
|
messageInfo.payloadProtocolID(protocolId);
|
||||||
|
messageInfo.streamNumber(streamNo);
|
||||||
|
ch.send(buffer, messageInfo);
|
||||||
|
return writtenBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Unpooled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class PooledSendBuffer implements SendBuffer {
|
||||||
|
|
||||||
|
private final Preallocation parent;
|
||||||
|
final ByteBuffer buffer;
|
||||||
|
final int initialPos;
|
||||||
|
final int streamNo;
|
||||||
|
final int protocolId;
|
||||||
|
|
||||||
|
PooledSendBuffer(int streamNo, int protocolId, Preallocation parent, ByteBuffer buffer) {
|
||||||
|
this.streamNo = streamNo;
|
||||||
|
this.protocolId = protocolId;
|
||||||
|
this.parent = parent;
|
||||||
|
this.buffer = buffer;
|
||||||
|
initialPos = buffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean finished() {
|
||||||
|
return !buffer.hasRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long writtenBytes() {
|
||||||
|
return buffer.position() - initialPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long totalBytes() {
|
||||||
|
return buffer.limit() - initialPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long transferTo(SctpChannel ch) throws IOException {
|
||||||
|
final MessageInfo messageInfo = MessageInfo.createOutgoing(ch.association(), null, streamNo);
|
||||||
|
messageInfo.payloadProtocolID(protocolId);
|
||||||
|
messageInfo.streamNumber(streamNo);
|
||||||
|
ch.send(buffer, messageInfo);
|
||||||
|
return writtenBytes();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
final Preallocation parent = this.parent;
|
||||||
|
if (--parent.refCnt == 0) {
|
||||||
|
parent.buffer.clear();
|
||||||
|
if (parent != current) {
|
||||||
|
poolHead = new PreallocationRef(parent, poolHead);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final class EmptySendBuffer implements SendBuffer {
|
||||||
|
|
||||||
|
EmptySendBuffer() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean finished() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final long writtenBytes() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final long totalBytes() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long transferTo(SctpChannel ch) throws IOException {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
// Unpooled.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ServerChannel;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A SCTP {@link org.jboss.netty.channel.ServerChannel} which accepts incoming SCTP connections.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
* @version $Rev$, $Date$
|
||||||
|
*/
|
||||||
|
public interface SctpServerChannel extends ServerChannel {
|
||||||
|
/**
|
||||||
|
* Returns the configuration of this channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
SctpServerChannelConfig getConfig();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the primary local address of the SCTP server channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
InetSocketAddress getLocalAddress();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all local addresses of the SCTP server channel.
|
||||||
|
*/
|
||||||
|
Set<InetSocketAddress> getAllLocalAddresses();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the primary remote address of the server SCTP channel.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
InetSocketAddress getRemoteAddress();
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return all remote addresses of the SCTP server channel.
|
||||||
|
*/
|
||||||
|
Set<InetSocketAddress> getAllRemoteAddresses();
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import static com.sun.nio.sctp.SctpStandardSocketOptions.*;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelConfig;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.jboss.netty.channel.ChannelConfig} for a {@link SctpServerChannelConfig}.
|
||||||
|
* <p/>
|
||||||
|
* <h3>Available options</h3>
|
||||||
|
* <p/>
|
||||||
|
* In addition to the options provided by {@link org.jboss.netty.channel.ChannelConfig},
|
||||||
|
* {@link SctpServerChannelConfig} allows the following options in the
|
||||||
|
* option map:
|
||||||
|
* <p/>
|
||||||
|
* <table border="1" cellspacing="0" cellpadding="6">
|
||||||
|
* <tr>
|
||||||
|
* <th>Name</th><th>Associated setter method</th>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "backlog"}</td><td>{@link #setBacklog(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* * <td>{@code "receiveBufferSize"}</td><td>{@link #setReceiveBufferSize(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "sendBufferSize"}</td><td>{@link #setSendBufferSize(int)}</td>
|
||||||
|
* </tr><tr>
|
||||||
|
* <td>{@code "sctpInitMaxStreams"}</td><td>{@link #setInitMaxStreams(InitMaxStreams)} (int)}}</td>
|
||||||
|
* </tr>
|
||||||
|
* </table>
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public interface SctpServerChannelConfig extends ChannelConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the backlog value to specify when the channel binds to a local
|
||||||
|
* address.
|
||||||
|
*/
|
||||||
|
int getBacklog();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the backlog value to specify when the channel binds to a local
|
||||||
|
* address.
|
||||||
|
*/
|
||||||
|
void setBacklog(int backlog);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
|
||||||
|
*/
|
||||||
|
int getSendBufferSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_SNDBUF}</a> option.
|
||||||
|
*/
|
||||||
|
void setSendBufferSize(int sendBufferSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
|
||||||
|
*/
|
||||||
|
int getReceiveBufferSize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SO_RCVBUF}</a> option.
|
||||||
|
*/
|
||||||
|
void setReceiveBufferSize(int receiveBufferSize);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
|
||||||
|
*/
|
||||||
|
InitMaxStreams getInitMaxStreams();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the <a href="http://openjdk.java.net/projects/sctp/javadoc/com/sun/nio/sctp/SctpStandardSocketOption.html">{@code SCTP_INIT_MAXSTREAMS}</a> option.
|
||||||
|
*/
|
||||||
|
void setInitMaxStreams(InitMaxStreams initMaxStreams);
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.locks.Lock;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.fireChannelOpen;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class SctpServerChannelImpl extends AbstractServerChannel
|
||||||
|
implements SctpServerChannel {
|
||||||
|
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpServerChannelImpl.class);
|
||||||
|
|
||||||
|
final com.sun.nio.sctp.SctpServerChannel serverChannel;
|
||||||
|
final Lock shutdownLock = new ReentrantLock();
|
||||||
|
volatile Selector selector;
|
||||||
|
private final SctpServerChannelConfig config;
|
||||||
|
|
||||||
|
private volatile boolean bound;
|
||||||
|
|
||||||
|
SctpServerChannelImpl(
|
||||||
|
ChannelFactory factory,
|
||||||
|
ChannelPipeline pipeline,
|
||||||
|
ChannelSink sink) {
|
||||||
|
|
||||||
|
super(factory, pipeline, sink);
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverChannel = com.sun.nio.sctp.SctpServerChannel.open();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new ChannelException(
|
||||||
|
"Failed to open a server socket.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
serverChannel.configureBlocking(false);
|
||||||
|
} catch (IOException e) {
|
||||||
|
try {
|
||||||
|
serverChannel.close();
|
||||||
|
} catch (IOException e2) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to close a partially initialized socket.", e2);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ChannelException("Failed to enter non-blocking mode.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
config = new DefaultSctpServerChannelConfig(serverChannel);
|
||||||
|
|
||||||
|
fireChannelOpen(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SctpServerChannelConfig getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getLocalAddress() {
|
||||||
|
try {
|
||||||
|
final Iterator<SocketAddress> iterator = serverChannel.getAllLocalAddresses().iterator();
|
||||||
|
return iterator.hasNext() ? (InetSocketAddress) iterator.next() : null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<InetSocketAddress> getAllLocalAddresses() {
|
||||||
|
try {
|
||||||
|
final Set<SocketAddress> allLocalAddresses = serverChannel.getAllLocalAddresses();
|
||||||
|
final Set<InetSocketAddress> addresses = new HashSet<InetSocketAddress>(allLocalAddresses.size());
|
||||||
|
for (SocketAddress socketAddress : allLocalAddresses) {
|
||||||
|
addresses.add((InetSocketAddress) socketAddress);
|
||||||
|
}
|
||||||
|
return addresses;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
return Collections.emptySet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InetSocketAddress getRemoteAddress() {
|
||||||
|
return null;// not available for server channel
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<InetSocketAddress> getAllRemoteAddresses() {
|
||||||
|
return null;// not available for server channel
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isBound() {
|
||||||
|
return isOpen() && bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setBound() {
|
||||||
|
bound = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean setClosed() {
|
||||||
|
return super.setClosed();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,288 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.SctpChannel;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
import org.jboss.netty.util.internal.DeadLockProofWorker;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.nio.channels.*;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
class SctpServerPipelineSink extends AbstractChannelSink {
|
||||||
|
|
||||||
|
static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpServerPipelineSink.class);
|
||||||
|
|
||||||
|
private final SctpWorker[] workers;
|
||||||
|
private final AtomicInteger workerIndex = new AtomicInteger();
|
||||||
|
|
||||||
|
SctpServerPipelineSink(Executor workerExecutor, int workerCount) {
|
||||||
|
workers = new SctpWorker[workerCount];
|
||||||
|
for (int i = 0; i < workers.length; i ++) {
|
||||||
|
workers[i] = new SctpWorker(workerExecutor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void eventSunk(
|
||||||
|
ChannelPipeline pipeline, ChannelEvent e) throws Exception {
|
||||||
|
Channel channel = e.getChannel();
|
||||||
|
if (channel instanceof SctpServerChannelImpl) {
|
||||||
|
handleServerSocket(e);
|
||||||
|
} else if (channel instanceof SctpChannelImpl) {
|
||||||
|
handleAcceptedSocket(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleServerSocket(ChannelEvent e) {
|
||||||
|
if (!(e instanceof ChannelStateEvent)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelStateEvent event = (ChannelStateEvent) e;
|
||||||
|
SctpServerChannelImpl channel =
|
||||||
|
(SctpServerChannelImpl) event.getChannel();
|
||||||
|
ChannelFuture future = event.getFuture();
|
||||||
|
ChannelState state = event.getState();
|
||||||
|
Object value = event.getValue();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
if (Boolean.FALSE.equals(value)) {
|
||||||
|
close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BOUND:
|
||||||
|
if (value != null) {
|
||||||
|
bind(channel, future, (SocketAddress) value);
|
||||||
|
} else {
|
||||||
|
close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleAcceptedSocket(ChannelEvent e) {
|
||||||
|
if (e instanceof ChannelStateEvent) {
|
||||||
|
ChannelStateEvent event = (ChannelStateEvent) e;
|
||||||
|
SctpChannelImpl channel = (SctpChannelImpl) event.getChannel();
|
||||||
|
ChannelFuture future = event.getFuture();
|
||||||
|
ChannelState state = event.getState();
|
||||||
|
Object value = event.getValue();
|
||||||
|
|
||||||
|
switch (state) {
|
||||||
|
case OPEN:
|
||||||
|
if (Boolean.FALSE.equals(value)) {
|
||||||
|
channel.worker.close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case BOUND:
|
||||||
|
case CONNECTED:
|
||||||
|
if (value == null) {
|
||||||
|
channel.worker.close(channel, future);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case INTEREST_OPS:
|
||||||
|
channel.worker.setInterestOps(channel, future, (Integer) value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (e instanceof MessageEvent) {
|
||||||
|
MessageEvent event = (MessageEvent) e;
|
||||||
|
SctpChannelImpl channel = (SctpChannelImpl) event.getChannel();
|
||||||
|
boolean offered = channel.writeBuffer.offer(event);
|
||||||
|
assert offered;
|
||||||
|
channel.worker.writeFromUserCode(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void bind(
|
||||||
|
SctpServerChannelImpl channel, ChannelFuture future,
|
||||||
|
SocketAddress localAddress) {
|
||||||
|
|
||||||
|
boolean bound = false;
|
||||||
|
boolean bossStarted = false;
|
||||||
|
try {
|
||||||
|
channel.serverChannel.bind(localAddress, channel.getConfig().getBacklog());
|
||||||
|
bound = true;
|
||||||
|
channel.setBound();
|
||||||
|
future.setSuccess();
|
||||||
|
fireChannelBound(channel, channel.getLocalAddress());
|
||||||
|
|
||||||
|
Executor bossExecutor =
|
||||||
|
((SctpServerSocketChannelFactory) channel.getFactory()).bossExecutor;
|
||||||
|
DeadLockProofWorker.start(bossExecutor, new Boss(channel));
|
||||||
|
bossStarted = true;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
} finally {
|
||||||
|
if (!bossStarted && bound) {
|
||||||
|
close(channel, future);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(SctpServerChannelImpl channel, ChannelFuture future) {
|
||||||
|
boolean bound = channel.isBound();
|
||||||
|
try {
|
||||||
|
if (channel.serverChannel.isOpen()) {
|
||||||
|
channel.serverChannel.close();
|
||||||
|
Selector selector = channel.selector;
|
||||||
|
if (selector != null) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure the boss thread is not running so that that the future
|
||||||
|
// is notified after a new connection cannot be accepted anymore.
|
||||||
|
// See NETTY-256 for more information.
|
||||||
|
channel.shutdownLock.lock();
|
||||||
|
try {
|
||||||
|
if (channel.setClosed()) {
|
||||||
|
future.setSuccess();
|
||||||
|
if (bound) {
|
||||||
|
fireChannelUnbound(channel);
|
||||||
|
}
|
||||||
|
fireChannelClosed(channel);
|
||||||
|
} else {
|
||||||
|
future.setSuccess();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
channel.shutdownLock.unlock();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SctpWorker nextWorker() {
|
||||||
|
return workers[Math.abs(
|
||||||
|
workerIndex.getAndIncrement() % workers.length)];
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class Boss implements Runnable {
|
||||||
|
private final Selector selector;
|
||||||
|
private final SctpServerChannelImpl channel;
|
||||||
|
|
||||||
|
Boss(SctpServerChannelImpl channel) throws IOException {
|
||||||
|
this.channel = channel;
|
||||||
|
|
||||||
|
selector = Selector.open();
|
||||||
|
|
||||||
|
boolean registered = false;
|
||||||
|
try {
|
||||||
|
channel.serverChannel.register(selector, SelectionKey.OP_ACCEPT);
|
||||||
|
registered = true;
|
||||||
|
} finally {
|
||||||
|
if (!registered) {
|
||||||
|
closeSelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.selector = selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
final Thread currentThread = Thread.currentThread();
|
||||||
|
|
||||||
|
channel.shutdownLock.lock();
|
||||||
|
try {
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
if (selector.select(500) > 0) {
|
||||||
|
selector.selectedKeys().clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
SctpChannel acceptedSocket = channel.serverChannel.accept();
|
||||||
|
if (acceptedSocket != null) {
|
||||||
|
registerAcceptedChannel(acceptedSocket, currentThread);
|
||||||
|
}
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// Thrown every second to get ClosedChannelException
|
||||||
|
// raised.
|
||||||
|
} catch (CancelledKeyException e) {
|
||||||
|
// Raised by accept() when the server socket was closed.
|
||||||
|
} catch (ClosedSelectorException e) {
|
||||||
|
// Raised by accept() when the server socket was closed.
|
||||||
|
} catch (ClosedChannelException e) {
|
||||||
|
// Closed as requested.
|
||||||
|
break;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to accept a connection.", e);
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e1) {
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
channel.shutdownLock.unlock();
|
||||||
|
closeSelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void registerAcceptedChannel(SctpChannel acceptedSocket, Thread currentThread) {
|
||||||
|
try {
|
||||||
|
ChannelPipeline pipeline =
|
||||||
|
channel.getConfig().getPipelineFactory().getPipeline();
|
||||||
|
SctpWorker worker = nextWorker();
|
||||||
|
worker.register(new SctpAcceptedChannel(
|
||||||
|
channel.getFactory(), pipeline, channel,
|
||||||
|
SctpServerPipelineSink.this, acceptedSocket,
|
||||||
|
worker, currentThread), null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to initialize an accepted socket.", e);
|
||||||
|
try {
|
||||||
|
acceptedSocket.close();
|
||||||
|
} catch (IOException e2) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to close a partially accepted socket.",
|
||||||
|
e2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeSelector() {
|
||||||
|
channel.selector = null;
|
||||||
|
try {
|
||||||
|
selector.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("Failed to close a selector.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelSink;
|
||||||
|
import org.jboss.netty.channel.ServerChannelFactory;
|
||||||
|
import org.jboss.netty.util.internal.ExecutorUtil;
|
||||||
|
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link org.jboss.netty.channel.socket.ServerSocketChannelFactory} which creates a server-side NIO-based
|
||||||
|
* {@link org.jboss.netty.channel.socket.ServerSocketChannel}. It utilizes the non-blocking I/O mode which
|
||||||
|
* was introduced with NIO to serve many number of concurrent connections
|
||||||
|
* efficiently.
|
||||||
|
*
|
||||||
|
* <h3>How threads work</h3>
|
||||||
|
* <p>
|
||||||
|
* There are two types of threads in a {@link SctpServerSocketChannelFactory};
|
||||||
|
* one is boss thread and the other is worker thread.
|
||||||
|
*
|
||||||
|
* <h4>Boss threads</h4>
|
||||||
|
* <p>
|
||||||
|
* Each bound {@link org.jboss.netty.channel.socket.ServerSocketChannel} has its own boss thread.
|
||||||
|
* For example, if you opened two server ports such as 80 and 443, you will
|
||||||
|
* have two boss threads. A boss thread accepts incoming connections until
|
||||||
|
* the port is unbound. Once a connection is accepted successfully, the boss
|
||||||
|
* thread passes the accepted {@link org.jboss.netty.channel.Channel} to one of the worker
|
||||||
|
* threads that the {@link SctpServerSocketChannelFactory} manages.
|
||||||
|
*
|
||||||
|
* <h4>Worker threads</h4>
|
||||||
|
* <p>
|
||||||
|
* One {@link SctpServerSocketChannelFactory} can have one or more worker
|
||||||
|
* threads. A worker thread performs non-blocking read and write for one or
|
||||||
|
* more {@link org.jboss.netty.channel.Channel}s in a non-blocking mode.
|
||||||
|
*
|
||||||
|
* <h3>Life cycle of threads and graceful shutdown</h3>
|
||||||
|
* <p>
|
||||||
|
* All threads are acquired from the {@link java.util.concurrent.Executor}s which were specified
|
||||||
|
* when a {@link SctpServerSocketChannelFactory} was created. Boss threads are
|
||||||
|
* acquired from the {@code bossExecutor}, and worker threads are acquired from
|
||||||
|
* the {@code workerExecutor}. Therefore, you should make sure the specified
|
||||||
|
* {@link java.util.concurrent.Executor}s are able to lend the sufficient number of threads.
|
||||||
|
* It is the best bet to specify {@linkplain java.util.concurrent.Executors#newCachedThreadPool() a cached thread pool}.
|
||||||
|
* <p>
|
||||||
|
* Both boss and worker threads are acquired lazily, and then released when
|
||||||
|
* there's nothing left to process. All the related resources such as
|
||||||
|
* {@link java.nio.channels.Selector} are also released when the boss and worker threads are
|
||||||
|
* released. Therefore, to shut down a service gracefully, you should do the
|
||||||
|
* following:
|
||||||
|
*
|
||||||
|
* <ol>
|
||||||
|
* <li>unbind all channels created by the factory,
|
||||||
|
* <li>close all child channels accepted by the unbound channels, and
|
||||||
|
* (these two steps so far is usually done using {@link org.jboss.netty.channel.group.ChannelGroup#close()})</li>
|
||||||
|
* <li>call {@link #releaseExternalResources()}.</li>
|
||||||
|
* </ol>
|
||||||
|
*
|
||||||
|
* Please make sure not to shut down the executor until all channels are
|
||||||
|
* closed. Otherwise, you will end up with a {@link java.util.concurrent.RejectedExecutionException}
|
||||||
|
* and the related resources might not be released properly.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*
|
||||||
|
* @apiviz.landmark
|
||||||
|
*/
|
||||||
|
public class SctpServerSocketChannelFactory implements ServerChannelFactory {
|
||||||
|
|
||||||
|
final Executor bossExecutor;
|
||||||
|
private final Executor workerExecutor;
|
||||||
|
private final ChannelSink sink;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance. Calling this constructor is same with calling
|
||||||
|
* {@link #SctpServerSocketChannelFactory(java.util.concurrent.Executor, java.util.concurrent.Executor, int)} with 2 *
|
||||||
|
* the number of available processors in the machine. The number of
|
||||||
|
* available processors is obtained by {@link Runtime#availableProcessors()}.
|
||||||
|
*
|
||||||
|
* @param bossExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the boss threads
|
||||||
|
* @param workerExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
|
||||||
|
*/
|
||||||
|
public SctpServerSocketChannelFactory(
|
||||||
|
Executor bossExecutor, Executor workerExecutor) {
|
||||||
|
this(bossExecutor, workerExecutor, SelectorUtil.DEFAULT_IO_THREADS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param bossExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the boss threads
|
||||||
|
* @param workerExecutor
|
||||||
|
* the {@link java.util.concurrent.Executor} which will execute the I/O worker threads
|
||||||
|
* @param workerCount
|
||||||
|
* the maximum number of I/O worker threads
|
||||||
|
*/
|
||||||
|
public SctpServerSocketChannelFactory(
|
||||||
|
Executor bossExecutor, Executor workerExecutor,
|
||||||
|
int workerCount) {
|
||||||
|
if (bossExecutor == null) {
|
||||||
|
throw new NullPointerException("bossExecutor");
|
||||||
|
}
|
||||||
|
if (workerExecutor == null) {
|
||||||
|
throw new NullPointerException("workerExecutor");
|
||||||
|
}
|
||||||
|
if (workerCount <= 0) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"workerCount (" + workerCount + ") " +
|
||||||
|
"must be a positive integer.");
|
||||||
|
}
|
||||||
|
this.bossExecutor = bossExecutor;
|
||||||
|
this.workerExecutor = workerExecutor;
|
||||||
|
sink = new SctpServerPipelineSink(workerExecutor, workerCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SctpServerChannel newChannel(ChannelPipeline pipeline) {
|
||||||
|
return new SctpServerChannelImpl(this, pipeline, sink);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releaseExternalResources() {
|
||||||
|
ExecutorUtil.terminate(bossExecutor, workerExecutor);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,784 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import com.sun.nio.sctp.*;
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffer;
|
||||||
|
import org.jboss.netty.buffer.ChannelBufferFactory;
|
||||||
|
import org.jboss.netty.channel.Channel;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.socket.sctp.SctpSendBufferPool.SendBuffer;
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
import org.jboss.netty.util.internal.DeadLockProofWorker;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.SocketAddress;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.channels.*;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.Queue;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.LinkedTransferQueue;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.concurrent.locks.ReadWriteLock;
|
||||||
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
|
import static org.jboss.netty.channel.Channels.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
class SctpWorker implements Runnable {
|
||||||
|
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SctpWorker.class);
|
||||||
|
|
||||||
|
private static final int CONSTRAINT_LEVEL = SctpProviderMetadata.CONSTRAINT_LEVEL;
|
||||||
|
|
||||||
|
static final int CLEANUP_INTERVAL = 256; // XXX Hard-coded value, but won't need customization.
|
||||||
|
|
||||||
|
private final Executor executor;
|
||||||
|
private boolean started;
|
||||||
|
private volatile Thread thread;
|
||||||
|
volatile Selector selector;
|
||||||
|
private final AtomicBoolean wakenUp = new AtomicBoolean();
|
||||||
|
private final ReadWriteLock selectorGuard = new ReentrantReadWriteLock();
|
||||||
|
private final Object startStopLock = new Object();
|
||||||
|
private final Queue<Runnable> registerTaskQueue = new LinkedTransferQueue<Runnable>();
|
||||||
|
private final Queue<Runnable> writeTaskQueue = new LinkedTransferQueue<Runnable>();
|
||||||
|
private volatile int cancelledKeys; // should use AtomicInteger but we just need approximation
|
||||||
|
|
||||||
|
private final SctpReceiveBufferPool recvBufferPool = new SctpReceiveBufferPool();
|
||||||
|
private final SctpSendBufferPool sendBufferPool = new SctpSendBufferPool();
|
||||||
|
|
||||||
|
private SctpNotificationHandler notificationHandler;
|
||||||
|
|
||||||
|
SctpWorker(Executor executor) {
|
||||||
|
this.executor = executor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void register(SctpChannelImpl channel, ChannelFuture future) {
|
||||||
|
|
||||||
|
boolean server = !(channel instanceof SctpClientChannel);
|
||||||
|
Runnable registerTask = new RegisterTask(channel, future, server);
|
||||||
|
notificationHandler = new SctpNotificationHandler(channel, this);
|
||||||
|
Selector selector;
|
||||||
|
|
||||||
|
synchronized (startStopLock) {
|
||||||
|
if (!started) {
|
||||||
|
// Open a selector if this worker didn't start yet.
|
||||||
|
try {
|
||||||
|
this.selector = selector = Selector.open();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
throw new ChannelException(
|
||||||
|
"Failed to create a selector.", t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start the worker thread with the new Selector.
|
||||||
|
boolean success = false;
|
||||||
|
try {
|
||||||
|
DeadLockProofWorker.start(executor, this);
|
||||||
|
success = true;
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
// Release the Selector if the execution fails.
|
||||||
|
try {
|
||||||
|
selector.close();
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn("Failed to close a selector.", t);
|
||||||
|
}
|
||||||
|
this.selector = selector = null;
|
||||||
|
// The method will return to the caller at this point.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Use the existing selector if this worker has been started.
|
||||||
|
selector = this.selector;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert selector != null && selector.isOpen();
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
boolean offered = registerTaskQueue.offer(registerTask);
|
||||||
|
assert offered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
thread = Thread.currentThread();
|
||||||
|
|
||||||
|
boolean shutdown = false;
|
||||||
|
Selector selector = this.selector;
|
||||||
|
for (; ;) {
|
||||||
|
wakenUp.set(false);
|
||||||
|
|
||||||
|
if (CONSTRAINT_LEVEL != 0) {
|
||||||
|
selectorGuard.writeLock().lock();
|
||||||
|
// This empty synchronization block prevents the selector
|
||||||
|
// from acquiring its lock.
|
||||||
|
selectorGuard.writeLock().unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
SelectorUtil.select(selector);
|
||||||
|
|
||||||
|
// 'wakenUp.compareAndSet(false, true)' is always evaluated
|
||||||
|
// before calling 'selector.wakeup()' to reduce the wake-up
|
||||||
|
// overhead. (Selector.wakeup() is an expensive operation.)
|
||||||
|
//
|
||||||
|
// However, there is a race condition in this approach.
|
||||||
|
// The race condition is triggered when 'wakenUp' is set to
|
||||||
|
// true too early.
|
||||||
|
//
|
||||||
|
// 'wakenUp' is set to true too early if:
|
||||||
|
// 1) Selector is waken up between 'wakenUp.set(false)' and
|
||||||
|
// 'selector.select(...)'. (BAD)
|
||||||
|
// 2) Selector is waken up between 'selector.select(...)' and
|
||||||
|
// 'if (wakenUp.get()) { ... }'. (OK)
|
||||||
|
//
|
||||||
|
// In the first case, 'wakenUp' is set to true and the
|
||||||
|
// following 'selector.select(...)' will wake up immediately.
|
||||||
|
// Until 'wakenUp' is set to false again in the next round,
|
||||||
|
// 'wakenUp.compareAndSet(false, true)' will fail, and therefore
|
||||||
|
// any attempt to wake up the Selector will fail, too, causing
|
||||||
|
// the following 'selector.select(...)' call to block
|
||||||
|
// unnecessarily.
|
||||||
|
//
|
||||||
|
// To fix this problem, we wake up the selector again if wakenUp
|
||||||
|
// is true immediately after selector.select(...).
|
||||||
|
// It is inefficient in that it wakes up the selector for both
|
||||||
|
// the first case (BAD - wake-up required) and the second case
|
||||||
|
// (OK - no wake-up required).
|
||||||
|
|
||||||
|
if (wakenUp.get()) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelledKeys = 0;
|
||||||
|
processRegisterTaskQueue();
|
||||||
|
processWriteTaskQueue();
|
||||||
|
processSelectedKeys(selector.selectedKeys());
|
||||||
|
|
||||||
|
// Exit the loop when there's nothing to handle.
|
||||||
|
// The shutdown flag is used to delay the shutdown of this
|
||||||
|
// loop to avoid excessive Selector creation when
|
||||||
|
// connections are registered in a one-by-one manner instead of
|
||||||
|
// concurrent manner.
|
||||||
|
if (selector.keys().isEmpty()) {
|
||||||
|
if (shutdown ||
|
||||||
|
executor instanceof ExecutorService && ((ExecutorService) executor).isShutdown()) {
|
||||||
|
|
||||||
|
synchronized (startStopLock) {
|
||||||
|
if (registerTaskQueue.isEmpty() && selector.keys().isEmpty()) {
|
||||||
|
started = false;
|
||||||
|
try {
|
||||||
|
selector.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to close a selector.", e);
|
||||||
|
} finally {
|
||||||
|
this.selector = null;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
shutdown = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Give one more second.
|
||||||
|
shutdown = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
shutdown = false;
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
logger.warn(
|
||||||
|
"Unexpected exception in the selector loop.", t);
|
||||||
|
|
||||||
|
// Prevent possible consecutive immediate failures that lead to
|
||||||
|
// excessive CPU consumption.
|
||||||
|
try {
|
||||||
|
Thread.sleep(1000);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Ignore.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processRegisterTaskQueue() throws IOException {
|
||||||
|
for (; ;) {
|
||||||
|
final Runnable task = registerTaskQueue.poll();
|
||||||
|
if (task == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
cleanUpCancelledKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processWriteTaskQueue() throws IOException {
|
||||||
|
for (; ;) {
|
||||||
|
final Runnable task = writeTaskQueue.poll();
|
||||||
|
if (task == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
task.run();
|
||||||
|
cleanUpCancelledKeys();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processSelectedKeys(final Set<SelectionKey> selectedKeys) throws IOException {
|
||||||
|
for (Iterator<SelectionKey> i = selectedKeys.iterator(); i.hasNext();) {
|
||||||
|
SelectionKey k = i.next();
|
||||||
|
i.remove();
|
||||||
|
try {
|
||||||
|
int readyOps = k.readyOps();
|
||||||
|
if ((readyOps & SelectionKey.OP_READ) != 0 || readyOps == 0) {
|
||||||
|
if (!read(k)) {
|
||||||
|
// Connection already closed - no need to handle write.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((readyOps & SelectionKey.OP_WRITE) != 0) {
|
||||||
|
writeFromSelectorLoop(k);
|
||||||
|
}
|
||||||
|
} catch (CancelledKeyException e) {
|
||||||
|
close(k);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanUpCancelledKeys()) {
|
||||||
|
break; // break the loop to avoid ConcurrentModificationException
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean cleanUpCancelledKeys() throws IOException {
|
||||||
|
if (cancelledKeys >= CLEANUP_INTERVAL) {
|
||||||
|
cancelledKeys = 0;
|
||||||
|
selector.selectNow();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean read(SelectionKey k) {
|
||||||
|
final SctpChannelImpl channel = (SctpChannelImpl) k.attachment();
|
||||||
|
|
||||||
|
final ReceiveBufferSizePredictor predictor =
|
||||||
|
channel.getConfig().getReceiveBufferSizePredictor();
|
||||||
|
final int predictedRecvBufSize = predictor.nextReceiveBufferSize();
|
||||||
|
|
||||||
|
boolean messageReceived = false;
|
||||||
|
boolean failure = true;
|
||||||
|
MessageInfo messageInfo = null;
|
||||||
|
|
||||||
|
ByteBuffer bb = recvBufferPool.acquire(predictedRecvBufSize);
|
||||||
|
try {
|
||||||
|
messageInfo = channel.channel.receive(bb, null, notificationHandler);
|
||||||
|
if (messageInfo != null) {
|
||||||
|
messageReceived = true;
|
||||||
|
if (messageInfo.isComplete()) {
|
||||||
|
failure = false;
|
||||||
|
} else {
|
||||||
|
logger.error("Received incomplete sctp packet, can not continue! Expected SCTP_EXPLICIT_COMPLETE message");
|
||||||
|
failure = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
messageReceived = false;
|
||||||
|
failure = false;
|
||||||
|
}
|
||||||
|
} catch (ClosedChannelException e) {
|
||||||
|
// Can happen, and does not need a user attention.
|
||||||
|
} catch (Throwable t) {
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messageReceived) {
|
||||||
|
bb.flip();
|
||||||
|
|
||||||
|
final ChannelBufferFactory bufferFactory =
|
||||||
|
channel.getConfig().getBufferFactory();
|
||||||
|
final int receivedBytes = bb.remaining();
|
||||||
|
final ChannelBuffer buffer = bufferFactory.getBuffer(receivedBytes);
|
||||||
|
buffer.setBytes(0, bb);
|
||||||
|
buffer.writerIndex(receivedBytes);
|
||||||
|
|
||||||
|
recvBufferPool.release(bb);
|
||||||
|
|
||||||
|
// Update the predictor.
|
||||||
|
predictor.previousReceiveBufferSize(receivedBytes);
|
||||||
|
|
||||||
|
// Fire the event.
|
||||||
|
fireMessageReceived(channel,
|
||||||
|
new SctpPayload(messageInfo.streamNumber(),
|
||||||
|
messageInfo.payloadProtocolID(),
|
||||||
|
buffer),
|
||||||
|
messageInfo.address());
|
||||||
|
} else {
|
||||||
|
recvBufferPool.release(bb);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.channel.isBlocking() && !messageReceived || failure) {
|
||||||
|
k.cancel(); // Some JDK implementations run into an infinite loop without this.
|
||||||
|
close(channel, succeededFuture(channel));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(SelectionKey k) {
|
||||||
|
SctpChannelImpl ch = (SctpChannelImpl) k.attachment();
|
||||||
|
close(ch, succeededFuture(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFromUserCode(final SctpChannelImpl channel) {
|
||||||
|
if (!channel.isConnected()) {
|
||||||
|
cleanUpWriteBuffer(channel);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scheduleWriteIfNecessary(channel)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// From here, we are sure Thread.currentThread() == workerThread.
|
||||||
|
|
||||||
|
if (channel.writeSuspended) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (channel.inWriteNowLoop) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
write0(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFromTaskLoop(final SctpChannelImpl ch) {
|
||||||
|
if (!ch.writeSuspended) {
|
||||||
|
write0(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void writeFromSelectorLoop(final SelectionKey k) {
|
||||||
|
SctpChannelImpl ch = (SctpChannelImpl) k.attachment();
|
||||||
|
ch.writeSuspended = false;
|
||||||
|
write0(ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean scheduleWriteIfNecessary(final SctpChannelImpl channel) {
|
||||||
|
final Thread currentThread = Thread.currentThread();
|
||||||
|
final Thread workerThread = thread;
|
||||||
|
if (currentThread != workerThread) {
|
||||||
|
if (channel.writeTaskInTaskQueue.compareAndSet(false, true)) {
|
||||||
|
boolean offered = writeTaskQueue.offer(channel.writeTask);
|
||||||
|
assert offered;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(channel instanceof SctpAcceptedChannel) ||
|
||||||
|
((SctpAcceptedChannel) channel).bossThread != currentThread) {
|
||||||
|
final Selector workerSelector = selector;
|
||||||
|
if (workerSelector != null) {
|
||||||
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
|
workerSelector.wakeup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// A write request can be made from an acceptor thread (boss)
|
||||||
|
// when a user attempted to write something in:
|
||||||
|
//
|
||||||
|
// * channelOpen()
|
||||||
|
// * channelBound()
|
||||||
|
// * channelConnected().
|
||||||
|
//
|
||||||
|
// In this case, there's no need to wake up the selector because
|
||||||
|
// the channel is not even registered yet at this moment.
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void write0(SctpChannelImpl channel) {
|
||||||
|
boolean open = true;
|
||||||
|
boolean addOpWrite = false;
|
||||||
|
boolean removeOpWrite = false;
|
||||||
|
|
||||||
|
long writtenBytes = 0;
|
||||||
|
|
||||||
|
final SctpSendBufferPool sendBufferPool = this.sendBufferPool;
|
||||||
|
final com.sun.nio.sctp.SctpChannel ch = channel.channel;
|
||||||
|
final Queue<MessageEvent> writeBuffer = channel.writeBuffer;
|
||||||
|
final int writeSpinCount = channel.getConfig().getWriteSpinCount();
|
||||||
|
synchronized (channel.writeLock) {
|
||||||
|
channel.inWriteNowLoop = true;
|
||||||
|
for (; ;) {
|
||||||
|
MessageEvent evt = channel.currentWriteEvent;
|
||||||
|
SendBuffer buf;
|
||||||
|
if (evt == null) {
|
||||||
|
if ((channel.currentWriteEvent = evt = writeBuffer.poll()) == null) {
|
||||||
|
removeOpWrite = true;
|
||||||
|
channel.writeSuspended = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
channel.currentWriteBuffer = buf = sendBufferPool.acquire(evt.getMessage());
|
||||||
|
} else {
|
||||||
|
buf = channel.currentWriteBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelFuture future = evt.getFuture();
|
||||||
|
try {
|
||||||
|
long localWrittenBytes = 0;
|
||||||
|
for (int i = writeSpinCount; i > 0; i--) {
|
||||||
|
localWrittenBytes = buf.transferTo(ch);
|
||||||
|
if (localWrittenBytes != 0) {
|
||||||
|
writtenBytes += localWrittenBytes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (buf.finished()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buf.finished()) {
|
||||||
|
// Successful write - proceed to the next message.
|
||||||
|
buf.release();
|
||||||
|
channel.currentWriteEvent = null;
|
||||||
|
channel.currentWriteBuffer = null;
|
||||||
|
evt = null;
|
||||||
|
buf = null;
|
||||||
|
future.setSuccess();
|
||||||
|
} else {
|
||||||
|
// Not written fully - perhaps the kernel buffer is full.
|
||||||
|
addOpWrite = true;
|
||||||
|
channel.writeSuspended = true;
|
||||||
|
|
||||||
|
if (localWrittenBytes > 0) {
|
||||||
|
// Notify progress listeners if necessary.
|
||||||
|
future.setProgress(
|
||||||
|
localWrittenBytes,
|
||||||
|
buf.writtenBytes(), buf.totalBytes());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (AsynchronousCloseException e) {
|
||||||
|
// Doesn't need a user attention - ignore.
|
||||||
|
} catch (Throwable t) {
|
||||||
|
buf.release();
|
||||||
|
channel.currentWriteEvent = null;
|
||||||
|
channel.currentWriteBuffer = null;
|
||||||
|
buf = null;
|
||||||
|
evt = null;
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
if (t instanceof IOException) {
|
||||||
|
open = false;
|
||||||
|
close(channel, succeededFuture(channel));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
channel.inWriteNowLoop = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (open) {
|
||||||
|
if (addOpWrite) {
|
||||||
|
setOpWrite(channel);
|
||||||
|
} else if (removeOpWrite) {
|
||||||
|
clearOpWrite(channel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fireWriteComplete(channel, writtenBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setOpWrite(SctpChannelImpl channel) {
|
||||||
|
Selector selector = this.selector;
|
||||||
|
SelectionKey key = channel.channel.keyFor(selector);
|
||||||
|
if (key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!key.isValid()) {
|
||||||
|
close(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interestOps can change at any time and at any thread.
|
||||||
|
// Acquire a lock to avoid possible race condition.
|
||||||
|
synchronized (channel.interestOpsLock) {
|
||||||
|
int interestOps = channel.getRawInterestOps();
|
||||||
|
if ((interestOps & SelectionKey.OP_WRITE) == 0) {
|
||||||
|
interestOps |= SelectionKey.OP_WRITE;
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearOpWrite(SctpChannelImpl channel) {
|
||||||
|
Selector selector = this.selector;
|
||||||
|
SelectionKey key = channel.channel.keyFor(selector);
|
||||||
|
if (key == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!key.isValid()) {
|
||||||
|
close(key);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// interestOps can change at any time and at any thread.
|
||||||
|
// Acquire a lock to avoid possible race condition.
|
||||||
|
synchronized (channel.interestOpsLock) {
|
||||||
|
int interestOps = channel.getRawInterestOps();
|
||||||
|
if ((interestOps & SelectionKey.OP_WRITE) != 0) {
|
||||||
|
interestOps &= ~SelectionKey.OP_WRITE;
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void close(SctpChannelImpl channel, ChannelFuture future) {
|
||||||
|
boolean connected = channel.isConnected();
|
||||||
|
boolean bound = channel.isBound();
|
||||||
|
try {
|
||||||
|
channel.channel.close();
|
||||||
|
cancelledKeys++;
|
||||||
|
|
||||||
|
if (channel.setClosed()) {
|
||||||
|
future.setSuccess();
|
||||||
|
if (connected) {
|
||||||
|
fireChannelDisconnected(channel);
|
||||||
|
}
|
||||||
|
if (bound) {
|
||||||
|
fireChannelUnbound(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanUpWriteBuffer(channel);
|
||||||
|
fireChannelClosed(channel);
|
||||||
|
} else {
|
||||||
|
future.setSuccess();
|
||||||
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanUpWriteBuffer(SctpChannelImpl channel) {
|
||||||
|
Exception cause = null;
|
||||||
|
boolean fireExceptionCaught = false;
|
||||||
|
|
||||||
|
// Clean up the stale messages in the write buffer.
|
||||||
|
synchronized (channel.writeLock) {
|
||||||
|
MessageEvent evt = channel.currentWriteEvent;
|
||||||
|
if (evt != null) {
|
||||||
|
// Create the exception only once to avoid the excessive overhead
|
||||||
|
// caused by fillStackTrace.
|
||||||
|
if (channel.isOpen()) {
|
||||||
|
cause = new NotYetConnectedException();
|
||||||
|
} else {
|
||||||
|
cause = new ClosedChannelException();
|
||||||
|
}
|
||||||
|
|
||||||
|
ChannelFuture future = evt.getFuture();
|
||||||
|
channel.currentWriteBuffer.release();
|
||||||
|
channel.currentWriteBuffer = null;
|
||||||
|
channel.currentWriteEvent = null;
|
||||||
|
evt = null;
|
||||||
|
future.setFailure(cause);
|
||||||
|
fireExceptionCaught = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Queue<MessageEvent> writeBuffer = channel.writeBuffer;
|
||||||
|
if (!writeBuffer.isEmpty()) {
|
||||||
|
// Create the exception only once to avoid the excessive overhead
|
||||||
|
// caused by fillStackTrace.
|
||||||
|
if (cause == null) {
|
||||||
|
if (channel.isOpen()) {
|
||||||
|
cause = new NotYetConnectedException();
|
||||||
|
} else {
|
||||||
|
cause = new ClosedChannelException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (; ;) {
|
||||||
|
evt = writeBuffer.poll();
|
||||||
|
if (evt == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
evt.getFuture().setFailure(cause);
|
||||||
|
fireExceptionCaught = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fireExceptionCaught) {
|
||||||
|
fireExceptionCaught(channel, cause);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setInterestOps(
|
||||||
|
SctpChannelImpl channel, ChannelFuture future, int interestOps) {
|
||||||
|
boolean changed = false;
|
||||||
|
try {
|
||||||
|
// interestOps can change at any time and at any thread.
|
||||||
|
// Acquire a lock to avoid possible race condition.
|
||||||
|
synchronized (channel.interestOpsLock) {
|
||||||
|
Selector selector = this.selector;
|
||||||
|
SelectionKey key = channel.channel.keyFor(selector);
|
||||||
|
|
||||||
|
if (key == null || selector == null) {
|
||||||
|
// Not registered to the worker yet.
|
||||||
|
// Set the rawInterestOps immediately; RegisterTask will pick it up.
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override OP_WRITE flag - a user cannot change this flag.
|
||||||
|
interestOps &= ~Channel.OP_WRITE;
|
||||||
|
interestOps |= channel.getRawInterestOps() & Channel.OP_WRITE;
|
||||||
|
|
||||||
|
switch (CONSTRAINT_LEVEL) {
|
||||||
|
case 0:
|
||||||
|
if (channel.getRawInterestOps() != interestOps) {
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
if (Thread.currentThread() != thread &&
|
||||||
|
wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
case 2:
|
||||||
|
if (channel.getRawInterestOps() != interestOps) {
|
||||||
|
if (Thread.currentThread() == thread) {
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
selectorGuard.readLock().lock();
|
||||||
|
try {
|
||||||
|
if (wakenUp.compareAndSet(false, true)) {
|
||||||
|
selector.wakeup();
|
||||||
|
}
|
||||||
|
key.interestOps(interestOps);
|
||||||
|
changed = true;
|
||||||
|
} finally {
|
||||||
|
selectorGuard.readLock().unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (changed) {
|
||||||
|
channel.setRawInterestOpsNow(interestOps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
future.setSuccess();
|
||||||
|
if (changed) {
|
||||||
|
fireChannelInterestChanged(channel);
|
||||||
|
}
|
||||||
|
} catch (CancelledKeyException e) {
|
||||||
|
// setInterestOps() was called on a closed channel.
|
||||||
|
ClosedChannelException cce = new ClosedChannelException();
|
||||||
|
future.setFailure(cce);
|
||||||
|
fireExceptionCaught(channel, cce);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
future.setFailure(t);
|
||||||
|
fireExceptionCaught(channel, t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final class RegisterTask implements Runnable {
|
||||||
|
private final SctpChannelImpl channel;
|
||||||
|
private final ChannelFuture future;
|
||||||
|
private final boolean server;
|
||||||
|
|
||||||
|
RegisterTask(
|
||||||
|
SctpChannelImpl channel, ChannelFuture future, boolean server) {
|
||||||
|
|
||||||
|
this.channel = channel;
|
||||||
|
this.future = future;
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
SocketAddress localAddress = channel.getLocalAddress();
|
||||||
|
SocketAddress remoteAddress = channel.getRemoteAddress();
|
||||||
|
if (localAddress == null || remoteAddress == null) {
|
||||||
|
if (future != null) {
|
||||||
|
future.setFailure(new ClosedChannelException());
|
||||||
|
}
|
||||||
|
close(channel, succeededFuture(channel));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (server) {
|
||||||
|
channel.channel.configureBlocking(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
synchronized (channel.interestOpsLock) {
|
||||||
|
channel.channel.register(
|
||||||
|
selector, channel.getRawInterestOps(), channel);
|
||||||
|
}
|
||||||
|
if (future != null) {
|
||||||
|
channel.setConnected();
|
||||||
|
future.setSuccess();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (future != null) {
|
||||||
|
future.setFailure(e);
|
||||||
|
}
|
||||||
|
close(channel, succeededFuture(channel));
|
||||||
|
if (!(e instanceof ClosedChannelException)) {
|
||||||
|
throw new ChannelException(
|
||||||
|
"Failed to register a socket to the selector.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!server) {
|
||||||
|
if (!((SctpClientChannel) channel).boundManually) {
|
||||||
|
fireChannelBound(channel, localAddress);
|
||||||
|
}
|
||||||
|
fireChannelConnected(channel, remoteAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.logging.InternalLogger;
|
||||||
|
import org.jboss.netty.logging.InternalLoggerFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.channels.CancelledKeyException;
|
||||||
|
import java.nio.channels.Selector;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
final class SelectorUtil {
|
||||||
|
private static final InternalLogger logger =
|
||||||
|
InternalLoggerFactory.getInstance(SelectorUtil.class);
|
||||||
|
|
||||||
|
static final int DEFAULT_IO_THREADS = Runtime.getRuntime().availableProcessors() * 2;
|
||||||
|
|
||||||
|
static void select(Selector selector) throws IOException {
|
||||||
|
try {
|
||||||
|
selector.select(10);// does small timeout give more throughput + less CPU usage?
|
||||||
|
} catch (CancelledKeyException e) {
|
||||||
|
// Harmless exception - log anyway
|
||||||
|
logger.debug(
|
||||||
|
CancelledKeyException.class.getSimpleName() +
|
||||||
|
" raised by a Selector - JDK bug?", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <a href="http://en.wikipedia.org/wiki/New_I/O">NIO</a>-based socket channel
|
||||||
|
* API implementation - recommended for a large number of connections (>= 1000).
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.channel.socket.sctp;
|
74
src/main/java/org/jboss/netty/example/sctp/SctpClient.java
Normal file
74
src/main/java/org/jboss/netty/example/sctp/SctpClient.java
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.example.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.bootstrap.ClientBootstrap;
|
||||||
|
import org.jboss.netty.channel.ChannelFuture;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
import org.jboss.netty.channel.socket.sctp.SctpClientSocketChannelFactory;
|
||||||
|
import org.jboss.netty.handler.execution.ExecutionHandler;
|
||||||
|
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple SCTP Echo Client
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class SctpClient {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
|
||||||
|
// Configure the client.
|
||||||
|
ClientBootstrap bootstrap = new ClientBootstrap(
|
||||||
|
new SctpClientSocketChannelFactory(
|
||||||
|
Executors.newCachedThreadPool(),
|
||||||
|
Executors.newCachedThreadPool()));
|
||||||
|
|
||||||
|
final ExecutionHandler executionHandler = new ExecutionHandler(
|
||||||
|
new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));
|
||||||
|
|
||||||
|
// Set up the pipeline factory.
|
||||||
|
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline getPipeline() throws Exception {
|
||||||
|
return Channels.pipeline(executionHandler, new SctpClientHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
bootstrap.setOption("sendBufferSize", 1048576);
|
||||||
|
bootstrap.setOption("receiveBufferSize", 1048576);
|
||||||
|
|
||||||
|
bootstrap.setOption("sctpNoDelay", true);
|
||||||
|
|
||||||
|
// Start the connection attempt.
|
||||||
|
ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 2955), new InetSocketAddress("localhost", 2956));
|
||||||
|
|
||||||
|
// Wait until the connection is closed or the connection attempt fails.
|
||||||
|
future.getChannel().getCloseFuture().awaitUninterruptibly();
|
||||||
|
|
||||||
|
// Please check SctpClientHandler to see, how echo message is sent & received
|
||||||
|
|
||||||
|
// Shut down thread pools to exit.
|
||||||
|
bootstrap.releaseExternalResources();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.example.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.buffer.ChannelBuffers;
|
||||||
|
import org.jboss.netty.channel.*;
|
||||||
|
import org.jboss.netty.channel.socket.sctp.SctpPayload;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler implementation for the echo client. It initiates the message
|
||||||
|
* and upon receiving echo back to the server
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class SctpClientHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
private static final Logger logger = Logger.getLogger(SctpClientHandler.class.getName());
|
||||||
|
|
||||||
|
private final AtomicLong counter = new AtomicLong(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a client-side handler.
|
||||||
|
*/
|
||||||
|
public SctpClientHandler() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After connection is initialized, start the echo from client
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent stateEvent) {
|
||||||
|
stateEvent.getChannel().write(new SctpPayload(0, 0, ChannelBuffers.wrappedBuffer("SCTP ECHO".getBytes())));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) {
|
||||||
|
// Send back the received message to the remote peer.
|
||||||
|
logger.log(Level.INFO, "Received " + counter.incrementAndGet() + "th message from server, sending it back.");
|
||||||
|
messageEvent.getChannel().write(messageEvent.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent exceptionEvent) {
|
||||||
|
// Close the connection when an exception is raised.
|
||||||
|
logger.log(Level.WARNING, "Unexpected exception from downstream.", exceptionEvent.getCause());
|
||||||
|
exceptionEvent.getChannel().close();
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/org/jboss/netty/example/sctp/SctpServer.java
Normal file
63
src/main/java/org/jboss/netty/example/sctp/SctpServer.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.example.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.bootstrap.ServerBootstrap;
|
||||||
|
import org.jboss.netty.channel.ChannelPipeline;
|
||||||
|
import org.jboss.netty.channel.ChannelPipelineFactory;
|
||||||
|
import org.jboss.netty.channel.Channels;
|
||||||
|
import org.jboss.netty.channel.socket.sctp.SctpServerSocketChannelFactory;
|
||||||
|
import org.jboss.netty.handler.execution.ExecutionHandler;
|
||||||
|
import org.jboss.netty.handler.execution.OrderedMemoryAwareThreadPoolExecutor;
|
||||||
|
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echoes back any received data from a client.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class SctpServer {
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
// Configure the server.
|
||||||
|
ServerBootstrap bootstrap = new ServerBootstrap(
|
||||||
|
new SctpServerSocketChannelFactory(
|
||||||
|
Executors.newCachedThreadPool(),
|
||||||
|
Executors.newCachedThreadPool()));
|
||||||
|
|
||||||
|
final ExecutionHandler executionHandler = new ExecutionHandler(
|
||||||
|
new OrderedMemoryAwareThreadPoolExecutor(16, 1048576, 1048576));
|
||||||
|
|
||||||
|
// Set up the pipeline factory.
|
||||||
|
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
|
||||||
|
@Override
|
||||||
|
public ChannelPipeline getPipeline() throws Exception {
|
||||||
|
return Channels.pipeline(executionHandler, new SctpServerHandler());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
bootstrap.setOption("sendBufferSize", 1048576);
|
||||||
|
bootstrap.setOption("receiveBufferSize", 1048576);
|
||||||
|
|
||||||
|
bootstrap.setOption("child.sctpNoDelay", true);
|
||||||
|
|
||||||
|
// Bind and start to accept incoming connections.
|
||||||
|
bootstrap.bind(new InetSocketAddress("localhost", 2955));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2011 The Netty Project
|
||||||
|
*
|
||||||
|
* The Netty Project licenses this file to you under the Apache License,
|
||||||
|
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||||
|
* with the License. You may obtain a copy of the License at:
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
* License for the specific language governing permissions and limitations
|
||||||
|
* under the License.
|
||||||
|
*/
|
||||||
|
package org.jboss.netty.example.sctp;
|
||||||
|
|
||||||
|
import org.jboss.netty.channel.ChannelHandlerContext;
|
||||||
|
import org.jboss.netty.channel.ExceptionEvent;
|
||||||
|
import org.jboss.netty.channel.MessageEvent;
|
||||||
|
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler implementation for the echo server.
|
||||||
|
*
|
||||||
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
|
* @author <a href="http://github.com/jestan">Jestan Nirojan</a>
|
||||||
|
*/
|
||||||
|
public class SctpServerHandler extends SimpleChannelUpstreamHandler {
|
||||||
|
private static final Logger logger = Logger.getLogger(SctpServerHandler.class.getName());
|
||||||
|
|
||||||
|
private final AtomicLong counter = new AtomicLong(0);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) {
|
||||||
|
// Send back the received message to the remote peer.
|
||||||
|
logger.log(Level.INFO, "Received " + counter.incrementAndGet() + "th message from client, sending it back.");
|
||||||
|
messageEvent.getChannel().write(messageEvent.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent event) {
|
||||||
|
// Close the connection when an exception is raised.
|
||||||
|
logger.log(Level.WARNING, "Unexpected exception from downstream.", event.getCause());
|
||||||
|
event.getChannel().close();
|
||||||
|
}
|
||||||
|
}
|
@ -24,15 +24,15 @@ import org.jboss.netty.util.EstimatableObjectWrapper;
|
|||||||
/**
|
/**
|
||||||
* a {@link Runnable} which sends the specified {@link ChannelEvent} upstream.
|
* a {@link Runnable} which sends the specified {@link ChannelEvent} upstream.
|
||||||
* Most users will not see this type at all because it is used by
|
* Most users will not see this type at all because it is used by
|
||||||
* {@link Executor} implementors only
|
* {@link Executor} implementers only
|
||||||
*
|
*
|
||||||
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
* @author <a href="http://www.jboss.org/netty/">The Netty Project</a>
|
||||||
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
* @author <a href="http://gleamynode.net/">Trustin Lee</a>
|
||||||
*/
|
*/
|
||||||
public class ChannelEventRunnable implements Runnable, EstimatableObjectWrapper {
|
public class ChannelEventRunnable implements Runnable, EstimatableObjectWrapper {
|
||||||
|
|
||||||
private final ChannelHandlerContext ctx;
|
protected final ChannelHandlerContext ctx;
|
||||||
private final ChannelEvent e;
|
protected final ChannelEvent e;
|
||||||
int estimatedSize;
|
int estimatedSize;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
Reference in New Issue
Block a user