initial commit
Some checks failed
CI / build (push) Failing after 2m11s

This commit is contained in:
2024-12-28 09:02:28 +08:00
commit bf87152b0a
374 changed files with 49436 additions and 0 deletions

12
.gitattributes vendored Normal file
View File

@@ -0,0 +1,12 @@
#
# https://help.github.com/articles/dealing-with-line-endings/
#
# Linux start script should use lf
/gradlew text eol=lf
# These are Windows script files and should use crlf
*.bat text eol=crlf
# Binary files should be left untouched
*.jar binary

View File

@@ -0,0 +1,17 @@
name: CI
on:
push:
tags:
- '*'
jobs:
build:
runs-on: hostinger
steps:
- name: Checkout sources
uses: actions/checkout@v4
- name: Setup Gradle
uses: gradle/actions/setup-gradle@v3
- name: Execute Gradle build
env:
PUBLISHER_TOKEN: ${{ secrets.PUBLISHER_TOKEN }}
run: ./gradlew build publish

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
# Ignore Gradle project-specific cache directory
.gradle
# Ignore Gradle build output directory
build

105
build.gradle Normal file
View File

@@ -0,0 +1,105 @@
plugins {
id 'java-library'
alias catalog.plugins.sambal
id 'maven-publish'
}
allprojects {
group = 'net.woggioni'
version = project.currentTag ?: "${getProperty('xmemcached.version')}-SNAPSHOT"
pluginManager.withPlugin('java-library') {
repositories {
mavenCentral()
}
java {
withSourcesJar()
modularity.inferModulePath = true
toolchain {
languageVersion = JavaLanguageVersion.of(21)
}
}
test {
useJUnitPlatform()
systemProperty('junit.jupiter.execution.parallel.enabled', 'true')
systemProperty('junit.jupiter.execution.parallel.mode.classes.default', 'concurrent')
systemProperty('junit.jupiter.execution.parallel.mode.default = concurrent', 'concurrent')
// systemProperty('test.memcached.host', 'localhost')
// systemProperty('test.memcached.port', '1211')
// systemProperty('test.memcached.servers', 'localhost:1211')
filter {
excludeTestsMatching "*IT"
}
}
}
pluginManager.withPlugin(catalog.plugins.lombok.get().pluginId) {
lombok {
version = catalog.versions.lombok.get()
}
}
pluginManager.withPlugin('maven-publish') {
pluginManager.withPlugin('java-library') {
java {
// withJavadocJar()
withSourcesJar()
}
}
publishing {
repositories {
maven {
name = "Gitea"
url = uri(getProperty('gitea.maven.url'))
credentials(HttpHeaderCredentials) {
name = "Authorization"
value = "token ${System.getenv()["PUBLISHER_TOKEN"]}"
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
publications {
maven(MavenPublication) {
from(components["java"])
}
}
}
}
}
dependencies {
implementation catalog.slf4j.api
// https://mvnrepository.com/artifact/org.springframework/spring-core
compileOnly group: 'org.springframework', name: 'spring-context', version: getProperty('spring.version')
// https://mvnrepository.com/artifact/org.springframework/spring-core
compileOnly group: 'org.springframework', name: 'spring-beans', version: getProperty('spring.version')
// https://mvnrepository.com/artifact/org.easymock/easymock
testImplementation group: 'org.easymock', name: 'easymock', version: getProperty('easymock.version')
testImplementation catalog.junit.jupiter.api
testRuntimeOnly catalog.junit.jupiter.engine
// https://mvnrepository.com/artifact/org.springframework/spring-core
testImplementation group: 'org.springframework', name: 'spring-context', version: getProperty('spring.version')
// https://mvnrepository.com/artifact/org.springframework/spring-core
testImplementation group: 'org.springframework', name: 'spring-beans', version: getProperty('spring.version')
// testImplementation platform(group: "org.testcontainers", name: "testcontainers-bom", version: "1.20.4")
// testImplementation group: "org.testcontainers", name: 'testcontainers'
// testImplementation group: 'org.testcontainers', name: 'junit-jupiter'
}

12
gradle.properties Normal file
View File

@@ -0,0 +1,12 @@
org.gradle.configuration-cache=true
org.gradle.parallel=true
org.gradle.caching=true
xmemcached.version = 2.4.9
lys.version = 2024.12.28
gitea.maven.url = https://gitea.woggioni.net/api/packages/woggioni/maven
spring.version=6.2.1
easymock.version=5.5.0

View File

@@ -0,0 +1,2 @@
# This file was generated by the Gradle 'init' task.
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

251
gradlew vendored Executable file
View File

@@ -0,0 +1,251 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://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.
#
# SPDX-License-Identifier: Apache-2.0
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

94
gradlew.bat vendored Normal file
View File

@@ -0,0 +1,94 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

32
settings.gradle Normal file
View File

@@ -0,0 +1,32 @@
pluginManagement {
repositories {
// mavenLocal() {
// content {
// includeGroup 'net.woggioni.gradle.sambal'
// includeModule 'net.woggioni', 'net.woggioni.gradle.sambal'
// }
// }
maven {
url = getProperty('gitea.maven.url')
}
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositories {
maven {
url = getProperty('gitea.maven.url')
content {
includeGroup 'com.lys'
}
}
}
versionCatalogs {
catalog {
from group: 'com.lys', name: 'lys-catalog', version: getProperty('lys.version')
}
}
}
rootProject.name = 'xmemcached'

View File

@@ -0,0 +1,47 @@
<assembly>
<id>bin-with-dependencies</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>User_Guide_zh</directory>
<outputDirectory>/User_Guide_zh</outputDirectory>
</fileSet>
<fileSet>
<directory>User_Guide_en</directory>
<outputDirectory>/User_Guide_en</outputDirectory>
</fileSet>
<fileSet>
<directory>${project.build.directory}
</directory>
<includes>
<include>*.jar</include>
</includes>
<outputDirectory>/</outputDirectory>
</fileSet>
</fileSets>
<dependencySets>
<dependencySet>
<outputDirectory>/lib</outputDirectory>
<scope>runtime</scope>
<excludes>
<exclude>xmemcached*</exclude>
</excludes>
</dependencySet>
</dependencySets>
<files>
<file>
<source>README.md</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>NOTICE.txt</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>LICENSE.txt</source>
<outputDirectory>/</outputDirectory>
</file>
</files>
</assembly>

30
src/assemble/src.xml Normal file
View File

@@ -0,0 +1,30 @@
<assembly>
<id>src</id>
<formats>
<format>tar.gz</format>
</formats>
<fileSets>
<fileSet>
<directory>${project.basedir}/src</directory>
<outputDirectory>/src</outputDirectory>
</fileSet>
</fileSets>
<files>
<file>
<source>README.md</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>pom.xml</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>NOTICE.txt</source>
<outputDirectory>/</outputDirectory>
</file>
<file>
<source>LICENSE.txt</source>
<outputDirectory>/</outputDirectory>
</file>
</files>
</assembly>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
/**
* A {@link RuntimeException} which is thrown when the data the {@link IoBuffer} contains is
* corrupt.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 671827 $, $Date: 2008-06-26 10:49:48 +0200 (Thu, 26 Jun 2008) $
*
*/
public class BufferDataException extends RuntimeException {
private static final long serialVersionUID = -4138189188602563502L;
public BufferDataException() {
super();
}
public BufferDataException(String message) {
super(message);
}
public BufferDataException(String message, Throwable cause) {
super(message, cause);
}
public BufferDataException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,271 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;
import com.google.code.yanf4j.util.CircularQueue;
/**
* An {@link IoBufferAllocator} that caches the buffers which are likely to be reused during
* auto-expansion of the buffers.
* <p>
* In {@link SimpleBufferAllocator}, the underlying {@link ByteBuffer} of the {@link IoBuffer} is
* reallocated on its capacity change, which means the newly allocated bigger {@link ByteBuffer}
* replaces the old small {@link ByteBuffer} . Consequently, the old {@link ByteBuffer} is marked
* for garbage collection.
* <p>
* It's not a problem in most cases as long as the capacity change doesn't happen frequently.
* However, once it happens too often, it burdens the VM and the cost of filling the newly allocated
* {@link ByteBuffer} with {@code NUL} surpass the cost of accessing the cache. In 2 dual-core
* Opteron Italy 270 processors, {@link CachedBufferAllocator} outperformed
* {@link SimpleBufferAllocator} in the following situation:
* <ul>
* <li>when a 32 bytes buffer is expanded 4 or more times,</li>
* <li>when a 64 bytes buffer is expanded 4 or more times,</li>
* <li>when a 128 bytes buffer is expanded 2 or more times,</li>
* <li>and when a 256 bytes or bigger buffer is expanded 1 or more times.</li>
* </ul>
* Please note the observation above is subject to change in a different environment.
* <p>
* {@link CachedBufferAllocator} uses {@link ThreadLocal} to store the cached buffer, allocates
* buffers whose capacity is power of 2 only and provides performance advantage if
* {@link IoBuffer#free()} is called properly.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 671827 $, $Date: 2008-06-26 10:49:48 +0200 (Thu, 26 Jun 2008) $
*/
public class CachedBufferAllocator implements IoBufferAllocator {
private static final int DEFAULT_MAX_POOL_SIZE = 8;
private static final int DEFAULT_MAX_CACHED_BUFFER_SIZE = 1 << 18; // 256KB
private final int maxPoolSize;
private final int maxCachedBufferSize;
private final ThreadLocal<Map<Integer, Queue<CachedBuffer>>> heapBuffers;
private final ThreadLocal<Map<Integer, Queue<CachedBuffer>>> directBuffers;
/**
* Creates a new instance with the default parameters ({@literal #DEFAULT_MAX_POOL_SIZE} and
* {@literal #DEFAULT_MAX_CACHED_BUFFER_SIZE}).
*/
public CachedBufferAllocator() {
this(DEFAULT_MAX_POOL_SIZE, DEFAULT_MAX_CACHED_BUFFER_SIZE);
}
/**
* Creates a new instance.
*
* @param maxPoolSize the maximum number of buffers with the same capacity per thread. <code>0</code>
* disables this limitation.
* @param maxCachedBufferSize the maximum capacity of a cached buffer. A buffer whose capacity is
* bigger than this value is not pooled. <code>0</code> disables this limitation.
*/
public CachedBufferAllocator(int maxPoolSize, int maxCachedBufferSize) {
if (maxPoolSize < 0) {
throw new IllegalArgumentException("maxPoolSize: " + maxPoolSize);
}
if (maxCachedBufferSize < 0) {
throw new IllegalArgumentException("maxCachedBufferSize: " + maxCachedBufferSize);
}
this.maxPoolSize = maxPoolSize;
this.maxCachedBufferSize = maxCachedBufferSize;
this.heapBuffers = new ThreadLocal<Map<Integer, Queue<CachedBuffer>>>() {
@Override
protected Map<Integer, Queue<CachedBuffer>> initialValue() {
return newPoolMap();
}
};
this.directBuffers = new ThreadLocal<Map<Integer, Queue<CachedBuffer>>>() {
@Override
protected Map<Integer, Queue<CachedBuffer>> initialValue() {
return newPoolMap();
}
};
}
/**
* Returns the maximum number of buffers with the same capacity per thread. <code>0</code> means 'no
* limitation'.
*/
public int getMaxPoolSize() {
return this.maxPoolSize;
}
/**
* Returns the maximum capacity of a cached buffer. A buffer whose capacity is bigger than this
* value is not pooled. <code>0</code> means 'no limitation'.
*/
public int getMaxCachedBufferSize() {
return this.maxCachedBufferSize;
}
private Map<Integer, Queue<CachedBuffer>> newPoolMap() {
Map<Integer, Queue<CachedBuffer>> poolMap = new HashMap<Integer, Queue<CachedBuffer>>();
int poolSize = this.maxPoolSize == 0 ? DEFAULT_MAX_POOL_SIZE : this.maxPoolSize;
for (int i = 0; i < 31; i++) {
poolMap.put(1 << i, new CircularQueue<CachedBuffer>(poolSize));
}
poolMap.put(0, new CircularQueue<CachedBuffer>(poolSize));
poolMap.put(Integer.MAX_VALUE, new CircularQueue<CachedBuffer>(poolSize));
return poolMap;
}
public IoBuffer allocate(int requestedCapacity, boolean direct) {
int actualCapacity = IoBuffer.normalizeCapacity(requestedCapacity);
IoBuffer buf;
if (this.maxCachedBufferSize != 0 && actualCapacity > this.maxCachedBufferSize) {
if (direct) {
buf = wrap(ByteBuffer.allocateDirect(actualCapacity));
} else {
buf = wrap(ByteBuffer.allocate(actualCapacity));
}
} else {
Queue<CachedBuffer> pool;
if (direct) {
pool = this.directBuffers.get().get(actualCapacity);
} else {
pool = this.heapBuffers.get().get(actualCapacity);
}
// Recycle if possible.
buf = pool.poll();
if (buf != null) {
buf.clear();
buf.setAutoExpand(false);
buf.order(ByteOrder.BIG_ENDIAN);
} else {
if (direct) {
buf = wrap(ByteBuffer.allocateDirect(actualCapacity));
} else {
buf = wrap(ByteBuffer.allocate(actualCapacity));
}
}
}
buf.limit(requestedCapacity);
return buf;
}
public ByteBuffer allocateNioBuffer(int capacity, boolean direct) {
return allocate(capacity, direct).buf();
}
public IoBuffer wrap(ByteBuffer nioBuffer) {
return new CachedBuffer(nioBuffer);
}
public void dispose() {}
private class CachedBuffer extends AbstractIoBuffer {
private final Thread ownerThread;
private ByteBuffer buf;
protected CachedBuffer(ByteBuffer buf) {
super(CachedBufferAllocator.this, buf.capacity());
this.ownerThread = Thread.currentThread();
this.buf = buf;
buf.order(ByteOrder.BIG_ENDIAN);
}
protected CachedBuffer(CachedBuffer parent, ByteBuffer buf) {
super(parent);
this.ownerThread = Thread.currentThread();
this.buf = buf;
}
@Override
public ByteBuffer buf() {
if (this.buf == null) {
throw new IllegalStateException("Buffer has been freed already.");
}
return this.buf;
}
@Override
protected void buf(ByteBuffer buf) {
ByteBuffer oldBuf = this.buf;
this.buf = buf;
free(oldBuf);
}
@Override
protected IoBuffer duplicate0() {
return new CachedBuffer(this, buf().duplicate());
}
@Override
protected IoBuffer slice0() {
return new CachedBuffer(this, buf().slice());
}
@Override
protected IoBuffer asReadOnlyBuffer0() {
return new CachedBuffer(this, buf().asReadOnlyBuffer());
}
@Override
public byte[] array() {
return buf().array();
}
@Override
public int arrayOffset() {
return buf().arrayOffset();
}
@Override
public boolean hasArray() {
return buf().hasArray();
}
@Override
public void free() {
free(this.buf);
this.buf = null;
}
private void free(ByteBuffer oldBuf) {
if (oldBuf == null || oldBuf.capacity() > CachedBufferAllocator.this.maxCachedBufferSize
|| oldBuf.isReadOnly() || isDerived() || Thread.currentThread() != this.ownerThread) {
return;
}
// Add to the cache.
Queue<CachedBuffer> pool;
if (oldBuf.isDirect()) {
pool = CachedBufferAllocator.this.directBuffers.get().get(oldBuf.capacity());
} else {
pool = CachedBufferAllocator.this.heapBuffers.get().get(oldBuf.capacity());
}
if (pool == null) {
return;
}
// Restrict the size of the pool to prevent OOM.
if (CachedBufferAllocator.this.maxPoolSize == 0
|| pool.size() < CachedBufferAllocator.this.maxPoolSize) {
pool.offer(new CachedBuffer(oldBuf));
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
import java.nio.ByteBuffer;
/**
* Allocates {@link IoBuffer}s and manages them. Please implement this interface if you need more
* advanced memory management scheme.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 671827 $, $Date: 2008-06-26 10:49:48 +0200 (Thu, 26 Jun 2008) $
*/
public interface IoBufferAllocator {
/**
* Returns the buffer which is capable of the specified size.
*
* @param capacity the capacity of the buffer
* @param direct <code>true</code> to get a direct buffer, <code>false</code> to get a heap buffer.
*/
IoBuffer allocate(int capacity, boolean direct);
/**
* Returns the NIO buffer which is capable of the specified size.
*
* @param capacity the capacity of the buffer
* @param direct <code>true</code> to get a direct buffer, <code>false</code> to get a heap buffer.
*/
ByteBuffer allocateNioBuffer(int capacity, boolean direct);
/**
* Wraps the specified NIO {@link ByteBuffer} into MINA buffer.
*/
IoBuffer wrap(ByteBuffer nioBuffer);
/**
* Dispose of this allocator.
*/
void dispose();
}

View File

@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
/**
* Provides utility methods to dump an {@link IoBuffer} into a hex formatted string.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 686598 $, $Date: 2008-08-17 12:58:23 +0200 (Sun, 17 Aug 2008) $
*/
class IoBufferHexDumper {
/**
* The high digits lookup table.
*/
private static final byte[] highDigits;
/**
* The low digits lookup table.
*/
private static final byte[] lowDigits;
/**
* Initialize lookup tables.
*/
static {
final byte[] digits =
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
int i;
byte[] high = new byte[256];
byte[] low = new byte[256];
for (i = 0; i < 256; i++) {
high[i] = digits[i >>> 4];
low[i] = digits[i & 0x0F];
}
highDigits = high;
lowDigits = low;
}
/**
* Dumps an {@link IoBuffer} to a hex formatted string.
*
* @param in the buffer to dump
* @param lengthLimit the limit at which hex dumping will stop
* @return a hex formatted string representation of the <i>in</i> {@link Iobuffer}.
*/
public static String getHexdump(IoBuffer in, int lengthLimit) {
if (lengthLimit == 0) {
throw new IllegalArgumentException("lengthLimit: " + lengthLimit + " (expected: 1+)");
}
boolean truncate = in.remaining() > lengthLimit;
int size;
if (truncate) {
size = lengthLimit;
} else {
size = in.remaining();
}
if (size == 0) {
return "empty";
}
StringBuilder out = new StringBuilder(in.remaining() * 3 - 1);
int mark = in.position();
// fill the first
int byteValue = in.get() & 0xFF;
out.append((char) highDigits[byteValue]);
out.append((char) lowDigits[byteValue]);
size--;
// and the others, too
for (; size > 0; size--) {
out.append(' ');
byteValue = in.get() & 0xFF;
out.append((char) highDigits[byteValue]);
out.append((char) lowDigits[byteValue]);
}
in.position(mark);
if (truncate) {
out.append("...");
}
return out.toString();
}
}

View File

@@ -0,0 +1,891 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
import java.io.FilterOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.CharBuffer;
import java.nio.DoubleBuffer;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.nio.LongBuffer;
import java.nio.ShortBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;
import java.util.EnumSet;
import java.util.Set;
/**
* A {@link IoBuffer} that wraps a buffer and proxies any operations to it.
* <p>
* You can think this class like a {@link FilterOutputStream}. All operations are proxied by default
* so that you can extend this class and override existing operations selectively. You can introduce
* new operations, too.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 671827 $, $Date: 2008-06-26 10:49:48 +0200 (Thu, 26 Jun 2008) $
*/
public class IoBufferWrapper extends IoBuffer {
/**
* The buffer proxied by this proxy.
*/
private final IoBuffer buf;
/**
* Create a new instance.
*
* @param buf the buffer to be proxied
*/
protected IoBufferWrapper(IoBuffer buf) {
if (buf == null) {
throw new NullPointerException("buf");
}
this.buf = buf;
}
/**
* Returns the parent buffer that this buffer wrapped.
*/
public IoBuffer getParentBuffer() {
return buf;
}
@Override
public boolean isDirect() {
return buf.isDirect();
}
@Override
public ByteBuffer buf() {
return buf.buf();
}
@Override
public int capacity() {
return buf.capacity();
}
@Override
public int position() {
return buf.position();
}
@Override
public IoBuffer position(int newPosition) {
buf.position(newPosition);
return this;
}
@Override
public int limit() {
return buf.limit();
}
@Override
public IoBuffer limit(int newLimit) {
buf.limit(newLimit);
return this;
}
@Override
public IoBuffer mark() {
buf.mark();
return this;
}
@Override
public IoBuffer reset() {
buf.reset();
return this;
}
@Override
public IoBuffer clear() {
buf.clear();
return this;
}
@Override
public IoBuffer sweep() {
buf.sweep();
return this;
}
@Override
public IoBuffer sweep(byte value) {
buf.sweep(value);
return this;
}
@Override
public IoBuffer flip() {
buf.flip();
return this;
}
@Override
public IoBuffer rewind() {
buf.rewind();
return this;
}
@Override
public int remaining() {
return buf.remaining();
}
@Override
public boolean hasRemaining() {
return buf.hasRemaining();
}
@Override
public byte get() {
return buf.get();
}
@Override
public short getUnsigned() {
return buf.getUnsigned();
}
@Override
public IoBuffer put(byte b) {
buf.put(b);
return this;
}
@Override
public byte get(int index) {
return buf.get(index);
}
@Override
public short getUnsigned(int index) {
return buf.getUnsigned(index);
}
@Override
public IoBuffer put(int index, byte b) {
buf.put(index, b);
return this;
}
@Override
public IoBuffer get(byte[] dst, int offset, int length) {
buf.get(dst, offset, length);
return this;
}
@Override
public IoBuffer getSlice(int index, int length) {
return buf.getSlice(index, length);
}
@Override
public IoBuffer getSlice(int length) {
return buf.getSlice(length);
}
@Override
public IoBuffer get(byte[] dst) {
buf.get(dst);
return this;
}
@Override
public IoBuffer put(IoBuffer src) {
buf.put(src);
return this;
}
@Override
public IoBuffer put(ByteBuffer src) {
buf.put(src);
return this;
}
@Override
public IoBuffer put(byte[] src, int offset, int length) {
buf.put(src, offset, length);
return this;
}
@Override
public IoBuffer put(byte[] src) {
buf.put(src);
return this;
}
@Override
public IoBuffer compact() {
buf.compact();
return this;
}
@Override
public String toString() {
return buf.toString();
}
@Override
public int hashCode() {
return buf.hashCode();
}
@Override
public boolean equals(Object ob) {
return buf.equals(ob);
}
public int compareTo(IoBuffer that) {
return buf.compareTo(that);
}
@Override
public ByteOrder order() {
return buf.order();
}
@Override
public IoBuffer order(ByteOrder bo) {
buf.order(bo);
return this;
}
@Override
public char getChar() {
return buf.getChar();
}
@Override
public IoBuffer putChar(char value) {
buf.putChar(value);
return this;
}
@Override
public char getChar(int index) {
return buf.getChar(index);
}
@Override
public IoBuffer putChar(int index, char value) {
buf.putChar(index, value);
return this;
}
@Override
public CharBuffer asCharBuffer() {
return buf.asCharBuffer();
}
@Override
public short getShort() {
return buf.getShort();
}
@Override
public int getUnsignedShort() {
return buf.getUnsignedShort();
}
@Override
public IoBuffer putShort(short value) {
buf.putShort(value);
return this;
}
@Override
public short getShort(int index) {
return buf.getShort(index);
}
@Override
public int getUnsignedShort(int index) {
return buf.getUnsignedShort(index);
}
@Override
public IoBuffer putShort(int index, short value) {
buf.putShort(index, value);
return this;
}
@Override
public ShortBuffer asShortBuffer() {
return buf.asShortBuffer();
}
@Override
public int getInt() {
return buf.getInt();
}
@Override
public long getUnsignedInt() {
return buf.getUnsignedInt();
}
@Override
public IoBuffer putInt(int value) {
buf.putInt(value);
return this;
}
@Override
public int getInt(int index) {
return buf.getInt(index);
}
@Override
public long getUnsignedInt(int index) {
return buf.getUnsignedInt(index);
}
@Override
public IoBuffer putInt(int index, int value) {
buf.putInt(index, value);
return this;
}
@Override
public IntBuffer asIntBuffer() {
return buf.asIntBuffer();
}
@Override
public long getLong() {
return buf.getLong();
}
@Override
public IoBuffer putLong(long value) {
buf.putLong(value);
return this;
}
@Override
public long getLong(int index) {
return buf.getLong(index);
}
@Override
public IoBuffer putLong(int index, long value) {
buf.putLong(index, value);
return this;
}
@Override
public LongBuffer asLongBuffer() {
return buf.asLongBuffer();
}
@Override
public float getFloat() {
return buf.getFloat();
}
@Override
public IoBuffer putFloat(float value) {
buf.putFloat(value);
return this;
}
@Override
public float getFloat(int index) {
return buf.getFloat(index);
}
@Override
public IoBuffer putFloat(int index, float value) {
buf.putFloat(index, value);
return this;
}
@Override
public FloatBuffer asFloatBuffer() {
return buf.asFloatBuffer();
}
@Override
public double getDouble() {
return buf.getDouble();
}
@Override
public IoBuffer putDouble(double value) {
buf.putDouble(value);
return this;
}
@Override
public double getDouble(int index) {
return buf.getDouble(index);
}
@Override
public IoBuffer putDouble(int index, double value) {
buf.putDouble(index, value);
return this;
}
@Override
public DoubleBuffer asDoubleBuffer() {
return buf.asDoubleBuffer();
}
@Override
public String getHexDump() {
return buf.getHexDump();
}
@Override
public String getString(int fieldSize, CharsetDecoder decoder) throws CharacterCodingException {
return buf.getString(fieldSize, decoder);
}
@Override
public String getString(CharsetDecoder decoder) throws CharacterCodingException {
return buf.getString(decoder);
}
@Override
public String getPrefixedString(CharsetDecoder decoder) throws CharacterCodingException {
return buf.getPrefixedString(decoder);
}
@Override
public String getPrefixedString(int prefixLength, CharsetDecoder decoder)
throws CharacterCodingException {
return buf.getPrefixedString(prefixLength, decoder);
}
@Override
public IoBuffer putString(CharSequence in, int fieldSize, CharsetEncoder encoder)
throws CharacterCodingException {
buf.putString(in, fieldSize, encoder);
return this;
}
@Override
public IoBuffer putString(CharSequence in, CharsetEncoder encoder)
throws CharacterCodingException {
buf.putString(in, encoder);
return this;
}
@Override
public IoBuffer putPrefixedString(CharSequence in, CharsetEncoder encoder)
throws CharacterCodingException {
buf.putPrefixedString(in, encoder);
return this;
}
@Override
public IoBuffer putPrefixedString(CharSequence in, int prefixLength, CharsetEncoder encoder)
throws CharacterCodingException {
buf.putPrefixedString(in, prefixLength, encoder);
return this;
}
@Override
public IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding,
CharsetEncoder encoder) throws CharacterCodingException {
buf.putPrefixedString(in, prefixLength, padding, encoder);
return this;
}
@Override
public IoBuffer putPrefixedString(CharSequence in, int prefixLength, int padding, byte padValue,
CharsetEncoder encoder) throws CharacterCodingException {
buf.putPrefixedString(in, prefixLength, padding, padValue, encoder);
return this;
}
@Override
public IoBuffer skip(int size) {
buf.skip(size);
return this;
}
@Override
public IoBuffer fill(byte value, int size) {
buf.fill(value, size);
return this;
}
@Override
public IoBuffer fillAndReset(byte value, int size) {
buf.fillAndReset(value, size);
return this;
}
@Override
public IoBuffer fill(int size) {
buf.fill(size);
return this;
}
@Override
public IoBuffer fillAndReset(int size) {
buf.fillAndReset(size);
return this;
}
@Override
public boolean isAutoExpand() {
return buf.isAutoExpand();
}
@Override
public IoBuffer setAutoExpand(boolean autoExpand) {
buf.setAutoExpand(autoExpand);
return this;
}
@Override
public IoBuffer expand(int pos, int expectedRemaining) {
buf.expand(pos, expectedRemaining);
return this;
}
@Override
public IoBuffer expand(int expectedRemaining) {
buf.expand(expectedRemaining);
return this;
}
@Override
public Object getObject() throws ClassNotFoundException {
return buf.getObject();
}
@Override
public Object getObject(ClassLoader classLoader) throws ClassNotFoundException {
return buf.getObject(classLoader);
}
@Override
public IoBuffer putObject(Object o) {
buf.putObject(o);
return this;
}
@Override
public InputStream asInputStream() {
return buf.asInputStream();
}
@Override
public OutputStream asOutputStream() {
return buf.asOutputStream();
}
@Override
public IoBuffer duplicate() {
return buf.duplicate();
}
@Override
public IoBuffer slice() {
return buf.slice();
}
@Override
public IoBuffer asReadOnlyBuffer() {
return buf.asReadOnlyBuffer();
}
@Override
public byte[] array() {
return buf.array();
}
@Override
public int arrayOffset() {
return buf.arrayOffset();
}
@Override
public int minimumCapacity() {
return buf.minimumCapacity();
}
@Override
public IoBuffer minimumCapacity(int minimumCapacity) {
buf.minimumCapacity(minimumCapacity);
return this;
}
@Override
public IoBuffer capacity(int newCapacity) {
buf.capacity(newCapacity);
return this;
}
@Override
public boolean isReadOnly() {
return buf.isReadOnly();
}
@Override
public int markValue() {
return buf.markValue();
}
@Override
public boolean hasArray() {
return buf.hasArray();
}
@Override
public void free() {
buf.free();
}
@Override
public boolean isDerived() {
return buf.isDerived();
}
@Override
public boolean isAutoShrink() {
return buf.isAutoShrink();
}
@Override
public IoBuffer setAutoShrink(boolean autoShrink) {
buf.setAutoShrink(autoShrink);
return this;
}
@Override
public IoBuffer shrink() {
buf.shrink();
return this;
}
@Override
public int getMediumInt() {
return buf.getMediumInt();
}
@Override
public int getUnsignedMediumInt() {
return buf.getUnsignedMediumInt();
}
@Override
public int getMediumInt(int index) {
return buf.getMediumInt(index);
}
@Override
public int getUnsignedMediumInt(int index) {
return buf.getUnsignedMediumInt(index);
}
@Override
public IoBuffer putMediumInt(int value) {
buf.putMediumInt(value);
return this;
}
@Override
public IoBuffer putMediumInt(int index, int value) {
buf.putMediumInt(index, value);
return this;
}
@Override
public String getHexDump(int lengthLimit) {
return buf.getHexDump(lengthLimit);
}
@Override
public boolean prefixedDataAvailable(int prefixLength) {
return buf.prefixedDataAvailable(prefixLength);
}
@Override
public boolean prefixedDataAvailable(int prefixLength, int maxDataLength) {
return buf.prefixedDataAvailable(prefixLength, maxDataLength);
}
@Override
public int indexOf(byte b) {
return buf.indexOf(b);
}
@Override
public <E extends Enum<E>> E getEnum(Class<E> enumClass) {
return buf.getEnum(enumClass);
}
@Override
public <E extends Enum<E>> E getEnum(int index, Class<E> enumClass) {
return buf.getEnum(index, enumClass);
}
@Override
public <E extends Enum<E>> E getEnumShort(Class<E> enumClass) {
return buf.getEnumShort(enumClass);
}
@Override
public <E extends Enum<E>> E getEnumShort(int index, Class<E> enumClass) {
return buf.getEnumShort(index, enumClass);
}
@Override
public <E extends Enum<E>> E getEnumInt(Class<E> enumClass) {
return buf.getEnumInt(enumClass);
}
@Override
public <E extends Enum<E>> E getEnumInt(int index, Class<E> enumClass) {
return buf.getEnumInt(index, enumClass);
}
@Override
public IoBuffer putEnum(Enum<?> e) {
buf.putEnum(e);
return this;
}
@Override
public IoBuffer putEnum(int index, Enum<?> e) {
buf.putEnum(index, e);
return this;
}
@Override
public IoBuffer putEnumShort(Enum<?> e) {
buf.putEnumShort(e);
return this;
}
@Override
public IoBuffer putEnumShort(int index, Enum<?> e) {
buf.putEnumShort(index, e);
return this;
}
@Override
public IoBuffer putEnumInt(Enum<?> e) {
buf.putEnumInt(e);
return this;
}
@Override
public IoBuffer putEnumInt(int index, Enum<?> e) {
buf.putEnumInt(index, e);
return this;
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSet(Class<E> enumClass) {
return buf.getEnumSet(enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSet(int index, Class<E> enumClass) {
return buf.getEnumSet(index, enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetShort(Class<E> enumClass) {
return buf.getEnumSetShort(enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetShort(int index, Class<E> enumClass) {
return buf.getEnumSetShort(index, enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetInt(Class<E> enumClass) {
return buf.getEnumSetInt(enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetInt(int index, Class<E> enumClass) {
return buf.getEnumSetInt(index, enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetLong(Class<E> enumClass) {
return buf.getEnumSetLong(enumClass);
}
@Override
public <E extends Enum<E>> EnumSet<E> getEnumSetLong(int index, Class<E> enumClass) {
return buf.getEnumSetLong(index, enumClass);
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSet(Set<E> set) {
buf.putEnumSet(set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSet(int index, Set<E> set) {
buf.putEnumSet(index, set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetShort(Set<E> set) {
buf.putEnumSetShort(set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetShort(int index, Set<E> set) {
buf.putEnumSetShort(index, set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetInt(Set<E> set) {
buf.putEnumSetInt(set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetInt(int index, Set<E> set) {
buf.putEnumSetInt(index, set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetLong(Set<E> set) {
buf.putEnumSetLong(set);
return this;
}
@Override
public <E extends Enum<E>> IoBuffer putEnumSetLong(int index, Set<E> set) {
buf.putEnumSetLong(index, set);
return this;
}
}

View File

@@ -0,0 +1,105 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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 com.google.code.yanf4j.buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* A simplistic {@link IoBufferAllocator} which simply allocates a new buffer every time.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 671827 $, $Date: 2008-06-26 10:49:48 +0200 (Thu, 26 Jun 2008) $
*/
public class SimpleBufferAllocator implements IoBufferAllocator {
public IoBuffer allocate(int capacity, boolean direct) {
return wrap(allocateNioBuffer(capacity, direct));
}
public ByteBuffer allocateNioBuffer(int capacity, boolean direct) {
ByteBuffer nioBuffer;
if (direct) {
nioBuffer = ByteBuffer.allocateDirect(capacity);
} else {
nioBuffer = ByteBuffer.allocate(capacity);
}
return nioBuffer;
}
public IoBuffer wrap(ByteBuffer nioBuffer) {
return new SimpleBuffer(nioBuffer);
}
public void dispose() {}
private class SimpleBuffer extends AbstractIoBuffer {
private ByteBuffer buf;
protected SimpleBuffer(ByteBuffer buf) {
super(SimpleBufferAllocator.this, buf.capacity());
this.buf = buf;
buf.order(ByteOrder.BIG_ENDIAN);
}
protected SimpleBuffer(SimpleBuffer parent, ByteBuffer buf) {
super(parent);
this.buf = buf;
}
@Override
public ByteBuffer buf() {
return buf;
}
@Override
protected void buf(ByteBuffer buf) {
this.buf = buf;
}
@Override
protected IoBuffer duplicate0() {
return new SimpleBuffer(this, this.buf.duplicate());
}
@Override
protected IoBuffer slice0() {
return new SimpleBuffer(this, this.buf.slice());
}
@Override
protected IoBuffer asReadOnlyBuffer0() {
return new SimpleBuffer(this, this.buf.asReadOnlyBuffer());
}
@Override
public byte[] array() {
return buf.array();
}
@Override
public int arrayOffset() {
return buf.arrayOffset();
}
@Override
public boolean hasArray() {
return buf.hasArray();
}
@Override
public void free() {}
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>IoBuffer</title>
</head>
<body>
<h1>IoBuffer from mina</h1>
</body>
</html>

View File

@@ -0,0 +1,191 @@
/**
* Copyright [2009-2010] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.config;
import net.rubyeye.xmemcached.impl.ReconnectRequest;
import java.util.concurrent.DelayQueue;
import com.google.code.yanf4j.util.SystemUtils;
/**
* Networking configuration
*
* @author dennis
*
*/
public class Configuration {
public static final String XMEMCACHED_SELECTOR_POOL_SIZE = "xmemcached.selector.pool.size";
/**
* Read buffer size per connection
*/
private int sessionReadBufferSize = 32 * 1024;
/**
* Socket SO_TIMEOUT option
*/
private int soTimeout = 0;
/**
* Thread count for processing WRITABLE event
*/
private int writeThreadCount = 0;
/**
* Whether to enable statistics
*/
private boolean statisticsServer = false;
/**
* Whether to handle read write concurrently,default is true
*/
private boolean handleReadWriteConcurrently = true;
/**
* Thread coount for processing message dispatching
*/
private int dispatchMessageThreadCount = 0;
/**
* THread count for processing READABLE event
*/
private int readThreadCount = 1;
private int selectorPoolSize =
System.getProperty(XMEMCACHED_SELECTOR_POOL_SIZE) == null ? SystemUtils.getSystemThreadCount()
: Integer.parseInt(System.getProperty(XMEMCACHED_SELECTOR_POOL_SIZE));
/**
* Increasing buffer size per time
*/
public static final int DEFAULT_INCREASE_BUFF_SIZE = 32 * 1024;
/**
* Max read buffer size for connection
*/
public final static int MAX_READ_BUFFER_SIZE = 128 * 1024;
/**
* check session idle interval
*/
private long checkSessionTimeoutInterval = 1000L;
public final int getWriteThreadCount() {
return this.writeThreadCount;
}
public final int getDispatchMessageThreadCount() {
return this.dispatchMessageThreadCount;
}
public final void setDispatchMessageThreadCount(int dispatchMessageThreadCount) {
this.dispatchMessageThreadCount = dispatchMessageThreadCount;
}
public final void setWriteThreadCount(int writeThreadCount) {
this.writeThreadCount = writeThreadCount;
}
private long sessionIdleTimeout = 5000L;
/**
* @see setSessionIdleTimeout
* @return
*/
public final long getSessionIdleTimeout() {
return this.sessionIdleTimeout;
}
public final void setSessionIdleTimeout(long sessionIdleTimeout) {
this.sessionIdleTimeout = sessionIdleTimeout;
}
/**
* @see setSessionReadBufferSize
* @return
*/
public final int getSessionReadBufferSize() {
return this.sessionReadBufferSize;
}
public final boolean isHandleReadWriteConcurrently() {
return this.handleReadWriteConcurrently;
}
public final int getSoTimeout() {
return this.soTimeout;
}
protected long statisticsInterval = 5 * 60 * 1000L;
public final long getStatisticsInterval() {
return this.statisticsInterval;
}
public final void setStatisticsInterval(long statisticsInterval) {
this.statisticsInterval = statisticsInterval;
}
public final void setSoTimeout(int soTimeout) {
if (soTimeout < 0) {
throw new IllegalArgumentException("soTimeout<0");
}
this.soTimeout = soTimeout;
}
public final void setHandleReadWriteConcurrently(boolean handleReadWriteConcurrently) {
this.handleReadWriteConcurrently = handleReadWriteConcurrently;
}
public final void setSessionReadBufferSize(int tcpHandlerReadBufferSize) {
if (tcpHandlerReadBufferSize <= 0) {
throw new IllegalArgumentException("tcpHandlerReadBufferSize<=0");
}
this.sessionReadBufferSize = tcpHandlerReadBufferSize;
}
public final boolean isStatisticsServer() {
return this.statisticsServer;
}
public final void setStatisticsServer(boolean statisticsServer) {
this.statisticsServer = statisticsServer;
}
/**
* @see setReadThreadCount
* @return
*/
public final int getReadThreadCount() {
return this.readThreadCount;
}
public final void setReadThreadCount(int readThreadCount) {
if (readThreadCount < 0) {
throw new IllegalArgumentException("readThreadCount<0");
}
this.readThreadCount = readThreadCount;
}
public void setCheckSessionTimeoutInterval(long checkSessionTimeoutInterval) {
this.checkSessionTimeoutInterval = checkSessionTimeoutInterval;
}
public long getCheckSessionTimeoutInterval() {
return this.checkSessionTimeoutInterval;
}
public void setSelectorPoolSize(int selectorPoolSize) {
this.selectorPoolSize = selectorPoolSize;
}
public int getSelectorPoolSize() {
return selectorPoolSize;
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>Networking configuration</title>
</head>
<body>
<h1>Networking configuration</h1>
</body>
</html>

View File

@@ -0,0 +1,42 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
import com.google.code.yanf4j.buffer.IoBuffer;
/**
*
*
* Codec factory
*
* @author boyan
*
*/
public interface CodecFactory {
public interface Encoder {
public IoBuffer encode(Object message, Session session);
}
public interface Decoder {
public Object decode(IoBuffer buff, Session session);
}
public Encoder getEncoder();
public Decoder getDecoder();
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
import java.io.IOException;
import java.net.InetSocketAddress;
import com.google.code.yanf4j.statistics.Statistics;
/**
* Networking Controller
*
*
* @author boyan
*
*/
public interface Controller {
public abstract long getSessionTimeout();
public long getSessionIdleTimeout();
public void setSessionIdleTimeout(long sessionIdleTimeout);
public abstract void setSessionTimeout(long sessionTimeout);
public abstract int getSoTimeout();
public abstract void setSoTimeout(int timeout);
public abstract void addStateListener(ControllerStateListener listener);
public void removeStateListener(ControllerStateListener listener);
public abstract boolean isHandleReadWriteConcurrently();
public abstract void setHandleReadWriteConcurrently(boolean handleReadWriteConcurrently);
public abstract int getReadThreadCount();
public abstract void setReadThreadCount(int readThreadCount);
public abstract Handler getHandler();
public abstract void setHandler(Handler handler);
public abstract int getPort();
public abstract void start() throws IOException;
public abstract boolean isStarted();
public abstract Statistics getStatistics();
public abstract CodecFactory getCodecFactory();
public abstract void setCodecFactory(CodecFactory codecFactory);
public abstract void stop() throws IOException;
public void setReceiveThroughputLimit(double receivePacketRate);
public double getReceiveThroughputLimit();
public double getSendThroughputLimit();
public void setSendThroughputLimit(double sendThroughputLimit);
public InetSocketAddress getLocalSocketAddress();
public void setLocalSocketAddress(InetSocketAddress inetAddress);
public int getDispatchMessageThreadCount();
public void setDispatchMessageThreadCount(int dispatchMessageThreadPoolSize);
public int getWriteThreadCount();
public void setWriteThreadCount(int writeThreadCount);
public <T> void setSocketOption(SocketOption<T> socketOption, T value);
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
* Controller lifecycle mark interface
*
* @author boyan
*
*/
public interface ControllerLifeCycle {
public void notifyReady();
public void notifyStarted();
public void notifyAllSessionClosed();
public void notifyException(Throwable t);
public void notifyStopped();
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
*
* Controller state listener
*
* @author boyan
*
* @since 1.0, 2009-12-16 <20><><EFBFBD><EFBFBD>05:59:44
*/
public interface ControllerStateListener {
/**
* When controller is started
*
* @param controller
*/
public void onStarted(final Controller controller);
/**
* When controller is ready
*
* @param controller
*/
public void onReady(final Controller controller);
/**
* When all connections are closed
*
* @param controller
*/
public void onAllSessionClosed(final Controller controller);
/**
* When controller has been stopped
*
* @param controller
*/
public void onStopped(final Controller controller);
public void onException(final Controller controller, Throwable t);
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
* Dispatcher
*
* @author dennis
*
*/
public interface Dispatcher {
public void dispatch(Runnable r);
public void stop();
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
* Event Type
*
* @author dennis
*
*/
public enum EventType {
REGISTER, READABLE, WRITEABLE, ENABLE_READ, ENABLE_WRITE, UNREGISTER, EXPIRED, IDLE, CONNECTED
}

View File

@@ -0,0 +1,46 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
*
* IO Event handler
*
* @author boyan
*
*/
public interface Handler {
public void onSessionCreated(Session session);
public void onSessionStarted(Session session);
public void onSessionClosed(Session session);
public void onMessageReceived(Session session, Object msg);
public void onMessageSent(Session session, Object msg);
public void onExceptionCaught(Session session, Throwable throwable);
public void onSessionExpired(Session session);
public void onSessionIdle(Session session);
public void onSessionConnected(Session session);
}

View File

@@ -0,0 +1,216 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteOrder;
/**
* Abstract connection
*
* @author dennis
*
*/
public interface Session {
public enum SessionStatus {
NULL, READING, WRITING, IDLE, INITIALIZE, CLOSING, CLOSED
}
/**
* Start session
*/
public void start();
/**
* Write a message,if you don't care when the message is written
*
* @param packet
*/
public void write(Object packet);
/**
* Check if session is closed
*
* @return
*/
public boolean isClosed();
/**
* Close session
*/
public void close();
/**
* Return the remote end's InetSocketAddress
*
* @return
*/
public InetSocketAddress getRemoteSocketAddress();
public InetAddress getLocalAddress();
/**
* Return true if using blocking write
*
* @return
*/
public boolean isUseBlockingWrite();
/**
* Set if using blocking write
*
* @param useBlockingWrite
*/
public void setUseBlockingWrite(boolean useBlockingWrite);
/**
* Return true if using blocking read
*
* @return
*/
public boolean isUseBlockingRead();
public void setUseBlockingRead(boolean useBlockingRead);
/**
* Flush the write queue,this method may be no effect if OP_WRITE is running.
*/
public void flush();
/**
* Return true if session is expired,session is expired beacause you set the sessionTimeout,if
* since session's last operation form now is over this vlaue,isExpired return true,and
* Handler.onExpired() will be invoked.
*
* @return
*/
public boolean isExpired();
/**
* Check if session is idle
*
* @return
*/
public boolean isIdle();
/**
* Return current encoder
*
* @return
*/
public CodecFactory.Encoder getEncoder();
/**
* Set encoder
*
* @param encoder
*/
public void setEncoder(CodecFactory.Encoder encoder);
/**
* Return current decoder
*
* @return
*/
public CodecFactory.Decoder getDecoder();
public void setDecoder(CodecFactory.Decoder decoder);
/**
* Return true if allow handling read and write concurrently,default is true.
*
* @return
*/
public boolean isHandleReadWriteConcurrently();
public void setHandleReadWriteConcurrently(boolean handleReadWriteConcurrently);
/**
* Return the session read buffer's byte order,big end or little end.
*
* @return
*/
public ByteOrder getReadBufferByteOrder();
public void setReadBufferByteOrder(ByteOrder readBufferByteOrder);
/**
* Set a attribute attched with this session
*
* @param key
* @param value
*/
public void setAttribute(String key, Object value);
/**
* Remove attribute
*
* @param key
*/
public void removeAttribute(String key);
/**
* Return attribute associated with key
*
* @param key
* @return
*/
public Object getAttribute(String key);
/**
* Clear attributes
*/
public void clearAttributes();
/**
* Return the bytes in write queue,there bytes is in memory.Use this method to controll writing
* speed.
*
* @return
*/
public long getScheduleWritenBytes();
/**
* Return last operation timestamp,operation include read,write,idle
*
* @return
*/
public long getLastOperationTimeStamp();
/**
* return true if it is a loopback connection
*
* @return
*/
public boolean isLoopbackConnection();
public long getSessionIdleTimeout();
public void setSessionIdleTimeout(long sessionIdleTimeout);
public long getSessionTimeout();
public void setSessionTimeout(long sessionTimeout);
public Object setAttributeIfAbsent(String key, Object value);
public Handler getHandler();
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
import java.util.Queue;
import com.google.code.yanf4j.statistics.Statistics;
/**
* Session configuration
*
* @author dennis
*
*/
public class SessionConfig {
public final Handler handler;
public final CodecFactory codecFactory;
public final Statistics statistics;
public final Queue<WriteMessage> queue;
public final Dispatcher dispatchMessageDispatcher;
public final boolean handleReadWriteConcurrently;
public final long sessionTimeout;
public final long sessionIdelTimeout;
public SessionConfig(Handler handler, CodecFactory codecFactory, Statistics statistics,
Queue<WriteMessage> queue, Dispatcher dispatchMessageDispatcher,
boolean handleReadWriteConcurrently, long sessionTimeout, long sessionIdelTimeout) {
this.handler = handler;
this.codecFactory = codecFactory;
this.statistics = statistics;
this.queue = queue;
this.dispatchMessageDispatcher = dispatchMessageDispatcher;
this.handleReadWriteConcurrently = handleReadWriteConcurrently;
this.sessionTimeout = sessionTimeout;
this.sessionIdelTimeout = sessionIdelTimeout;
}
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
* Session manager
*
* @author dennis
*
*/
public interface SessionManager {
/**
* Register session to controller
*
* @param session
*/
public void registerSession(Session session);
/**
* Unregister session
*
* @param session
*/
public void unregisterSession(Session session);
}

View File

@@ -0,0 +1,79 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
/**
* Socket option
*
* @author dennis
*
* @param <T>
*/
public class SocketOption<T> {
private final String name;
private final Class<T> type;
public SocketOption(String name, Class<T> type) {
this.name = name;
this.type = type;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (this.name == null ? 0 : this.name.hashCode());
return result;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
SocketOption other = (SocketOption) obj;
if (this.name == null) {
if (other.name != null) {
return false;
}
} else if (!this.name.equals(other.name)) {
return false;
}
return true;
}
public String name() {
return this.name;
}
public Class<T> type() {
return this.type;
}
@Override
public String toString() {
return this.name;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.impl.FutureImpl;
/**
* Write message with a buffer
*
* @author dennis
*
*/
public interface WriteMessage {
public void writing();
public boolean isWriting();
public IoBuffer getWriteBuffer();
public Object getMessage();
public void setWriteBuffer(IoBuffer buffers);
public FutureImpl<Boolean> getWriteFuture();
}

View File

@@ -0,0 +1,541 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ThreadPoolExecutor;
import net.rubyeye.xmemcached.utils.AddrUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Controller;
import com.google.code.yanf4j.core.ControllerLifeCycle;
import com.google.code.yanf4j.core.ControllerStateListener;
import com.google.code.yanf4j.core.Dispatcher;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.SocketOption;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.statistics.Statistics;
import com.google.code.yanf4j.statistics.impl.DefaultStatistics;
import com.google.code.yanf4j.statistics.impl.SimpleStatistics;
import com.google.code.yanf4j.util.ConcurrentHashSet;
import com.google.code.yanf4j.util.DispatcherFactory;
import com.google.code.yanf4j.util.LinkedTransferQueue;
/**
* Base controller
*
* @author dennis
*
*/
public abstract class AbstractController implements Controller, ControllerLifeCycle {
protected Statistics statistics = new DefaultStatistics();
protected long statisticsInterval;
protected static final Logger log = LoggerFactory.getLogger(AbstractController.class);
/**
* controller state listener list
*/
protected CopyOnWriteArrayList<ControllerStateListener> stateListeners =
new CopyOnWriteArrayList<ControllerStateListener>();
/**
* Event handler
*/
protected Handler handler;
/**
* Codec Factory
*/
protected CodecFactory codecFactory;
/**
* Status
*/
protected volatile boolean started;
/**
* local bind address
*/
protected InetSocketAddress localSocketAddress;
/**
* Read event processing thread count
*/
protected int readThreadCount;
protected int writeThreadCount;
protected int dispatchMessageThreadCount;
protected Configuration configuration;
protected Dispatcher readEventDispatcher, dispatchMessageDispatcher, writeEventDispatcher;
protected long sessionTimeout;
protected boolean handleReadWriteConcurrently = true;
protected int soTimeout;
/**
* Socket options
*/
@SuppressWarnings("unchecked")
protected Map<SocketOption, Object> socketOptions = new HashMap<SocketOption, Object>();
@SuppressWarnings("unchecked")
public void setSocketOptions(Map<SocketOption, Object> socketOptions) {
if (socketOptions == null) {
throw new NullPointerException("Null socketOptions");
}
this.socketOptions = socketOptions;
}
/**
* Connected session set
*/
protected Set<Session> sessionSet = new ConcurrentHashSet<Session>();
private Thread shutdownHookThread;
private volatile boolean isHutdownHookCalled = false;
public final int getDispatchMessageThreadCount() {
return dispatchMessageThreadCount;
}
public final void setDispatchMessageThreadCount(int dispatchMessageThreadPoolSize) {
if (started) {
throw new IllegalStateException("Controller is started");
}
if (dispatchMessageThreadPoolSize < 0) {
throw new IllegalArgumentException("dispatchMessageThreadPoolSize<0");
}
dispatchMessageThreadCount = dispatchMessageThreadPoolSize;
}
public long getSessionIdleTimeout() {
return configuration.getSessionIdleTimeout();
}
/**
* Build write queue for session
*
* @return
*/
protected Queue<WriteMessage> buildQueue() {
return new LinkedTransferQueue<WriteMessage>();
}
public void setSessionIdleTimeout(long sessionIdleTimeout) {
configuration.setSessionIdleTimeout(sessionIdleTimeout);
}
public long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public int getSoTimeout() {
return soTimeout;
}
public void setSoTimeout(int timeout) {
soTimeout = timeout;
}
public AbstractController() {
this(new Configuration(), null, null);
}
public double getReceiveThroughputLimit() {
return statistics.getReceiveThroughputLimit();
}
public double getSendThroughputLimit() {
return statistics.getSendThroughputLimit();
}
public void setReceiveThroughputLimit(double receiveThroughputLimit) {
statistics.setReceiveThroughputLimit(receiveThroughputLimit);
}
public void setSendThroughputLimit(double sendThroughputLimit) {
statistics.setSendThroughputLimit(sendThroughputLimit);
}
public AbstractController(Configuration configuration) {
this(configuration, null, null);
}
public AbstractController(Configuration configuration, CodecFactory codecFactory) {
this(configuration, null, codecFactory);
}
public AbstractController(Configuration configuration, Handler handler,
CodecFactory codecFactory) {
init(configuration, handler, codecFactory);
}
private synchronized void init(Configuration configuration, Handler handler,
CodecFactory codecFactory) {
setHandler(handler);
setCodecFactory(codecFactory);
setConfiguration(configuration);
setReadThreadCount(configuration.getReadThreadCount());
setWriteThreadCount(configuration.getWriteThreadCount());
setDispatchMessageThreadCount(configuration.getDispatchMessageThreadCount());
setHandleReadWriteConcurrently(configuration.isHandleReadWriteConcurrently());
setSoTimeout(configuration.getSoTimeout());
setStatisticsConfig(configuration);
setReceiveThroughputLimit(-0.1d);
setStarted(false);
}
void setStarted(boolean started) {
this.started = started;
}
private void setStatisticsConfig(Configuration configuration) {
if (configuration.isStatisticsServer()) {
statistics = new SimpleStatistics();
statisticsInterval = configuration.getStatisticsInterval();
} else {
statistics = new DefaultStatistics();
statisticsInterval = -1;
}
}
public Configuration getConfiguration() {
return configuration;
}
public void setConfiguration(Configuration configuration) {
if (configuration == null) {
throw new IllegalArgumentException("Null Configuration");
}
this.configuration = configuration;
}
public InetSocketAddress getLocalSocketAddress() {
return localSocketAddress;
}
public void setLocalSocketAddress(InetSocketAddress inetSocketAddress) {
localSocketAddress = inetSocketAddress;
}
public void onAccept(SelectionKey sk) throws IOException {
statistics.statisticsAccept();
}
public void onConnect(SelectionKey key) throws IOException {
throw new UnsupportedOperationException();
}
public void addStateListener(ControllerStateListener listener) {
stateListeners.add(listener);
}
public void removeStateListener(ControllerStateListener listener) {
stateListeners.remove(listener);
}
public boolean isHandleReadWriteConcurrently() {
return handleReadWriteConcurrently;
}
public void setHandleReadWriteConcurrently(boolean handleReadWriteConcurrently) {
this.handleReadWriteConcurrently = handleReadWriteConcurrently;
}
public int getReadThreadCount() {
return readThreadCount;
}
public void setReadThreadCount(int readThreadCount) {
if (started) {
throw new IllegalStateException();
}
if (readThreadCount < 0) {
throw new IllegalArgumentException("readThreadCount<0");
}
this.readThreadCount = readThreadCount;
}
public final int getWriteThreadCount() {
return writeThreadCount;
}
public final void setWriteThreadCount(int writeThreadCount) {
if (started) {
throw new IllegalStateException();
}
if (writeThreadCount < 0) {
throw new IllegalArgumentException("readThreadCount<0");
}
this.writeThreadCount = writeThreadCount;
}
public Handler getHandler() {
return handler;
}
public void setHandler(Handler handler) {
if (started) {
throw new IllegalStateException("The Controller have started");
}
this.handler = handler;
}
public int getPort() {
if (localSocketAddress != null) {
return localSocketAddress.getPort();
}
throw new NullPointerException("Controller is not binded");
}
public synchronized void start() throws IOException {
if (isStarted()) {
return;
}
if (getHandler() == null) {
throw new IOException("The handler is null");
}
if (getCodecFactory() == null) {
setCodecFactory(new ByteBufferCodecFactory());
}
setStarted(true);
setReadEventDispatcher(DispatcherFactory.newDispatcher(getReadThreadCount(),
new ThreadPoolExecutor.CallerRunsPolicy(), "xmemcached-read-thread"));
setWriteEventDispatcher(DispatcherFactory.newDispatcher(getWriteThreadCount(),
new ThreadPoolExecutor.CallerRunsPolicy(), "xmemcached-write-thread"));
setDispatchMessageDispatcher(DispatcherFactory.newDispatcher(getDispatchMessageThreadCount(),
new ThreadPoolExecutor.CallerRunsPolicy(), "xmemcached-dispatch-thread"));
startStatistics();
start0();
notifyStarted();
if (AddrUtil.isEnableShutDownHook()) {
shutdownHookThread = new Thread() {
@Override
public void run() {
try {
isHutdownHookCalled = true;
AbstractController.this.stop();
} catch (IOException e) {
log.error("Stop controller fail", e);
}
}
};
Runtime.getRuntime().addShutdownHook(shutdownHookThread);
}
log.info("The Controller started at " + localSocketAddress + " ...");
}
protected abstract void start0() throws IOException;
void setDispatchMessageDispatcher(Dispatcher dispatcher) {
Dispatcher oldDispatcher = dispatchMessageDispatcher;
dispatchMessageDispatcher = dispatcher;
if (oldDispatcher != null) {
oldDispatcher.stop();
}
}
Dispatcher getReadEventDispatcher() {
return readEventDispatcher;
}
void setReadEventDispatcher(Dispatcher dispatcher) {
Dispatcher oldDispatcher = readEventDispatcher;
readEventDispatcher = dispatcher;
if (oldDispatcher != null) {
oldDispatcher.stop();
}
}
void setWriteEventDispatcher(Dispatcher dispatcher) {
Dispatcher oldDispatcher = writeEventDispatcher;
writeEventDispatcher = dispatcher;
if (oldDispatcher != null) {
oldDispatcher.stop();
}
}
private final void startStatistics() {
statistics.start();
}
public void notifyStarted() {
for (ControllerStateListener stateListener : stateListeners) {
stateListener.onStarted(this);
}
}
public boolean isStarted() {
return started;
}
public final Statistics getStatistics() {
return statistics;
}
public final CodecFactory getCodecFactory() {
return codecFactory;
}
public final void setCodecFactory(CodecFactory codecFactory) {
this.codecFactory = codecFactory;
}
public void notifyReady() {
for (ControllerStateListener stateListener : stateListeners) {
stateListener.onReady(this);
}
}
public final synchronized void unregisterSession(Session session) {
sessionSet.remove(session);
if (sessionSet.size() == 0) {
notifyAllSessionClosed();
notifyAll();
}
}
public void checkStatisticsForRestart() {
if (statisticsInterval > 0
&& System.currentTimeMillis() - statistics.getStartedTime() > statisticsInterval * 1000) {
statistics.restart();
}
}
public final synchronized void registerSession(Session session) {
if (started) {
sessionSet.add(session);
} else {
session.close();
}
}
public void stop() throws IOException {
synchronized (this) {
if (!isStarted()) {
return;
}
setStarted(false);
}
for (Session session : sessionSet) {
session.close();
}
stopStatistics();
stopDispatcher();
sessionSet.clear();
notifyStopped();
clearStateListeners();
stop0();
if (AddrUtil.isEnableShutDownHook() && shutdownHookThread != null && !isHutdownHookCalled) {
Runtime.getRuntime().removeShutdownHook(shutdownHookThread);
}
log.info("Controller has been stopped.");
}
protected abstract void stop0() throws IOException;
private final void stopDispatcher() {
if (readEventDispatcher != null) {
readEventDispatcher.stop();
}
if (dispatchMessageDispatcher != null) {
dispatchMessageDispatcher.stop();
}
if (writeEventDispatcher != null) {
writeEventDispatcher.stop();
}
}
private final void stopStatistics() {
statistics.stop();
}
private final void clearStateListeners() {
stateListeners.clear();
}
public final void notifyException(Throwable t) {
for (ControllerStateListener stateListener : stateListeners) {
stateListener.onException(this, t);
}
}
public final void notifyStopped() {
for (ControllerStateListener stateListener : stateListeners) {
stateListener.onStopped(this);
}
}
public final void notifyAllSessionClosed() {
for (ControllerStateListener stateListener : stateListeners) {
stateListener.onAllSessionClosed(this);
}
}
public Set<Session> getSessionSet() {
return Collections.unmodifiableSet(sessionSet);
}
public <T> void setSocketOption(SocketOption<T> socketOption, T value) {
if (socketOption == null) {
throw new NullPointerException("Null socketOption");
}
if (value == null) {
throw new NullPointerException("Null value");
}
if (!socketOption.type().equals(value.getClass())) {
throw new IllegalArgumentException("Expected " + socketOption.type().getSimpleName()
+ " value,but givend " + value.getClass().getSimpleName());
}
socketOptions.put(socketOption, value);
}
@SuppressWarnings("unchecked")
public <T> T getSocketOption(SocketOption<T> socketOption) {
return (T) socketOptions.get(socketOption);
}
/**
* Bind localhost address
*
* @param inetSocketAddress
* @throws IOException
*/
public void bind(InetSocketAddress inetSocketAddress) throws IOException {
if (inetSocketAddress == null) {
throw new IllegalArgumentException("Null inetSocketAddress");
}
setLocalSocketAddress(inetSocketAddress);
start();
}
}

View File

@@ -0,0 +1,439 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.io.IOException;
import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.util.Queue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Dispatcher;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.SessionConfig;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.statistics.Statistics;
import com.google.code.yanf4j.util.LinkedTransferQueue;
/**
* Base connection
*
* @author dennis
*
*/
public abstract class AbstractSession implements Session {
protected IoBuffer readBuffer;
protected static final Logger log = LoggerFactory.getLogger(AbstractSession.class);
protected final ConcurrentHashMap<String, Object> attributes =
new ConcurrentHashMap<String, Object>();
protected Queue<WriteMessage> writeQueue;
protected long sessionIdleTimeout;
protected long sessionTimeout;
public long getSessionIdleTimeout() {
return sessionIdleTimeout;
}
public void setSessionIdleTimeout(long sessionIdleTimeout) {
this.sessionIdleTimeout = sessionIdleTimeout;
}
public long getSessionTimeout() {
return sessionTimeout;
}
public void setSessionTimeout(long sessionTimeout) {
this.sessionTimeout = sessionTimeout;
}
public Queue<WriteMessage> getWriteQueue() {
return writeQueue;
}
public Statistics getStatistics() {
return statistics;
}
public Handler getHandler() {
return handler;
}
public Dispatcher getDispatchMessageDispatcher() {
return dispatchMessageDispatcher;
}
public ReentrantLock getWriteLock() {
return writeLock;
}
protected CodecFactory.Encoder encoder;
protected CodecFactory.Decoder decoder;
protected volatile boolean closed;
protected Statistics statistics;
protected Handler handler;
protected boolean loopback;
public AtomicLong lastOperationTimeStamp = new AtomicLong(0);
protected AtomicLong scheduleWritenBytes = new AtomicLong(0);
protected final Dispatcher dispatchMessageDispatcher;
protected boolean useBlockingWrite = false;
protected boolean useBlockingRead = true;
protected boolean handleReadWriteConcurrently = true;
public abstract void decode();
public void updateTimeStamp() {
lastOperationTimeStamp.set(System.currentTimeMillis());
}
public long getLastOperationTimeStamp() {
return lastOperationTimeStamp.get();
}
public final boolean isHandleReadWriteConcurrently() {
return handleReadWriteConcurrently;
}
public final void setHandleReadWriteConcurrently(boolean handleReadWriteConcurrently) {
this.handleReadWriteConcurrently = handleReadWriteConcurrently;
}
public long getScheduleWritenBytes() {
return scheduleWritenBytes.get();
}
public CodecFactory.Encoder getEncoder() {
return encoder;
}
public void setEncoder(CodecFactory.Encoder encoder) {
this.encoder = encoder;
}
public CodecFactory.Decoder getDecoder() {
return decoder;
}
public IoBuffer getReadBuffer() {
return readBuffer;
}
public void setReadBuffer(IoBuffer readBuffer) {
this.readBuffer = readBuffer;
}
public void setDecoder(CodecFactory.Decoder decoder) {
this.decoder = decoder;
}
public final ByteOrder getReadBufferByteOrder() {
if (readBuffer == null) {
throw new IllegalStateException();
}
return readBuffer.order();
}
public final void setReadBufferByteOrder(ByteOrder readBufferByteOrder) {
if (readBuffer == null) {
throw new NullPointerException("Null ReadBuffer");
}
readBuffer.order(readBufferByteOrder);
}
// synchronized,prevent reactors invoking this method concurrently.
protected synchronized void onIdle() {
try {
// check twice
if (isIdle()) {
updateTimeStamp();
handler.onSessionIdle(this);
}
} catch (Throwable e) {
onException(e);
}
}
protected void onConnected() {
try {
handler.onSessionConnected(this);
} catch (Throwable e) {
onException(e);
}
}
public void onExpired() {
try {
if (isExpired()) {
handler.onSessionExpired(this);
}
} catch (Throwable e) {
onException(e);
}
}
protected abstract WriteMessage wrapMessage(Object msg, Future<Boolean> writeFuture);
/**
* Pre-Process WriteMessage before writing to channel
*
* @param writeMessage
* @return
*/
protected WriteMessage preprocessWriteMessage(WriteMessage writeMessage) {
return writeMessage;
}
protected void dispatchReceivedMessage(final Object message) {
if (dispatchMessageDispatcher == null) {
long start = -1;
if (statistics != null && statistics.isStatistics()) {
start = System.currentTimeMillis();
}
onMessage(message, this);
if (start != -1) {
statistics.statisticsProcess(System.currentTimeMillis() - start);
}
} else {
dispatchMessageDispatcher.dispatch(new Runnable() {
public void run() {
long start = -1;
if (statistics != null && statistics.isStatistics()) {
start = System.currentTimeMillis();
}
onMessage(message, AbstractSession.this);
if (start != -1) {
statistics.statisticsProcess(System.currentTimeMillis() - start);
}
}
});
}
}
private void onMessage(final Object message, Session session) {
try {
handler.onMessageReceived(session, message);
} catch (Throwable e) {
onException(e);
}
}
public final boolean isClosed() {
return closed;
}
public final void setClosed(boolean closed) {
this.closed = closed;
}
public void close() {
synchronized (this) {
if (isClosed()) {
return;
}
setClosed(true);
}
try {
closeChannel();
clearAttributes();
log.debug("session closed");
} catch (IOException e) {
onException(e);
log.error("Close session error", e);
} finally {
onClosed();
}
}
protected abstract void closeChannel() throws IOException;
public void onException(Throwable e) {
handler.onExceptionCaught(this, e);
}
protected void onClosed() {
try {
handler.onSessionClosed(this);
} catch (Throwable e) {
onException(e);
}
}
public void setAttribute(String key, Object value) {
attributes.put(key, value);
}
public Object setAttributeIfAbsent(String key, Object value) {
return attributes.putIfAbsent(key, value);
}
public void removeAttribute(String key) {
attributes.remove(key);
}
public Object getAttribute(String key) {
return attributes.get(key);
}
public void clearAttributes() {
attributes.clear();
}
public synchronized void start() {
log.debug("session started");
onStarted();
start0();
}
protected abstract void start0();
protected void onStarted() {
try {
handler.onSessionStarted(this);
} catch (Throwable e) {
onException(e);
}
}
protected ReentrantLock writeLock = new ReentrantLock();
protected AtomicReference<WriteMessage> currentMessage =
new LinkedTransferQueue.PaddedAtomicReference<WriteMessage>(null);
static final class FailFuture implements Future<Boolean> {
public boolean cancel(boolean mayInterruptIfRunning) {
return Boolean.FALSE;
}
public Boolean get() throws InterruptedException, ExecutionException {
return Boolean.FALSE;
}
public Boolean get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
return Boolean.FALSE;
}
public boolean isCancelled() {
return false;
}
public boolean isDone() {
return true;
}
}
public void write(Object packet) {
if (closed) {
return;
}
WriteMessage message = wrapMessage(packet, null);
scheduleWritenBytes.addAndGet(message.getWriteBuffer().remaining());
writeFromUserCode(message);
}
public abstract void writeFromUserCode(WriteMessage message);
public final boolean isLoopbackConnection() {
return loopback;
}
public boolean isUseBlockingWrite() {
return useBlockingWrite;
}
public void setUseBlockingWrite(boolean useBlockingWrite) {
this.useBlockingWrite = useBlockingWrite;
}
public boolean isUseBlockingRead() {
return useBlockingRead;
}
public void setUseBlockingRead(boolean useBlockingRead) {
this.useBlockingRead = useBlockingRead;
}
public void clearWriteQueue() {
writeQueue.clear();
}
public boolean isExpired() {
return false;
}
public boolean isIdle() {
long lastOpTimestamp = getLastOperationTimeStamp();
return lastOpTimestamp > 0 && System.currentTimeMillis() - lastOpTimestamp > sessionIdleTimeout;
}
public AbstractSession(SessionConfig sessionConfig) {
super();
lastOperationTimeStamp.set(System.currentTimeMillis());
statistics = sessionConfig.statistics;
handler = sessionConfig.handler;
writeQueue = sessionConfig.queue;
encoder = sessionConfig.codecFactory.getEncoder();
decoder = sessionConfig.codecFactory.getDecoder();
dispatchMessageDispatcher = sessionConfig.dispatchMessageDispatcher;
handleReadWriteConcurrently = sessionConfig.handleReadWriteConcurrently;
sessionTimeout = sessionConfig.sessionTimeout;
sessionIdleTimeout = sessionConfig.sessionIdelTimeout;
}
public long transferTo(long position, long count, FileChannel target) throws IOException {
throw new UnsupportedOperationException();
}
public long transferFrom(long position, long count, FileChannel source) throws IOException {
throw new UnsupportedOperationException();
}
protected void onCreated() {
try {
handler.onSessionCreated(this);
} catch (Throwable e) {
onException(e);
}
}
}

View File

@@ -0,0 +1,96 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Session;
/**
* Default codec factory
*
* @author dennis
*
*/
public class ByteBufferCodecFactory implements CodecFactory {
static final IoBuffer EMPTY_BUFFER = IoBuffer.allocate(0);
private boolean direct;
public ByteBufferCodecFactory() {
this(false);
}
public ByteBufferCodecFactory(boolean direct) {
super();
this.direct = direct;
this.encoder = new ByteBufferEncoder();
this.decoder = new ByteBufferDecoder();
}
public class ByteBufferDecoder implements Decoder {
public Object decode(IoBuffer buff, Session session) {
if (buff == null) {
return null;
}
if (buff.remaining() == 0) {
return EMPTY_BUFFER;
}
byte[] bytes = new byte[buff.remaining()];
buff.get(bytes);
IoBuffer result = IoBuffer.allocate(bytes.length, ByteBufferCodecFactory.this.direct);
result.put(bytes);
result.flip();
return result;
}
}
private Decoder decoder;
public Decoder getDecoder() {
return this.decoder;
}
public class ByteBufferEncoder implements Encoder {
public IoBuffer encode(Object message, Session session) {
final IoBuffer msgBuffer = (IoBuffer) message;
if (msgBuffer == null) {
return null;
}
if (msgBuffer.remaining() == 0) {
return EMPTY_BUFFER;
}
byte[] bytes = new byte[msgBuffer.remaining()];
msgBuffer.get(bytes);
IoBuffer result = IoBuffer.allocate(bytes.length, ByteBufferCodecFactory.this.direct);
result.put(bytes);
result.flip();
return result;
}
}
private Encoder encoder;
public Encoder getEncoder() {
return this.encoder;
}
}

View File

@@ -0,0 +1,170 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Simple {@link Future} implementation, which uses synchronization {@link Object} to synchronize
* during the lifecycle.
*
* @see Future
*
* @author Alexey Stashok
*/
public class FutureImpl<R> implements Future<R> {
private final Object sync;
private boolean isDone;
private boolean isCancelled;
private Throwable failure;
protected R result;
public FutureImpl() {
this(new Object());
}
public FutureImpl(Object sync) {
this.sync = sync;
}
/**
* Get current result value without any blocking.
*
* @return current result value without any blocking.
*/
public R getResult() {
synchronized (this.sync) {
return this.result;
}
}
/**
* Set the result value and notify about operation completion.
*
* @param result the result value
*/
public void setResult(R result) {
synchronized (this.sync) {
this.result = result;
notifyHaveResult();
}
}
/**
* {@inheritDoc}
*/
public boolean cancel(boolean mayInterruptIfRunning) {
synchronized (this.sync) {
this.isCancelled = true;
notifyHaveResult();
return true;
}
}
/**
* {@inheritDoc}
*/
public boolean isCancelled() {
synchronized (this.sync) {
return this.isCancelled;
}
}
/**
* {@inheritDoc}
*/
public boolean isDone() {
synchronized (this.sync) {
return this.isDone;
}
}
/**
* {@inheritDoc}
*/
public R get() throws InterruptedException, ExecutionException {
synchronized (this.sync) {
for (;;) {
if (this.isDone) {
if (this.isCancelled) {
throw new CancellationException();
} else if (this.failure != null) {
throw new ExecutionException(this.failure);
} else if (this.result != null) {
return this.result;
}
}
this.sync.wait();
}
}
}
/**
* {@inheritDoc}
*/
public R get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
long startTime = System.currentTimeMillis();
long timeoutMillis = TimeUnit.MILLISECONDS.convert(timeout, unit);
synchronized (this.sync) {
for (;;) {
if (this.isDone) {
if (this.isCancelled) {
throw new CancellationException();
} else if (this.failure != null) {
throw new ExecutionException(this.failure);
} else if (this.result != null) {
return this.result;
}
} else if (System.currentTimeMillis() - startTime > timeoutMillis) {
throw new TimeoutException();
}
this.sync.wait(timeoutMillis);
}
}
}
/**
* Notify about the failure, occured during asynchronous operation execution.
*
* @param failure
*/
public void failure(Throwable failure) {
synchronized (this.sync) {
this.failure = failure;
notifyHaveResult();
}
}
/**
* Notify blocked listeners threads about operation completion.
*/
protected void notifyHaveResult() {
this.isDone = true;
this.sync.notifyAll();
}
}

View File

@@ -0,0 +1,206 @@
/*
*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 2007-2008 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU General Public License
* Version 2 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively,
* the "License"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html or
* glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file and include the
* License file at glassfish/bootstrap/legal/LICENSE.txt. Sun designates this particular file as
* subject to the "Classpath" exception as provided by Sun in the GPL Version 2 section of the
* License file that accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or only the GPL Version 2,
* indicate your decision by adding "[Contributor] elects to include this software in this
* distribution under the [CDDL or GPL Version 2] license." If you don't indicate a single choice of
* license, a recipient has the option to distribute your version of this file under either the
* CDDL, the GPL Version 2 or to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then the
* option applies only if the new code is made subject to such option by the copyright holder.
*/
package com.google.code.yanf4j.core.impl;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.ReentrantLock;
/**
* Simple {@link Future} implementation, which uses {@link ReentrantLock} to synchronize during the
* lifecycle.
*
* @see Future
* @see ReentrantLock
*
* @author Alexey Stashok
*/
public class FutureLockImpl<R> implements Future<R> {
private final ReentrantLock lock;
private boolean isDone;
private CountDownLatch latch;
private boolean isCancelled;
private Throwable failure;
protected R result;
public FutureLockImpl() {
this(new ReentrantLock());
}
public FutureLockImpl(ReentrantLock lock) {
this.lock = lock;
latch = new CountDownLatch(1);
}
/**
* Get current result value without any blocking.
*
* @return current result value without any blocking.
*/
public R getResult() {
try {
lock.lock();
return result;
} finally {
lock.unlock();
}
}
/**
* Set the result value and notify about operation completion.
*
* @param result the result value
*/
public void setResult(R result) {
try {
lock.lock();
this.result = result;
notifyHaveResult();
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
public boolean cancel(boolean mayInterruptIfRunning) {
try {
lock.lock();
isCancelled = true;
notifyHaveResult();
return true;
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
public boolean isCancelled() {
try {
lock.lock();
return isCancelled;
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
public boolean isDone() {
try {
lock.lock();
return isDone;
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
public R get() throws InterruptedException, ExecutionException {
latch.await();
try {
lock.lock();
if (isCancelled) {
throw new CancellationException();
} else if (failure != null) {
throw new ExecutionException(failure);
}
return result;
} finally {
lock.unlock();
}
}
/**
* {@inheritDoc}
*/
public R get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
boolean isTimeOut = !latch.await(timeout, unit);
try {
lock.lock();
if (!isTimeOut) {
if (isCancelled) {
throw new CancellationException();
} else if (failure != null) {
throw new ExecutionException(failure);
}
return result;
} else {
throw new TimeoutException();
}
} finally {
lock.unlock();
}
}
/**
* Notify about the failure, occured during asynchronous operation execution.
*
* @param failure
*/
public void failure(Throwable failure) {
try {
lock.lock();
this.failure = failure;
notifyHaveResult();
} finally {
lock.unlock();
}
}
/**
* Notify blocked listeners threads about operation completion.
*/
protected void notifyHaveResult() {
isDone = true;
latch.countDown();
}
}

View File

@@ -0,0 +1,71 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
/**
* IO Handler adapter
*
*
*
* @author boyan
*
*/
public class HandlerAdapter implements Handler {
private static final Logger log = LoggerFactory.getLogger(HandlerAdapter.class);
public void onExceptionCaught(Session session, Throwable throwable) {
}
public void onMessageSent(Session session, Object message) {
}
public void onSessionConnected(Session session) {
}
public void onSessionStarted(Session session) {
}
public void onSessionCreated(Session session) {
}
public void onSessionClosed(Session session) {
}
public void onMessageReceived(Session session, Object message) {
}
public void onSessionIdle(Session session) {
}
public void onSessionExpired(Session session) {
log.warn("Session(" + session.getRemoteSocketAddress() + ") is expired.");
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import com.google.code.yanf4j.core.Dispatcher;
import com.google.code.yanf4j.util.WorkerThreadFactory;
/**
*
*
*
* Pool dispatcher,wrap a threadpool.
*
* @author dennis
*
*/
public class PoolDispatcher implements Dispatcher {
public static final int DEFAULT_POOL_QUEUE_SIZE_FACTOR = 1000;
public static final float DEFAULT_MAX_POOL_SIZE_FACTOR = 1.25f;
private ThreadPoolExecutor threadPool;
public PoolDispatcher(int poolSize) {
this(poolSize, 60, TimeUnit.SECONDS, new ThreadPoolExecutor.AbortPolicy(), "pool-dispatcher");
}
public PoolDispatcher(int poolSize, long keepAliveTime, TimeUnit unit,
RejectedExecutionHandler rejectedExecutionHandler, String prefix) {
this(poolSize, DEFAULT_POOL_QUEUE_SIZE_FACTOR, DEFAULT_MAX_POOL_SIZE_FACTOR, keepAliveTime,
unit, rejectedExecutionHandler, prefix);
}
public PoolDispatcher(int poolSize, int poolQueueSizeFactor, float maxPoolSizeFactor,
long keepAliveTime, TimeUnit unit, RejectedExecutionHandler rejectedExecutionHandler,
String prefix) {
this.threadPool = new ThreadPoolExecutor(poolSize, (int) (maxPoolSizeFactor * poolSize),
keepAliveTime, unit, new ArrayBlockingQueue<Runnable>(poolSize * poolQueueSizeFactor),
new WorkerThreadFactory(prefix));
this.threadPool.setRejectedExecutionHandler(rejectedExecutionHandler);
}
public final void dispatch(Runnable r) {
if (!this.threadPool.isShutdown()) {
this.threadPool.execute(r);
}
}
public void stop() {
this.threadPool.shutdown();
try {
this.threadPool.awaitTermination(1000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}

View File

@@ -0,0 +1,200 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.net.ServerSocket;
import java.net.Socket;
import com.google.code.yanf4j.core.SocketOption;
/**
* Standard socket options
*
* @author dennis
*
*/
public class StandardSocketOption {
/**
* Keep connection alive.
*
* <p>
* The value of this socket option is a {@code Boolean} that represents whether the option is
* enabled or disabled. When the {@code SO_KEEPALIVE} option is enabled the operating system may
* use a <em>keep-alive</em> mechanism to periodically probe the other end of a connection when
* the connection is otherwise idle. The exact semantics of the keep alive mechanism is system
* dependent and therefore unspecified.
*
* <p>
* The initial value of this socket option is {@code FALSE}. The socket option may be enabled or
* disabled at any time.
*
* @see <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC&nbsp;1122 * Requirements for Internet
* Hosts -- Communication Layers< /a>
* @see Socket#setKeepAlive
*/
public static final SocketOption<Boolean> SO_KEEPALIVE =
new SocketOption<Boolean>("SO_KEEPALIVE", Boolean.class);
/**
* The size of the socket send buffer.
*
* <p>
* The value of this socket option is an {@code Integer} that is the size of the socket send
* buffer in bytes. The socket send buffer is an output buffer used by the networking
* implementation. It may need to be increased for high-volume connections. The value of the
* socket option is a <em>hint</em> to the implementation to size the buffer and the actual size
* may differ. The socket option can be queried to retrieve the actual size.
*
* <p>
* For datagram-oriented sockets, the size of the send buffer may limit the size of the datagrams
* that may be sent by the socket. Whether datagrams larger than the buffer size are sent or
* discarded is system dependent.
*
* <p>
* The initial/default size of the socket send buffer and the range of allowable values is system
* dependent although a negative size is not allowed. An attempt to set the socket send buffer to
* larger than its maximum size causes it to be set to its maximum size.
*
* <p>
* An implementation allows this socket option to be set before the socket is bound or connected.
* Whether an implementation allows the socket send buffer to be changed after the socket is bound
* is system dependent.
*
* @see Socket#setSendBufferSize
*/
public static final SocketOption<Integer> SO_SNDBUF =
new SocketOption<Integer>("SO_SNDBUF", Integer.class);
/**
* The size of the socket receive buffer.
*
* <p>
* The value of this socket option is an {@code Integer} that is the size of the socket receive
* buffer in bytes. The socket receive buffer is an input buffer used by the networking
* implementation. It may need to be increased for high-volume connections or decreased to limit
* the possible backlog of incoming data. The value of the socket option is a <em>hint</em> to the
* implementation to size the buffer and the actual size may differ.
*
* <p>
* For datagram-oriented sockets, the size of the receive buffer may limit the size of the
* datagrams that can be received. Whether datagrams larger than the buffer size can be received
* is system dependent. Increasing the socket receive buffer may be important for cases where
* datagrams arrive in bursts faster than they can be processed.
*
* <p>
* In the case of stream-oriented sockets and the TCP/IP protocol, the size of the socket receive
* buffer may be used when advertising the size of the TCP receive window to the remote peer.
*
* <p>
* The initial/default size of the socket receive buffer and the range of allowable values is
* system dependent although a negative size is not allowed. An attempt to set the socket receive
* buffer to larger than its maximum size causes it to be set to its maximum size.
*
* <p>
* An implementation allows this socket option to be set before the socket is bound or connected.
* Whether an implementation allows the socket receive buffer to be changed after the socket is
* bound is system dependent.
*
* @see <a href="http://www.ietf.org/rfc/rfc1323.txt">RFC&nbsp;1323: TCP * Extensions for High
* Performance< /a>
* @see Socket#setReceiveBufferSize
* @see ServerSocket#setReceiveBufferSize
*/
public static final SocketOption<Integer> SO_RCVBUF =
new SocketOption<Integer>("SO_RCVBUF", Integer.class);
/**
* Re-use address.
*
* <p>
* The value of this socket option is a {@code Boolean} that represents whether the option is
* enabled or disabled. The exact semantics of this socket option are socket type and system
* dependent.
*
* <p>
* In the case of stream-oriented sockets, this socket option will usually determine whether the
* socket can be bound to a socket address when a previous connection involving that socket
* address is in the <em>TIME_WAIT</em> state. On implementations where the semantics differ, and
* the socket option is not required to be enabled in order to bind the socket when a previous
* connection is in this state, then the implementation may choose to ignore this option.
*
* <p>
* For datagram-oriented sockets the socket option is used to allow multiple programs bind to the
* same address. This option should be enabled when the socket is to be used for Internet Protocol
* (IP) multicasting.
*
* <p>
* An implementation allows this socket option to be set before the socket is bound or connected.
* Changing the value of this socket option after the socket is bound has no effect. The default
* value of this socket option is system dependent.
*
* @see <a href="http://www.ietf.org/rfc/rfc793.txt">RFC&nbsp;793: * Transmission Control
* Protocol< /a>
* @see ServerSocket#setReuseAddress
*/
public static final SocketOption<Boolean> SO_REUSEADDR =
new SocketOption<Boolean>("SO_REUSEADDR", Boolean.class);
/**
* Linger on close if data is present.
*
* <p>
* The value of this socket option is an {@code Integer} that controls the action taken when
* unsent data is queued on the socket and a method to close the socket is invoked. If the value
* of the socket option is zero or greater, then it represents a timeout value, in seconds, known
* as the <em>linger interval</em>. The linger interval is the timeout for the {@code close}
* method to block while the operating system attempts to transmit the unsent data or it decides
* that it is unable to transmit the data. If the value of the socket option is less than zero
* then the option is disabled. In that case the {@code close} method does not wait until unsent
* data is transmitted; if possible the operating system will transmit any unsent data before the
* connection is closed.
*
* <p>
* This socket option is intended for use with sockets that are configured in
* {@link java.nio.channels.SelectableChannel#isBlocking() blocking} mode only. The behavior of
* the {@code close} method when this option is enabled on a non-blocking socket is not defined.
*
* <p>
* The initial value of this socket option is a negative value, meaning that the option is
* disabled. The option may be enabled, or the linger interval changed, at any time. The maximum
* value of the linger interval is system dependent. Setting the linger interval to a value that
* is greater than its maximum value causes the linger interval to be set to its maximum value.
*
* @see Socket#setSoLinger
*/
public static final SocketOption<Integer> SO_LINGER =
new SocketOption<Integer>("SO_LINGER", Integer.class);
/**
* Disable the Nagle algorithm.
*
* <p>
* The value of this socket option is a {@code Boolean} that represents whether the option is
* enabled or disabled. The socket option is specific to stream-oriented sockets using the TCP/IP
* protocol. TCP/IP uses an algorithm known as <em>The Nagle Algorithm</em> to coalesce short
* segments and improve network efficiency.
*
* <p>
* The default value of this socket option is {@code FALSE}. The socket option should only be
* enabled in cases where it is known that the coalescing impacts performance. The socket option
* may be enabled at any time. In other words, the Nagle Algorithm can be disabled. Once the
* option is enabled, it is system dependent whether it can be subsequently disabled. If it
* cannot, then invoking the {@code setOption} method to disable the option has no effect.
*
* @see <a href="http://www.ietf.org/rfc/rfc1122.txt">RFC&nbsp;1122: * Requirements for Internet
* Hosts -- Communication Layers< /a>
* @see Socket#setTcpNoDelay
*/
public static final SocketOption<Boolean> TCP_NODELAY =
new SocketOption<Boolean>("TCP_NODELAY", Boolean.class);
}

View File

@@ -0,0 +1,117 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.util.ByteBufferMatcher;
import com.google.code.yanf4j.util.ShiftAndByteBufferMatcher;
/**
* Text line codec factory
*
* @author dennis
*
*/
public class TextLineCodecFactory implements CodecFactory {
public static final IoBuffer SPLIT = IoBuffer.wrap("\r\n".getBytes());
private static final ByteBufferMatcher SPLIT_PATTERN = new ShiftAndByteBufferMatcher(SPLIT);
public static final String DEFAULT_CHARSET_NAME = "utf-8";
private Charset charset;
public TextLineCodecFactory() {
this.charset = Charset.forName(DEFAULT_CHARSET_NAME);
}
public TextLineCodecFactory(String charsetName) {
this.charset = Charset.forName(charsetName);
}
class StringDecoder implements Decoder {
public Object decode(IoBuffer buffer, Session session) {
String result = null;
int index = SPLIT_PATTERN.matchFirst(buffer);
if (index >= 0) {
int limit = buffer.limit();
buffer.limit(index);
CharBuffer charBuffer = TextLineCodecFactory.this.charset.decode(buffer.buf());
result = charBuffer.toString();
buffer.limit(limit);
buffer.position(index + SPLIT.remaining());
}
return result;
}
}
private Decoder decoder = new StringDecoder();
public Decoder getDecoder() {
return this.decoder;
}
class StringEncoder implements Encoder {
public IoBuffer encode(Object msg, Session session) {
if (msg == null) {
return null;
}
String message = (String) msg;
ByteBuffer buff = TextLineCodecFactory.this.charset.encode(message);
byte[] bs = new byte[buff.remaining() + SPLIT.remaining()];
int len = buff.remaining();
System.arraycopy(buff.array(), buff.position(), bs, 0, len);
bs[len] = 13; // \r
bs[len + 1] = 10; // \n
IoBuffer resultBuffer = IoBuffer.wrap(bs);
return resultBuffer;
}
}
private Encoder encoder = new StringEncoder();
public Encoder getEncoder() {
return this.encoder;
}
// public static void main(String args[]) {
// TextLineCodecFactory codecFactory = new TextLineCodecFactory();
// Encoder encoder = codecFactory.getEncoder();
// long sum = 0;
// for (int i = 0; i < 100000; i++) {
// sum += encoder.encode("hello", null).remaining();
// }
//
// long start = System.currentTimeMillis();
//
// for (int i = 0; i < 10000000; i++) {
// sum += encoder.encode("hello", null).remaining();
// }
// long cost = System.currentTimeMillis() - start;
// System.out.println("sum=" + sum + ",cost = " + cost + " ms.");
// }
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.core.impl;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.WriteMessage;
/**
* Write message implementation with a buffer
*
* @author dennis
*
*/
public class WriteMessageImpl implements WriteMessage {
protected Object message;
protected IoBuffer buffer;
protected FutureImpl<Boolean> writeFuture;
protected boolean writing;
public final void writing() {
this.writing = true;
}
public final boolean isWriting() {
return this.writing;
}
public WriteMessageImpl(Object message, FutureImpl<Boolean> writeFuture) {
this.message = message;
this.writeFuture = writeFuture;
}
/*
* (non-Javadoc)
*
* @see com.google.code.yanf4j.nio.IWriteMessage#getBuffers()
*/
public synchronized final IoBuffer getWriteBuffer() {
return this.buffer;
}
public synchronized final void setWriteBuffer(IoBuffer buffers) {
this.buffer = buffers;
}
public final FutureImpl<Boolean> getWriteFuture() {
return this.writeFuture;
}
/*
* (non-Javadoc)
*
* @see com.google.code.yanf4j.nio.IWriteMessage#getMessage()
*/
public final Object getMessage() {
return this.message;
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>Networking core package</title>
</head>
<body>
<h1>Networking core package</h1>
</body>
</html>

View File

@@ -0,0 +1,52 @@
/**
* Copyright [2009-2010] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.nio;
import java.nio.channels.SelectableChannel;
import java.nio.channels.Selector;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Session;
/**
* Nio connection
*
* @author dennis
*
*/
public interface NioSession extends Session {
/**
* When io event occured
*
* @param event
* @param selector
*/
public void onEvent(EventType event, Selector selector);
/**
* Enable read event
*
* @param selector
*/
public void enableRead(Selector selector);
/**
* Enable write event
*
* @param selector
*/
public void enableWrite(Selector selector);
/**
* return the channel for this connection
*
* @return
*/
public SelectableChannel channel();
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.nio;
import java.nio.channels.SelectableChannel;
import java.util.Queue;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Dispatcher;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.SessionConfig;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.nio.impl.SelectorManager;
import com.google.code.yanf4j.statistics.Statistics;
/**
* Nio session configuration
*
* @author dennis
*
*/
public class NioSessionConfig extends SessionConfig {
public final SelectableChannel selectableChannel;
public final SelectorManager selectorManager;
public NioSessionConfig(SelectableChannel sc, Handler handler, SelectorManager reactor,
CodecFactory codecFactory, Statistics statistics, Queue<WriteMessage> queue,
Dispatcher dispatchMessageDispatcher, boolean handleReadWriteConcurrently,
long sessionTimeout, long sessionIdleTimeout) {
super(handler, codecFactory, statistics, queue, dispatchMessageDispatcher,
handleReadWriteConcurrently, sessionTimeout, sessionIdleTimeout);
this.selectableChannel = sc;
this.selectorManager = reactor;
}
}

View File

@@ -0,0 +1,33 @@
package com.google.code.yanf4j.nio;
/**
* Copyright [2008-2009] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
/**
* SelectionKey handler
*
* @author dennis
*
*/
public interface SelectionKeyHandler {
public void onAccept(SelectionKey sk) throws IOException;
public void closeSelectionKey(SelectionKey key);
public void onWrite(SelectionKey key);
public void onRead(SelectionKey key);
public void onConnect(SelectionKey key) throws IOException;
public void closeChannel(Selector selector) throws IOException;
}

View File

@@ -0,0 +1,165 @@
/**
* Copyright [2008-2009] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.impl.StandardSocketOption;
import com.google.code.yanf4j.nio.impl.SocketChannelController;
/**
* Controller for tcp server
*
* @author dennis
*/
public class TCPController extends SocketChannelController {
private ServerSocketChannel serverSocketChannel;
/**
* Accept backlog queue size
*/
private int backlog = 500; // default 500
public int getBacklog() {
return this.backlog;
}
public void setBacklog(int backlog) {
if (isStarted()) {
throw new IllegalStateException();
}
if (backlog < 0) {
throw new IllegalArgumentException("backlog<0");
}
this.backlog = backlog;
}
public TCPController() {
super();
}
public TCPController(Configuration configuration) {
super(configuration, null, null);
}
public TCPController(Configuration configuration, CodecFactory codecFactory) {
super(configuration, null, codecFactory);
}
public TCPController(Configuration configuration, Handler handler, CodecFactory codecFactory) {
super(configuration, handler, codecFactory);
}
private int connectionTime, latency, bandwidth;
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
this.connectionTime = connectionTime;
this.latency = latency;
this.bandwidth = bandwidth;
}
@Override
protected void doStart() throws IOException {
this.serverSocketChannel = ServerSocketChannel.open();
this.serverSocketChannel.socket().setSoTimeout(this.soTimeout);
if (this.connectionTime != 0 || this.latency != 0 || this.bandwidth != 0) {
this.serverSocketChannel.socket().setPerformancePreferences(this.connectionTime, this.latency,
this.bandwidth);
}
this.serverSocketChannel.configureBlocking(false);
if (this.socketOptions.get(StandardSocketOption.SO_REUSEADDR) != null) {
this.serverSocketChannel.socket().setReuseAddress(StandardSocketOption.SO_REUSEADDR.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_REUSEADDR)));
}
if (this.socketOptions.get(StandardSocketOption.SO_RCVBUF) != null) {
this.serverSocketChannel.socket().setReceiveBufferSize(StandardSocketOption.SO_RCVBUF.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_RCVBUF)));
}
if (this.localSocketAddress != null) {
this.serverSocketChannel.socket().bind(this.localSocketAddress, this.backlog);
} else {
this.serverSocketChannel.socket().bind(new InetSocketAddress("localhost", 0), this.backlog);
}
setLocalSocketAddress(
(InetSocketAddress) this.serverSocketChannel.socket().getLocalSocketAddress());
this.selectorManager.registerChannel(this.serverSocketChannel, SelectionKey.OP_ACCEPT, null);
}
@Override
public void onAccept(SelectionKey selectionKey) throws IOException {
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>رգ<D8B1>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
if (!this.serverSocketChannel.isOpen()) {
selectionKey.cancel();
return;
}
SocketChannel sc = null;
try {
sc = this.serverSocketChannel.accept();
if (sc != null) {
configureSocketChannel(sc);
Session session = buildSession(sc);
// enable read
this.selectorManager.registerSession(session, EventType.ENABLE_READ);
session.start();
super.onAccept(selectionKey); // for statistics
} else {
log.debug("Accept fail");
}
} catch (IOException e) {
closeAcceptChannel(selectionKey, sc);
log.error("Accept connection error", e);
notifyException(e);
}
}
/**
*
* @param sk
* @param sc
* @throws IOException
* @throws SocketException
*/
private void closeAcceptChannel(SelectionKey sk, SocketChannel sc)
throws IOException, SocketException {
if (sk != null) {
sk.cancel();
}
if (sc != null) {
sc.socket().setSoLinger(true, 0); // await TIME_WAIT status
sc.socket().shutdownOutput();
sc.close();
}
}
public void closeChannel(Selector selector) throws IOException {
if (this.serverSocketChannel != null) {
this.serverSocketChannel.close();
}
}
public void unbind() throws IOException {
stop();
}
}

View File

@@ -0,0 +1,343 @@
package com.google.code.yanf4j.nio.impl;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.core.impl.AbstractSession;
import com.google.code.yanf4j.nio.NioSession;
import com.google.code.yanf4j.nio.NioSessionConfig;
import com.google.code.yanf4j.util.SelectorFactory;
/**
* Abstract nio session
*
* @author dennis
*
*/
public abstract class AbstractNioSession extends AbstractSession implements NioSession {
public SelectableChannel channel() {
return selectableChannel;
}
protected SelectorManager selectorManager;
protected SelectableChannel selectableChannel;
public AbstractNioSession(NioSessionConfig sessionConfig) {
super(sessionConfig);
selectorManager = sessionConfig.selectorManager;
selectableChannel = sessionConfig.selectableChannel;
}
public final void enableRead(Selector selector) {
SelectionKey key = selectableChannel.keyFor(selector);
if (key != null && key.isValid()) {
interestRead(key);
} else {
try {
selectableChannel.register(selector, SelectionKey.OP_READ, this);
} catch (ClosedChannelException e) {
// ignore
} catch (CancelledKeyException e) {
// ignore
}
}
}
private void interestRead(SelectionKey key) {
if (key.attachment() == null) {
key.attach(this);
}
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
}
@Override
protected void start0() {
registerSession();
}
public InetAddress getLocalAddress() {
return ((SocketChannel) selectableChannel).socket().getLocalAddress();
}
protected abstract Object writeToChannel(WriteMessage msg)
throws ClosedChannelException, IOException;
protected void onWrite(SelectionKey key) {
boolean isLockedByMe = false;
if (currentMessage.get() == null) {
// get next message
WriteMessage nextMessage = writeQueue.peek();
if (nextMessage != null && writeLock.tryLock()) {
if (!writeQueue.isEmpty() && currentMessage.compareAndSet(null, nextMessage)) {
writeQueue.remove();
}
} else {
return;
}
} else if (!writeLock.tryLock()) {
return;
}
isLockedByMe = true;
WriteMessage currentMessage = null;
final long maxWritten = readBuffer.capacity() + readBuffer.capacity() >>> 1;
try {
long written = 0;
while (this.currentMessage.get() != null) {
currentMessage = this.currentMessage.get();
currentMessage = preprocessWriteMessage(currentMessage);
this.currentMessage.set(currentMessage);
long before = this.currentMessage.get().getWriteBuffer().remaining();
Object writeResult = null;
if (written < maxWritten) {
writeResult = writeToChannel(currentMessage);
written += this.currentMessage.get().getWriteBuffer().remaining() - before;
} else {
// wait for next time to write
}
// write complete
if (writeResult != null) {
this.currentMessage.set(writeQueue.poll());
handler.onMessageSent(this, currentMessage.getMessage());
// try to get next message
if (this.currentMessage.get() == null) {
if (isLockedByMe) {
isLockedByMe = false;
writeLock.unlock();
}
// get next message
WriteMessage nextMessage = writeQueue.peek();
if (nextMessage != null && writeLock.tryLock()) {
isLockedByMe = true;
if (!writeQueue.isEmpty() && this.currentMessage.compareAndSet(null, nextMessage)) {
writeQueue.remove();
}
continue;
} else {
break;
}
}
} else {
// does't write complete
if (isLockedByMe) {
isLockedByMe = false;
writeLock.unlock();
}
// register OP_WRITE event
selectorManager.registerSession(this, EventType.ENABLE_WRITE);
break;
}
}
} catch (IOException e) {
handler.onExceptionCaught(this, e);
if (currentMessage != null && currentMessage.getWriteFuture() != null) {
currentMessage.getWriteFuture().failure(e);
}
if (isLockedByMe) {
isLockedByMe = false;
writeLock.unlock();
}
close();
} finally {
if (isLockedByMe) {
writeLock.unlock();
}
}
}
public final void enableWrite(Selector selector) {
SelectionKey key = selectableChannel.keyFor(selector);
if (key != null && key.isValid()) {
interestWrite(key);
} else {
try {
selectableChannel.register(selector, SelectionKey.OP_WRITE, this);
} catch (ClosedChannelException e) {
// ignore
} catch (CancelledKeyException e) {
// ignore
}
}
}
private void interestWrite(SelectionKey key) {
if (key.attachment() == null) {
key.attach(this);
}
key.interestOps(key.interestOps() | SelectionKey.OP_WRITE);
}
protected void onRead(SelectionKey key) {
readFromBuffer();
}
protected abstract void readFromBuffer();
@Override
protected void closeChannel() throws IOException {
flush0();
unregisterSession();
unregisterChannel();
}
protected final void unregisterChannel() throws IOException {
writeLock.lock();
try {
if (getAttribute(SelectorManager.REACTOR_ATTRIBUTE) != null) {
((Reactor) getAttribute(SelectorManager.REACTOR_ATTRIBUTE))
.unregisterChannel(selectableChannel);
}
if (selectableChannel.isOpen()) {
selectableChannel.close();
}
} finally {
writeLock.unlock();
}
}
protected final void registerSession() {
selectorManager.registerSession(this, EventType.REGISTER);
}
protected void unregisterSession() {
selectorManager.registerSession(this, EventType.UNREGISTER);
}
@Override
public void writeFromUserCode(WriteMessage message) {
if (schduleWriteMessage(message)) {
return;
}
onWrite(null);
}
protected boolean schduleWriteMessage(WriteMessage writeMessage) {
boolean offered = writeQueue.offer(writeMessage);
assert offered;
final Reactor reactor = selectorManager.getReactorFromSession(this);
if (Thread.currentThread() != reactor) {
selectorManager.registerSession(this, EventType.ENABLE_WRITE);
return true;
}
return false;
}
public void flush() {
if (isClosed()) {
return;
}
flush0();
}
protected final void flush0() {
SelectionKey tmpKey = null;
Selector writeSelector = null;
int attempts = 0;
try {
while (true) {
if (writeSelector == null) {
writeSelector = SelectorFactory.getSelector();
if (writeSelector == null) {
return;
}
tmpKey = selectableChannel.register(writeSelector, SelectionKey.OP_WRITE);
}
if (writeSelector.select(1000) == 0) {
attempts++;
if (attempts > 2) {
return;
}
} else {
break;
}
}
onWrite(selectableChannel.keyFor(writeSelector));
} catch (ClosedChannelException cce) {
onException(cce);
log.error("Flush error", cce);
close();
} catch (IOException ioe) {
onException(ioe);
log.error("Flush error", ioe);
close();
} finally {
if (tmpKey != null) {
// Cancel the key.
tmpKey.cancel();
tmpKey = null;
}
if (writeSelector != null) {
try {
writeSelector.selectNow();
} catch (IOException e) {
log.error("Temp selector selectNow error", e);
}
// return selector
SelectorFactory.returnSelector(writeSelector);
}
}
}
protected final long doRealWrite(SelectableChannel channel, IoBuffer buffer) throws IOException {
if (log.isDebugEnabled()) {
StringBuffer bufMsg = new StringBuffer("send buffers:\n[\n");
final ByteBuffer buff = buffer.buf();
bufMsg.append(" buffer:position=").append(buff.position()).append(",limit=")
.append(buff.limit()).append(",capacity=").append(buff.capacity()).append("\n");
bufMsg.append("]");
log.debug(bufMsg.toString());
}
return ((WritableByteChannel) channel).write(buffer.buf());
}
/**
* <20>ɷ<EFBFBD>IO<49>¼<EFBFBD>
*/
public final void onEvent(EventType event, Selector selector) {
if (isClosed()) {
return;
}
SelectionKey key = selectableChannel.keyFor(selector);
switch (event) {
case EXPIRED:
onExpired();
break;
case WRITEABLE:
onWrite(key);
break;
case READABLE:
onRead(key);
break;
case ENABLE_WRITE:
enableWrite(selector);
break;
case ENABLE_READ:
enableRead(selector);
break;
case IDLE:
onIdle();
break;
case CONNECTED:
onConnected();
break;
default:
log.error("Unknown event:" + event.name());
break;
}
}
}

View File

@@ -0,0 +1,226 @@
/**
* Copyright [2009-2010] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.nio.impl;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.util.Queue;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.core.impl.AbstractController;
import com.google.code.yanf4j.nio.NioSessionConfig;
import com.google.code.yanf4j.nio.SelectionKeyHandler;
import com.google.code.yanf4j.util.SystemUtils;
/**
* Base nio controller
*
* @author dennis
*
*/
public abstract class NioController extends AbstractController implements SelectionKeyHandler {
protected SelectorManager selectorManager;
/**
* Reactor count
*/
protected int selectorPoolSize = SystemUtils.getSystemThreadCount();
/**
* @see setSelectorPoolSize
* @return
*/
public int getSelectorPoolSize() {
return this.selectorPoolSize;
}
public void setSelectorPoolSize(int selectorPoolSize) {
if (isStarted()) {
throw new IllegalStateException("Controller has been started");
}
this.selectorPoolSize = selectorPoolSize;
}
public NioController() {
super();
}
public NioController(Configuration configuration, CodecFactory codecFactory) {
super(configuration, codecFactory);
}
public NioController(Configuration configuration, Handler handler, CodecFactory codecFactory) {
super(configuration, handler, codecFactory);
}
public NioController(Configuration configuration) {
super(configuration);
}
/**
* Write task
*
* @author dennis
*
*/
private final class WriteTask implements Runnable {
private final SelectionKey key;
private WriteTask(SelectionKey key) {
this.key = key;
}
public final void run() {
dispatchWriteEvent(this.key);
}
}
/**
* Read task
*
* @author dennis
*
*/
private final class ReadTask implements Runnable {
private final SelectionKey key;
private ReadTask(SelectionKey key) {
this.key = key;
}
public final void run() {
dispatchReadEvent(this.key);
}
}
public final SelectorManager getSelectorManager() {
return this.selectorManager;
}
@Override
protected void start0() throws IOException {
try {
initialSelectorManager();
doStart();
} catch (IOException e) {
log.error("Start server error", e);
notifyException(e);
stop();
throw e;
}
}
/**
* Start selector manager
*
* @throws IOException
*/
protected void initialSelectorManager() throws IOException {
if (this.selectorManager == null) {
this.selectorManager = new SelectorManager(this.selectorPoolSize, this, this.configuration);
this.selectorManager.start();
}
}
/**
* Inner startup
*
* @throws IOException
*/
protected abstract void doStart() throws IOException;
/**
* Read event occured
*/
public void onRead(SelectionKey key) {
if (this.readEventDispatcher == null) {
dispatchReadEvent(key);
} else {
this.readEventDispatcher.dispatch(new ReadTask(key));
}
}
/**
* Writable event occured
*/
public void onWrite(final SelectionKey key) {
if (this.writeEventDispatcher == null) {
dispatchWriteEvent(key);
} else {
this.writeEventDispatcher.dispatch(new WriteTask(key));
}
}
/**
* Cancel selection key
*/
public void closeSelectionKey(SelectionKey key) {
if (key.attachment() instanceof Session) {
Session session = (Session) key.attachment();
if (session != null) {
session.close();
}
}
}
/**
* Dispatch read event
*
* @param key
* @return
*/
protected abstract void dispatchReadEvent(final SelectionKey key);
/**
* Dispatch write event
*
* @param key
* @return
*/
protected abstract void dispatchWriteEvent(final SelectionKey key);
@Override
protected void stop0() throws IOException {
if (this.selectorManager == null || !this.selectorManager.isStarted()) {
return;
}
this.selectorManager.stop();
}
public synchronized void bind(int port) throws IOException {
if (isStarted()) {
throw new IllegalStateException("Server has been bind to " + getLocalSocketAddress());
}
bind(new InetSocketAddress(port));
}
/**
* Build nio session config
*
* @param sc
* @param queue
* @return
*/
protected final NioSessionConfig buildSessionConfig(SelectableChannel sc,
Queue<WriteMessage> queue) {
final NioSessionConfig sessionConfig =
new NioSessionConfig(sc, getHandler(), this.selectorManager, getCodecFactory(),
getStatistics(), queue, this.dispatchMessageDispatcher, isHandleReadWriteConcurrently(),
this.sessionTimeout, this.configuration.getSessionIdleTimeout());
return sessionConfig;
}
}

View File

@@ -0,0 +1,327 @@
package com.google.code.yanf4j.nio.impl;
/**
* Copyright [2008-2009] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License. You may obtain a
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
* law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
* for the specific language governing permissions and limitations under the License
*/
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.concurrent.Future;
import com.google.code.yanf4j.buffer.IoBuffer;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.core.impl.FutureImpl;
import com.google.code.yanf4j.core.impl.WriteMessageImpl;
import com.google.code.yanf4j.nio.NioSessionConfig;
import com.google.code.yanf4j.util.ByteBufferUtils;
import com.google.code.yanf4j.util.SelectorFactory;
/**
* Nio tcp connection
*
* @author dennis
*
*/
public class NioTCPSession extends AbstractNioSession {
private InetSocketAddress remoteAddress;
@Override
public final boolean isExpired() {
if (log.isDebugEnabled()) {
log.debug("sessionTimeout=" + this.sessionTimeout + ",this.timestamp="
+ this.lastOperationTimeStamp.get() + ",current=" + System.currentTimeMillis());
}
return this.sessionTimeout <= 0 ? false
: System.currentTimeMillis() - this.lastOperationTimeStamp.get() >= this.sessionTimeout;
}
public NioTCPSession(NioSessionConfig sessionConfig, int readRecvBufferSize) {
super(sessionConfig);
if (this.selectableChannel != null && this.getRemoteSocketAddress() != null) {
this.loopback = this.getRemoteSocketAddress().getAddress().isLoopbackAddress();
}
this.setReadBuffer(IoBuffer.allocate(readRecvBufferSize));
this.onCreated();
}
@Override
protected Object writeToChannel(WriteMessage message) throws IOException {
if (message.getWriteFuture() != null && !message.isWriting()
&& message.getWriteFuture().isCancelled()) {
return message.getMessage();
}
if (message.getWriteBuffer() == null) {
if (message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
return message.getMessage();
}
IoBuffer writeBuffer = message.getWriteBuffer();
// begin writing
message.writing();
if (this.useBlockingWrite) {
return this.blockingWrite(this.selectableChannel, message, writeBuffer);
} else {
while (true) {
long n = this.doRealWrite(this.selectableChannel, writeBuffer);
if (n > 0) {
this.statistics.statisticsWrite(n);
this.scheduleWritenBytes.addAndGet(0 - n);
}
if (writeBuffer == null || !writeBuffer.hasRemaining()) {
if (message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
return message.getMessage();
} else if (n == 0) {
// have more data, but the buffer is full,
// wait next time to write
return null;
}
}
}
}
public InetSocketAddress getRemoteSocketAddress() {
if (this.remoteAddress == null) {
this.remoteAddress = (InetSocketAddress) ((SocketChannel) this.selectableChannel).socket()
.getRemoteSocketAddress();
}
return this.remoteAddress;
}
/**
* Blocking write using temp selector
*
* @param channel
* @param message
* @param writeBuffer
* @return
* @throws IOException
* @throws ClosedChannelException
*/
protected final Object blockingWrite(SelectableChannel channel, WriteMessage message,
IoBuffer writeBuffer) throws IOException, ClosedChannelException {
SelectionKey tmpKey = null;
Selector writeSelector = null;
int attempts = 0;
int bytesProduced = 0;
try {
while (writeBuffer.hasRemaining()) {
long len = this.doRealWrite(channel, writeBuffer);
if (len > 0) {
attempts = 0;
bytesProduced += len;
this.statistics.statisticsWrite(len);
} else {
attempts++;
if (writeSelector == null) {
writeSelector = SelectorFactory.getSelector();
if (writeSelector == null) {
// Continue using the main one.
continue;
}
tmpKey = channel.register(writeSelector, SelectionKey.OP_WRITE);
}
if (writeSelector.select(1000) == 0) {
if (attempts > 2) {
throw new IOException("Client disconnected");
}
}
}
}
if (!writeBuffer.hasRemaining() && message.getWriteFuture() != null) {
message.getWriteFuture().setResult(Boolean.TRUE);
}
} finally {
if (tmpKey != null) {
tmpKey.cancel();
tmpKey = null;
}
if (writeSelector != null) {
// Cancel the key.
writeSelector.selectNow();
SelectorFactory.returnSelector(writeSelector);
}
}
this.scheduleWritenBytes.addAndGet(0 - bytesProduced);
return message.getMessage();
}
@Override
protected WriteMessage wrapMessage(Object msg, Future<Boolean> writeFuture) {
WriteMessage message = new WriteMessageImpl(msg, (FutureImpl<Boolean>) writeFuture);
if (message.getWriteBuffer() == null) {
message.setWriteBuffer(this.encoder.encode(message.getMessage(), this));
}
return message;
}
@Override
protected void readFromBuffer() {
if (!this.readBuffer.hasRemaining()) {
if (this.readBuffer.capacity() < Configuration.MAX_READ_BUFFER_SIZE) {
this.readBuffer =
IoBuffer.wrap(ByteBufferUtils.increaseBufferCapatity(this.readBuffer.buf()));
} else {
// buffer's capacity is greater than maxium
return;
}
}
if (this.closed) {
return;
}
int n = -1;
int readCount = 0;
try {
while ((n = ((ReadableByteChannel) this.selectableChannel).read(this.readBuffer.buf())) > 0) {
readCount += n;
}
if (readCount > 0) {
decodeAndDispatch();
} else if (readCount == 0
&& !((SocketChannel) this.selectableChannel).socket().isInputShutdown()
&& this.useBlockingRead) {
n = this.blockingRead();
if (n > 0) {
readCount += n;
}
}
if (n < 0) { // Connection closed
this.close();
} else {
this.selectorManager.registerSession(this, EventType.ENABLE_READ);
}
if (log.isDebugEnabled()) {
log.debug("read " + readCount + " bytes from channel");
}
} catch (ClosedChannelException e) {
// ignore exception
this.close();
} catch (Throwable e) {
this.onException(e);
this.close();
}
}
private void decodeAndDispatch() {
updateTimeStamp();
this.readBuffer.flip();
this.decode();
this.readBuffer.compact();
}
/**
* Blocking read using temp selector
*
* @return
* @throws ClosedChannelException
* @throws IOException
*/
protected final int blockingRead() throws ClosedChannelException, IOException {
int n = 0;
int readCount = 0;
Selector readSelector = SelectorFactory.getSelector();
SelectionKey tmpKey = null;
try {
if (this.selectableChannel.isOpen()) {
tmpKey = this.selectableChannel.register(readSelector, 0);
tmpKey.interestOps(tmpKey.interestOps() | SelectionKey.OP_READ);
int code = readSelector.select(500);
tmpKey.interestOps(tmpKey.interestOps() & ~SelectionKey.OP_READ);
if (code > 0) {
do {
n = ((ReadableByteChannel) this.selectableChannel).read(this.readBuffer.buf());
readCount += n;
if (log.isDebugEnabled()) {
log.debug("use temp selector read " + n + " bytes");
}
} while (n > 0 && this.readBuffer.hasRemaining());
if (readCount > 0) {
decodeAndDispatch();
}
}
}
} finally {
if (tmpKey != null) {
tmpKey.cancel();
tmpKey = null;
}
if (readSelector != null) {
// Cancel the key.
readSelector.selectNow();
SelectorFactory.returnSelector(readSelector);
}
}
return readCount;
}
/**
* Decode buffer
*/
@Override
public void decode() {
Object message;
int size = this.readBuffer.remaining();
while (this.readBuffer.hasRemaining()) {
try {
message = this.decoder.decode(this.readBuffer, this);
if (message == null) {
break;
} else {
if (this.statistics.isStatistics()) {
this.statistics.statisticsRead(size - this.readBuffer.remaining());
size = this.readBuffer.remaining();
}
}
this.dispatchReceivedMessage(message);
} catch (Exception e) {
this.onException(e);
log.error("Decode error", e);
super.close();
break;
}
}
}
public Socket socket() {
return ((SocketChannel) this.selectableChannel).socket();
}
@Override
protected final void closeChannel() throws IOException {
this.flush0();
// try to close output first
Socket socket = ((SocketChannel) this.selectableChannel).socket();
try {
if (!socket.isClosed() && !socket.isOutputShutdown()) {
socket.shutdownOutput();
}
if (!socket.isClosed() && !socket.isInputShutdown()) {
socket.shutdownInput();
}
} catch (Exception e) {
}
try {
socket.close();
} catch (Exception e) {
}
this.unregisterSession();
this.unregisterChannel();
}
}

View File

@@ -0,0 +1,544 @@
/*
*
*/
package com.google.code.yanf4j.nio.impl;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.ClosedSelectorException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.util.Date;
import java.util.Iterator;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.nio.NioSession;
import com.google.code.yanf4j.util.SystemUtils;
/**
* Reactor pattern
*
* @author dennis
*
*/
public final class Reactor extends Thread {
/**
* JVM bug threshold
*/
public static final int JVMBUG_THRESHHOLD =
Integer.getInteger("com.googlecode.yanf4j.nio.JVMBUG_THRESHHOLD", 128);
public static final int JVMBUG_THRESHHOLD2 = JVMBUG_THRESHHOLD * 2;
public static final int JVMBUG_THRESHHOLD1 = (JVMBUG_THRESHHOLD2 + JVMBUG_THRESHHOLD) / 2;
public static final int DEFAULT_WAIT = 1000;
private static final Logger log = LoggerFactory.getLogger("remoting");
private boolean jvmBug0;
private boolean jvmBug1;
private final int reactorIndex;
private final SelectorManager selectorManager;
private final AtomicInteger jvmBug = new AtomicInteger(0);
private long lastJVMBug;
private Selector selector;
private final NioController controller;
private final Configuration configuration;
static public class PaddingAtomicBoolean extends AtomicBoolean {
/**
*
*/
private static final long serialVersionUID = 5227571972657902891L;
public int p1;
public long p2, p3, p4, p5, p6, p7, p8;
PaddingAtomicBoolean(boolean v) {
super(v);
}
}
private final AtomicBoolean wakenUp = new PaddingAtomicBoolean(false);
public static class RegisterEvent {
SelectableChannel channel;
int ops;
EventType eventType;
Object attachment;
Session session;
public RegisterEvent(SelectableChannel channel, int ops, Object attachment) {
super();
this.channel = channel;
this.ops = ops;
this.attachment = attachment;
}
public RegisterEvent(Session session, EventType eventType) {
super();
this.session = session;
this.eventType = eventType;
}
}
private Queue<RegisterEvent> register;
private final Lock gate = new ReentrantLock();
private int selectTries = 0;
private long nextTimeout = 0;
private long lastCheckTimestamp = 0L;
Reactor(SelectorManager selectorManager, Configuration configuration, int index)
throws IOException {
super();
reactorIndex = index;
this.register = (Queue<Reactor.RegisterEvent>) SystemUtils.createTransferQueue();
this.selectorManager = selectorManager;
controller = selectorManager.getController();
selector = SystemUtils.openSelector();
this.configuration = configuration;
setName("Xmemcached-Reactor-" + index);
setDaemon(true);
}
public final Selector getSelector() {
return selector;
}
public int getReactorIndex() {
return reactorIndex;
}
@Override
public void run() {
selectorManager.notifyReady();
while (selectorManager.isStarted() && selector.isOpen()) {
try {
beforeSelect();
wakenUp.set(false);
long before = -1;
// Wether to look jvm bug
if (SystemUtils.isLinuxPlatform() && !SystemUtils.isAfterJava6u4Version()) {
before = System.currentTimeMillis();
}
long wait = DEFAULT_WAIT;
if (nextTimeout > 0) {
wait = nextTimeout;
}
int selected = selector.select(wait);
if (selected == 0) {
if (before != -1) {
lookJVMBug(before, selected, wait);
}
selectTries++;
// check timeout and idle
nextTimeout = checkSessionTimeout();
continue;
} else {
selectTries = 0;
}
} catch (ClosedSelectorException e) {
break;
} catch (IOException e) {
log.error("Reactor select error", e);
if (selector.isOpen()) {
continue;
} else {
break;
}
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
gate.lock();
try {
postSelect(selectedKeys, selector.keys());
dispatchEvent(selectedKeys);
} finally {
gate.unlock();
}
}
if (selector != null) {
if (selector.isOpen()) {
try {
controller.closeChannel(selector);
selector.close();
} catch (IOException e) {
controller.notifyException(e);
log.error("stop reactor error", e);
}
}
}
}
/**
* Look jvm bug
*
* @param before
* @param selected
* @param wait
* @return
* @throws IOException
*/
private boolean lookJVMBug(long before, int selected, long wait) throws IOException {
boolean seeing = false;
long now = System.currentTimeMillis();
if (JVMBUG_THRESHHOLD > 0 && selected == 0 && wait > JVMBUG_THRESHHOLD
&& now - before < wait / 4 && !wakenUp.get() /* waken up */
&& !Thread.currentThread().isInterrupted()/* Interrupted */) {
jvmBug.incrementAndGet();
if (jvmBug.get() >= JVMBUG_THRESHHOLD2) {
gate.lock();
try {
lastJVMBug = now;
log.warn("JVM bug occured at " + new Date(lastJVMBug)
+ ",http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6403933,reactIndex="
+ reactorIndex);
if (jvmBug1) {
log.debug("seeing JVM BUG(s) - recreating selector,reactIndex=" + reactorIndex);
} else {
jvmBug1 = true;
log.info("seeing JVM BUG(s) - recreating selector,reactIndex=" + reactorIndex);
}
seeing = true;
final Selector new_selector = SystemUtils.openSelector();
for (SelectionKey k : selector.keys()) {
if (!k.isValid() || k.interestOps() == 0) {
continue;
}
final SelectableChannel channel = k.channel();
final Object attachment = k.attachment();
channel.register(new_selector, k.interestOps(), attachment);
}
selector.close();
selector = new_selector;
} finally {
gate.unlock();
}
jvmBug.set(0);
} else if (jvmBug.get() == JVMBUG_THRESHHOLD || jvmBug.get() == JVMBUG_THRESHHOLD1) {
if (jvmBug0) {
log.debug("seeing JVM BUG(s) - cancelling interestOps==0,reactIndex=" + reactorIndex);
} else {
jvmBug0 = true;
log.info("seeing JVM BUG(s) - cancelling interestOps==0,reactIndex=" + reactorIndex);
}
gate.lock();
seeing = true;
try {
for (SelectionKey k : selector.keys()) {
if (k.isValid() && k.interestOps() == 0) {
k.cancel();
}
}
} finally {
gate.unlock();
}
}
} else {
jvmBug.set(0);
}
return seeing;
}
/**
* Dispatch selected event
*
* @param selectedKeySet
*/
public final void dispatchEvent(Set<SelectionKey> selectedKeySet) {
Iterator<SelectionKey> it = selectedKeySet.iterator();
boolean skipOpRead = false;
while (it.hasNext()) {
SelectionKey key = it.next();
it.remove();
if (!key.isValid()) {
if (key.attachment() != null) {
controller.closeSelectionKey(key);
} else {
key.cancel();
}
continue;
}
try {
if (key.isValid() && key.isAcceptable()) {
controller.onAccept(key);
continue;
}
if (key.isValid() && (key.readyOps() & SelectionKey.OP_WRITE) == SelectionKey.OP_WRITE) {
// Remove write interest
key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE);
controller.onWrite(key);
if (!controller.isHandleReadWriteConcurrently()) {
skipOpRead = true;
}
}
if (!skipOpRead && key.isValid()
&& (key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
key.interestOps(key.interestOps() & ~SelectionKey.OP_READ);
if (!controller.getStatistics().isReceiveOverFlow()) {
// Remove read interest
controller.onRead(key);
} else {
key.interestOps(key.interestOps() | SelectionKey.OP_READ);
}
}
if ((key.readyOps() & SelectionKey.OP_CONNECT) == SelectionKey.OP_CONNECT) {
controller.onConnect(key);
}
} catch (CancelledKeyException e) {
// ignore
} catch (RejectedExecutionException e) {
if (key.attachment() instanceof AbstractNioSession) {
((AbstractNioSession) key.attachment()).onException(e);
}
controller.notifyException(e);
if (selector.isOpen()) {
continue;
} else {
break;
}
} catch (Exception e) {
if (key.attachment() instanceof AbstractNioSession) {
((AbstractNioSession) key.attachment()).onException(e);
}
controller.closeSelectionKey(key);
controller.notifyException(e);
log.error("Reactor dispatch events error", e);
if (selector.isOpen()) {
continue;
} else {
break;
}
}
}
}
final void unregisterChannel(SelectableChannel channel) throws IOException {
Selector selector = this.selector;
if (selector != null) {
if (channel != null) {
SelectionKey key = channel.keyFor(selector);
if (key != null) {
key.cancel();
}
}
}
wakeup();
}
/**
* Check session timeout or idle
*
* @return
*/
private final long checkSessionTimeout() {
long nextTimeout = 0;
if (configuration.getCheckSessionTimeoutInterval() > 0) {
gate.lock();
try {
if (isNeedCheckSessionIdleTimeout()) {
nextTimeout = configuration.getCheckSessionTimeoutInterval();
for (SelectionKey key : selector.keys()) {
if (key.attachment() != null) {
long n = checkExpiredIdle(key, getSessionFromAttchment(key));
nextTimeout = n < nextTimeout ? n : nextTimeout;
}
}
selectTries = 0;
lastCheckTimestamp = System.currentTimeMillis();
}
} finally {
gate.unlock();
}
}
return nextTimeout;
}
private boolean isNeedCheckSessionIdleTimeout() {
return selectTries * 1000 >= configuration.getCheckSessionTimeoutInterval()
|| System.currentTimeMillis() - this.lastCheckTimestamp >= configuration
.getCheckSessionTimeoutInterval();
}
private final Session getSessionFromAttchment(SelectionKey key) {
if (key.attachment() instanceof Session) {
return (Session) key.attachment();
}
return null;
}
public final void registerSession(Session session, EventType event) {
final Selector selector = this.selector;
if (isReactorThread() && selector != null) {
dispatchSessionEvent(session, event);
} else {
register.offer(new RegisterEvent(session, event));
wakeup();
}
}
private final boolean isReactorThread() {
return Thread.currentThread() == this;
}
final void beforeSelect() {
controller.checkStatisticsForRestart();
processRegister();
}
private final void processRegister() {
RegisterEvent event = null;
while ((event = register.poll()) != null) {
if (event.session != null) {
dispatchSessionEvent(event.session, event.eventType);
} else {
registerChannelNow(event.channel, event.ops, event.attachment);
}
}
}
Configuration getConfiguration() {
return configuration;
}
private final void dispatchSessionEvent(Session session, EventType event) {
if (session.isClosed() && event != EventType.UNREGISTER) {
return;
}
switch (event) {
case REGISTER:
controller.registerSession(session);
break;
case UNREGISTER:
controller.unregisterSession(session);
break;
default:
((NioSession) session).onEvent(event, selector);
break;
}
}
public final void postSelect(Set<SelectionKey> selectedKeys, Set<SelectionKey> allKeys) {
if (controller.getSessionTimeout() > 0 || controller.getSessionIdleTimeout() > 0) {
if (isNeedCheckSessionIdleTimeout()) {
for (SelectionKey key : allKeys) {
if (!selectedKeys.contains(key)) {
if (key.attachment() != null) {
checkExpiredIdle(key, getSessionFromAttchment(key));
}
}
}
lastCheckTimestamp = System.currentTimeMillis();
}
}
}
private long checkExpiredIdle(SelectionKey key, Session session) {
if (session == null) {
return 0;
}
long nextTimeout = 0;
boolean expired = false;
if (controller.getSessionTimeout() > 0) {
expired = checkExpired(key, session);
nextTimeout = controller.getSessionTimeout();
}
if (controller.getSessionIdleTimeout() > 0 && !expired) {
checkIdle(session);
nextTimeout = controller.getSessionIdleTimeout();
}
return nextTimeout;
}
private final void checkIdle(Session session) {
if (controller.getSessionIdleTimeout() > 0) {
if (session.isIdle()) {
((NioSession) session).onEvent(EventType.IDLE, selector);
}
}
}
private final boolean checkExpired(SelectionKey key, Session session) {
if (session != null && session.isExpired()) {
((NioSession) session).onEvent(EventType.EXPIRED, selector);
controller.closeSelectionKey(key);
return true;
}
return false;
}
public final void registerChannel(SelectableChannel channel, int ops, Object attachment) {
if (isReactorThread()) {
registerChannelNow(channel, ops, attachment);
} else {
register.offer(new RegisterEvent(channel, ops, attachment));
wakeup();
}
}
private void registerChannelNow(SelectableChannel channel, int ops, Object attachment) {
if (channel.isOpen()) {
gate.lock();
try {
channel.register(selector, ops, attachment);
} catch (ClosedChannelException e) {
log.error("Register channel error", e);
controller.notifyException(e);
} finally {
gate.unlock();
}
}
}
final void wakeup() {
if (wakenUp.compareAndSet(false, true)) {
final Selector selector = this.selector;
if (selector != null) {
selector.wakeup();
}
}
}
final void selectNow() throws IOException {
final Selector selector = this.selector;
if (selector != null) {
selector.selectNow();
}
}
}

View File

@@ -0,0 +1,189 @@
package com.google.code.yanf4j.nio.impl;
import java.io.IOException;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.util.concurrent.atomic.AtomicInteger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Session;
/**
* Selector manager
*
* @author dennis
*
*/
public class SelectorManager {
private final Reactor[] reactorSet;
private final AtomicInteger sets = new AtomicInteger(0);
private final NioController controller;
private final int dividend;
/**
* Reactor count which are ready
*/
private int reactorReadyCount;
public SelectorManager(int selectorPoolSize, NioController controller, Configuration conf)
throws IOException {
if (selectorPoolSize <= 0) {
throw new IllegalArgumentException("selectorPoolSize<=0");
}
log.info("Creating " + selectorPoolSize + " reactors...");
reactorSet = new Reactor[selectorPoolSize];
this.controller = controller;
for (int i = 0; i < selectorPoolSize; i++) {
reactorSet[i] = new Reactor(this, conf, i);
}
dividend = reactorSet.length - 1;
}
private volatile boolean started;
public int getSelectorCount() {
return reactorSet == null ? 0 : reactorSet.length;
}
public synchronized void start() {
if (started) {
return;
}
started = true;
for (Reactor reactor : reactorSet) {
reactor.start();
}
}
Reactor getReactorFromSession(Session session) {
Reactor reactor = (Reactor) session.getAttribute(REACTOR_ATTRIBUTE);
if (reactor == null) {
reactor = nextReactor();
final Reactor oldReactor = (Reactor) session.setAttributeIfAbsent(REACTOR_ATTRIBUTE, reactor);
if (oldReactor != null) {
reactor = oldReactor;
}
}
return reactor;
}
/**
* Find reactor by index
*
* @param index
* @return
*/
public Reactor getReactorByIndex(int index) {
if (index < 0 || index > reactorSet.length - 1) {
throw new ArrayIndexOutOfBoundsException();
}
return reactorSet[index];
}
public synchronized void stop() {
if (!started) {
return;
}
started = false;
for (Reactor reactor : reactorSet) {
reactor.interrupt();
}
}
public static final String REACTOR_ATTRIBUTE = System.currentTimeMillis() + "_Reactor_Attribute";
/**
* Register channel
*
* @param channel
* @param ops
* @param attachment
* @return
*/
public final Reactor registerChannel(SelectableChannel channel, int ops, Object attachment) {
awaitReady();
int index = 0;
// Accept event used index 0 reactor
if (ops == SelectionKey.OP_ACCEPT || ops == SelectionKey.OP_CONNECT) {
index = 0;
} else {
index = sets.incrementAndGet() % dividend + 1;
}
final Reactor reactor = reactorSet[index];
reactor.registerChannel(channel, ops, attachment);
return reactor;
}
void awaitReady() {
synchronized (this) {
while (!started || reactorReadyCount != reactorSet.length) {
try {
this.wait(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();// reset interrupt status
}
}
}
}
/**
* Get next reactor
*
* @return
*/
public final Reactor nextReactor() {
if (dividend > 0) {
return reactorSet[sets.incrementAndGet() % dividend + 1];
} else {
return reactorSet[0];
}
}
/**
* Register session
*
* @param session
* @param event
*/
public final void registerSession(Session session, EventType event) {
if (session.isClosed() && event != EventType.UNREGISTER) {
return;
}
Reactor reactor = (Reactor) session.getAttribute(REACTOR_ATTRIBUTE);
if (reactor == null) {
reactor = nextReactor();
final Reactor oldReactor = (Reactor) session.setAttributeIfAbsent(REACTOR_ATTRIBUTE, reactor);
if (oldReactor != null) {
reactor = oldReactor;
}
}
reactor.registerSession(session, event);
}
public NioController getController() {
return controller;
}
/**
* Notify all reactor have been ready
*/
synchronized void notifyReady() {
reactorReadyCount++;
if (reactorReadyCount == reactorSet.length) {
controller.notifyReady();
notifyAll();
}
}
private static final Logger log = LoggerFactory.getLogger(SelectorManager.class);
public final boolean isStarted() {
return started;
}
}

View File

@@ -0,0 +1,115 @@
package com.google.code.yanf4j.nio.impl;
import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import java.util.Queue;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.CodecFactory;
import com.google.code.yanf4j.core.EventType;
import com.google.code.yanf4j.core.Handler;
import com.google.code.yanf4j.core.Session;
import com.google.code.yanf4j.core.WriteMessage;
import com.google.code.yanf4j.core.impl.StandardSocketOption;
import com.google.code.yanf4j.nio.NioSession;
import com.google.code.yanf4j.nio.NioSessionConfig;
/**
* Nio tcp socket controller
*
* @author dennis
*
*/
public abstract class SocketChannelController extends NioController {
protected boolean soLingerOn = false;
public void setSoLinger(boolean on, int value) {
this.soLingerOn = on;
this.socketOptions.put(StandardSocketOption.SO_LINGER, value);
}
public SocketChannelController() {
super();
}
public SocketChannelController(Configuration configuration) {
super(configuration, null, null);
}
public SocketChannelController(Configuration configuration, CodecFactory codecFactory) {
super(configuration, null, codecFactory);
}
public SocketChannelController(Configuration configuration, Handler handler,
CodecFactory codecFactory) {
super(configuration, handler, codecFactory);
}
@Override
protected final void dispatchReadEvent(SelectionKey key) {
Session session = (Session) key.attachment();
if (session != null) {
((NioSession) session).onEvent(EventType.READABLE, key.selector());
} else {
log.warn("Could not find session for readable event,maybe it is closed");
}
}
@Override
protected final void dispatchWriteEvent(SelectionKey key) {
Session session = (Session) key.attachment();
if (session != null) {
((NioSession) session).onEvent(EventType.WRITEABLE, key.selector());
} else {
log.warn("Could not find session for writable event,maybe it is closed");
}
}
protected NioSession buildSession(SocketChannel sc) {
Queue<WriteMessage> queue = buildQueue();
NioSessionConfig sessionConfig = buildSessionConfig(sc, queue);
NioSession session =
new NioTCPSession(sessionConfig, this.configuration.getSessionReadBufferSize());
return session;
}
/**
* Confiure socket channel
*
* @param sc
* @throws IOException
*/
protected final void configureSocketChannel(SocketChannel sc) throws IOException {
sc.socket().setSoTimeout(this.soTimeout);
sc.configureBlocking(false);
if (this.socketOptions.get(StandardSocketOption.SO_REUSEADDR) != null) {
sc.socket().setReuseAddress(StandardSocketOption.SO_REUSEADDR.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_REUSEADDR)));
}
if (this.socketOptions.get(StandardSocketOption.SO_SNDBUF) != null) {
sc.socket().setSendBufferSize(StandardSocketOption.SO_SNDBUF.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_SNDBUF)));
}
if (this.socketOptions.get(StandardSocketOption.SO_KEEPALIVE) != null) {
sc.socket().setKeepAlive(StandardSocketOption.SO_KEEPALIVE.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_KEEPALIVE)));
}
if (this.socketOptions.get(StandardSocketOption.SO_LINGER) != null) {
sc.socket().setSoLinger(this.soLingerOn, StandardSocketOption.SO_LINGER.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_LINGER)));
}
if (this.socketOptions.get(StandardSocketOption.SO_RCVBUF) != null) {
sc.socket().setReceiveBufferSize(StandardSocketOption.SO_RCVBUF.type()
.cast(this.socketOptions.get(StandardSocketOption.SO_RCVBUF)));
}
if (this.socketOptions.get(StandardSocketOption.TCP_NODELAY) != null) {
sc.socket().setTcpNoDelay(StandardSocketOption.TCP_NODELAY.type()
.cast(this.socketOptions.get(StandardSocketOption.TCP_NODELAY)));
}
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>Nio implementation</title>
</head>
<body>
<h1>Nio implementation</h1>
</body>
</html>

View File

@@ -0,0 +1,95 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.statistics;
/**
* Statistics
*
* @author dennis
*
*/
public interface Statistics {
public void start();
public void stop();
public double getReceiveBytesPerSecond();
public double getSendBytesPerSecond();
public abstract void statisticsProcess(long n);
public abstract long getProcessedMessageCount();
public abstract double getProcessedMessageAverageTime();
public abstract void statisticsRead(long n);
public abstract void statisticsWrite(long n);
public abstract long getRecvMessageCount();
public abstract long getRecvMessageTotalSize();
public abstract long getRecvMessageAverageSize();
public abstract long getWriteMessageTotalSize();
public abstract long getWriteMessageCount();
public abstract long getWriteMessageAverageSize();
public abstract double getRecvMessageCountPerSecond();
public abstract double getWriteMessageCountPerSecond();
public void statisticsAccept();
public double getAcceptCountPerSecond();
public long getStartedTime();
public void reset();
public void restart();
public boolean isStatistics();
public void setReceiveThroughputLimit(double receiveThroughputLimit);
/**
* Check session if receive bytes per second is over flow controll
*
* @return
*/
public boolean isReceiveOverFlow();
/**
* Check session if receive bytes per second is over flow controll
*
* @return
*/
public boolean isSendOverFlow();
public double getSendThroughputLimit();
public void setSendThroughputLimit(double sendThroughputLimit);
public double getReceiveThroughputLimit();
}

View File

@@ -0,0 +1,148 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.statistics.impl;
import com.google.code.yanf4j.statistics.Statistics;
/**
* Default statistics implementation
*
* @author dennis
*
*/
public class DefaultStatistics implements Statistics {
public void start() {
}
public double getSendBytesPerSecond() {
return 0;
}
public double getReceiveBytesPerSecond() {
return 0;
}
public boolean isStatistics() {
return false;
}
public long getStartedTime() {
return 0;
}
public void reset() {
}
public void restart() {
}
public double getProcessedMessageAverageTime() {
return 0;
}
public long getProcessedMessageCount() {
return 0;
}
public void statisticsProcess(long n) {
}
public void stop() {
}
public long getRecvMessageCount() {
return 0;
}
public long getRecvMessageTotalSize() {
return 0;
}
public long getRecvMessageAverageSize() {
return 0;
}
public double getRecvMessageCountPerSecond() {
return 0;
}
public long getWriteMessageCount() {
return 0;
}
public long getWriteMessageTotalSize() {
return 0;
}
public long getWriteMessageAverageSize() {
return 0;
}
public void statisticsRead(long n) {
}
public void statisticsWrite(long n) {
}
public double getWriteMessageCountPerSecond() {
return 0;
}
public double getAcceptCountPerSecond() {
return 0;
}
public void statisticsAccept() {
}
public void setReceiveThroughputLimit(double receivePacketRate) {}
public boolean isReceiveOverFlow() {
return false;
}
public boolean isSendOverFlow() {
return false;
}
public double getSendThroughputLimit() {
return -1.0;
}
public void setSendThroughputLimit(double sendThroughputLimit) {}
public final double getReceiveThroughputLimit() {
return -1.0;
}
}

View File

@@ -0,0 +1,237 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.statistics.impl;
import java.util.concurrent.atomic.AtomicLong;
import com.google.code.yanf4j.statistics.Statistics;
/**
* A simple statistics implementation
*
* @author dennis
*
*/
public class SimpleStatistics implements Statistics {
private boolean started = false;
public boolean isStatistics() {
return this.started;
}
public synchronized void reset() {
if (this.started) {
throw new IllegalStateException();
}
this.startTime = this.stopTime = -1;
this.recvMessageCount.set(0);
this.recvMessageTotalSize.set(0);
this.writeMessageCount.set(0);
this.writeMessageTotalSize.set(0);
this.processMessageCount.set(0);
this.processMessageTotalTime.set(0);
this.acceptCount.set(0);
}
private double receiveThroughputLimit = -1.0; // receive bytes per second
private double sendThroughputLimit = -1.0; // send bytes per second
public void setReceiveThroughputLimit(double receivePacketRate) {
this.receiveThroughputLimit = receivePacketRate;
}
/**
* Check session if receive bytes per second is over flow controll
*
* @return
*/
public boolean isReceiveOverFlow() {
if (getReceiveThroughputLimit() < 0.0000f) {
return false;
}
return getReceiveBytesPerSecond() > getReceiveThroughputLimit();
}
/**
* Check session if receive bytes per second is over flow controll
*
* @return
*/
public boolean isSendOverFlow() {
if (getSendThroughputLimit() < 0.0000f) {
return false;
}
return getSendBytesPerSecond() > getSendThroughputLimit();
}
public double getSendThroughputLimit() {
return this.sendThroughputLimit;
}
public void setSendThroughputLimit(double sendThroughputLimit) {
this.sendThroughputLimit = sendThroughputLimit;
}
public final double getReceiveThroughputLimit() {
return this.receiveThroughputLimit;
}
public synchronized void restart() {
stop();
reset();
start();
}
private long startTime, stopTime = -1;
private AtomicLong recvMessageCount = new AtomicLong();
private AtomicLong recvMessageTotalSize = new AtomicLong();
private AtomicLong writeMessageCount = new AtomicLong();
private AtomicLong writeMessageTotalSize = new AtomicLong();
private AtomicLong processMessageCount = new AtomicLong();
private AtomicLong acceptCount = new AtomicLong();
private AtomicLong processMessageTotalTime = new AtomicLong();
public long getStartedTime() {
return this.startTime;
}
public double getProcessedMessageAverageTime() {
return this.processMessageCount.get() == 0 ? 0
: (double) this.processMessageTotalTime.get() / this.processMessageCount.get();
}
public long getProcessedMessageCount() {
return this.processMessageCount.get();
}
public void statisticsProcess(long n) {
if (!this.started) {
return;
}
if (n < 0) {
return;
}
this.processMessageTotalTime.addAndGet(n);
this.processMessageCount.incrementAndGet();
}
public SimpleStatistics() {
}
public synchronized void start() {
this.startTime = System.currentTimeMillis();
this.started = true;
}
public synchronized void stop() {
this.stopTime = System.currentTimeMillis();
this.started = false;
}
public void statisticsRead(long n) {
if (!this.started) {
return;
}
if (n <= 0) {
return;
}
this.recvMessageCount.incrementAndGet();
this.recvMessageTotalSize.addAndGet(n);
}
public long getRecvMessageCount() {
return this.recvMessageCount.get();
}
public long getRecvMessageTotalSize() {
return this.recvMessageTotalSize.get();
}
public long getWriteMessageCount() {
return this.writeMessageCount.get();
}
public long getWriteMessageTotalSize() {
return this.writeMessageTotalSize.get();
}
public void statisticsWrite(long n) {
if (!this.started) {
return;
}
if (n <= 0) {
return;
}
this.writeMessageCount.incrementAndGet();
this.writeMessageTotalSize.addAndGet(n);
}
public long getRecvMessageAverageSize() {
return this.recvMessageCount.get() == 0 ? 0
: this.recvMessageTotalSize.get() / this.recvMessageCount.get();
}
public double getRecvMessageCountPerSecond() {
long duration = (this.stopTime == -1) ? (System.currentTimeMillis() - this.startTime)
: (this.stopTime - this.startTime);
return duration == 0 ? 0 : (double) this.recvMessageCount.get() * 1000 / duration;
}
public double getWriteMessageCountPerSecond() {
long duration = (this.stopTime == -1) ? (System.currentTimeMillis() - this.startTime)
: (this.stopTime - this.startTime);
return duration == 0 ? 0 : (double) this.writeMessageCount.get() * 1000 / duration;
}
public long getWriteMessageAverageSize() {
return this.writeMessageCount.get() == 0 ? 0
: this.writeMessageTotalSize.get() / this.writeMessageCount.get();
}
public double getAcceptCountPerSecond() {
long duration = (this.stopTime == -1) ? (System.currentTimeMillis() - this.startTime)
: (this.stopTime - this.startTime);
return duration == 0 ? 0 : (double) this.acceptCount.get() * 1000 / duration;
}
public double getReceiveBytesPerSecond() {
long duration = (this.stopTime == -1) ? (System.currentTimeMillis() - this.startTime)
: (this.stopTime - this.startTime);
return duration == 0 ? 0 : (double) this.recvMessageTotalSize.get() * 1000 / duration;
}
public double getSendBytesPerSecond() {
long duration = (this.stopTime == -1) ? (System.currentTimeMillis() - this.startTime)
: (this.stopTime - this.startTime);
return duration == 0 ? 0 : (double) this.writeMessageTotalSize.get() * 1000 / duration;
}
public void statisticsAccept() {
if (!this.started) {
return;
}
this.acceptCount.incrementAndGet();
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>Statistics</title>
</head>
<body>
<h1>Networking statistics</h1>
</body>
</html>

View File

@@ -0,0 +1,34 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.List;
import com.google.code.yanf4j.buffer.IoBuffer;
/**
* ByteBuffer matcher
*
* @author dennis
*
*/
public interface ByteBufferMatcher {
public int matchFirst(IoBuffer buffer);
public List<Integer> matchAll(final IoBuffer buffer);
}

View File

@@ -0,0 +1,197 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
/*
* Copyright 2004-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
/**
* 来自于cindy2.4的工具类,做了简化和新增
*/
import java.nio.ByteBuffer;
import com.google.code.yanf4j.config.Configuration;
public class ByteBufferUtils {
/**
*
* @param byteBuffer
* @return *
*/
public static final ByteBuffer increaseBufferCapatity(ByteBuffer byteBuffer) {
if (byteBuffer == null) {
throw new IllegalArgumentException("buffer is null");
}
if (Configuration.DEFAULT_INCREASE_BUFF_SIZE < 0) {
throw new IllegalArgumentException("size less than 0");
}
int capacity = byteBuffer.capacity() + Configuration.DEFAULT_INCREASE_BUFF_SIZE;
if (capacity < 0) {
throw new IllegalArgumentException("capacity can't be negative");
}
ByteBuffer result = (byteBuffer.isDirect() ? ByteBuffer.allocateDirect(capacity)
: ByteBuffer.allocate(capacity));
result.order(byteBuffer.order());
byteBuffer.flip();
result.put(byteBuffer);
return result;
}
public static final void flip(ByteBuffer[] buffers) {
if (buffers == null) {
return;
}
for (ByteBuffer buffer : buffers) {
if (buffer != null) {
buffer.flip();
}
}
}
public static final ByteBuffer gather(ByteBuffer[] buffers) {
if (buffers == null || buffers.length == 0) {
return null;
}
ByteBuffer result = ByteBuffer.allocate(remaining(buffers));
result.order(buffers[0].order());
for (int i = 0; i < buffers.length; i++) {
if (buffers[i] != null) {
result.put(buffers[i]);
}
}
result.flip();
return result;
}
public static final int remaining(ByteBuffer[] buffers) {
if (buffers == null) {
return 0;
}
int remaining = 0;
for (int i = 0; i < buffers.length; i++) {
if (buffers[i] != null) {
remaining += buffers[i].remaining();
}
}
return remaining;
}
public static final void clear(ByteBuffer[] buffers) {
if (buffers == null) {
return;
}
for (ByteBuffer buffer : buffers) {
if (buffer != null) {
buffer.clear();
}
}
}
public static final String toHex(byte b) {
return ("" + "0123456789ABCDEF".charAt(0xf & b >> 4) + "0123456789ABCDEF".charAt(b & 0xf));
}
public static final int indexOf(ByteBuffer buffer, ByteBuffer pattern) {
if (pattern == null || buffer == null) {
return -1;
}
int n = buffer.remaining();
int m = pattern.remaining();
int patternPos = pattern.position();
int bufferPos = buffer.position();
if (n < m) {
return -1;
}
for (int s = 0; s <= n - m; s++) {
boolean match = true;
for (int i = 0; i < m; i++) {
if (buffer.get(s + i + bufferPos) != pattern.get(patternPos + i)) {
match = false;
break;
}
}
if (match) {
return (bufferPos + s);
}
}
return -1;
}
public static final int indexOf(ByteBuffer buffer, ByteBuffer pattern, int offset) {
if (offset < 0) {
throw new IllegalArgumentException("offset must be greater than 0");
}
if (pattern == null || buffer == null) {
return -1;
}
int patternPos = pattern.position();
int n = buffer.remaining();
int m = pattern.remaining();
if (n < m) {
return -1;
}
if (offset < buffer.position() || offset > buffer.limit()) {
return -1;
}
for (int s = 0; s <= n - m; s++) {
boolean match = true;
for (int i = 0; i < m; i++) {
if (buffer.get(s + i + offset) != pattern.get(patternPos + i)) {
match = false;
break;
}
}
if (match) {
return (offset + s);
}
}
return -1;
}
/**
* 查看ByteBuffer数组是否还有剩余
*
* @param buffers ByteBuffers
* @return have remaining
*/
public static final boolean hasRemaining(ByteBuffer[] buffers) {
if (buffers == null) {
return false;
}
for (int i = 0; i < buffers.length; i++) {
if (buffers[i] != null && buffers[i].hasRemaining()) {
return true;
}
}
return false;
}
public static final int uByte(byte b) {
return b & 0xFF;
}
}

View File

@@ -0,0 +1,342 @@
package com.google.code.yanf4j.util;
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF 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.
*/
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
/**
* A circular queue from mina
*
* @author dennis
*
* @param <E>
*/
public class CircularQueue<E> extends AbstractList<E> implements List<E>, Queue<E>, Serializable {
private static final long serialVersionUID = 3993421269224511264L;
private static final int DEFAULT_CAPACITY = 4;
private final int initialCapacity;
// XXX: This volatile keyword here is a workaround for SUN Java Compiler
// bug,
// which produces buggy byte code. I don't event know why adding a volatile
// fixes the problem. Eclipse Java Compiler seems to produce correct byte
// code.
private volatile Object[] items;
private int mask;
private int first = 0;
private int last = 0;
private boolean full;
private int shrinkThreshold;
/**
* Construct a new, empty queue.
*/
public CircularQueue() {
this(DEFAULT_CAPACITY);
}
public CircularQueue(int initialCapacity) {
int actualCapacity = normalizeCapacity(initialCapacity);
this.items = new Object[actualCapacity];
this.mask = actualCapacity - 1;
this.initialCapacity = actualCapacity;
this.shrinkThreshold = 0;
}
private static int normalizeCapacity(int initialCapacity) {
int actualCapacity = 1;
while (actualCapacity < initialCapacity) {
actualCapacity <<= 1;
if (actualCapacity < 0) {
actualCapacity = 1 << 30;
break;
}
}
return actualCapacity;
}
/**
* Returns the capacity of this queue.
*/
public int capacity() {
return this.items.length;
}
@Override
public void clear() {
if (!isEmpty()) {
Arrays.fill(this.items, null);
this.first = 0;
this.last = 0;
this.full = false;
shrinkIfNeeded();
}
}
@SuppressWarnings("unchecked")
public E poll() {
if (isEmpty()) {
return null;
}
Object ret = this.items[this.first];
this.items[this.first] = null;
decreaseSize();
if (this.first == this.last) {
this.first = this.last = 0;
}
shrinkIfNeeded();
return (E) ret;
}
public boolean offer(E item) {
if (item == null) {
throw new NullPointerException("item");
}
expandIfNeeded();
this.items[this.last] = item;
increaseSize();
return true;
}
@SuppressWarnings("unchecked")
public E peek() {
if (isEmpty()) {
return null;
}
return (E) this.items[this.first];
}
@SuppressWarnings("unchecked")
@Override
public E get(int idx) {
checkIndex(idx);
return (E) this.items[getRealIndex(idx)];
}
@Override
public boolean isEmpty() {
return (this.first == this.last) && !this.full;
}
@Override
public int size() {
if (this.full) {
return capacity();
}
if (this.last >= this.first) {
return this.last - this.first;
} else {
return this.last - this.first + capacity();
}
}
@Override
public String toString() {
return "first=" + this.first + ", last=" + this.last + ", size=" + size() + ", mask = "
+ this.mask;
}
private void checkIndex(int idx) {
if (idx < 0 || idx >= size()) {
throw new IndexOutOfBoundsException(String.valueOf(idx));
}
}
private int getRealIndex(int idx) {
return (this.first + idx) & this.mask;
}
private void increaseSize() {
this.last = (this.last + 1) & this.mask;
this.full = this.first == this.last;
}
private void decreaseSize() {
this.first = (this.first + 1) & this.mask;
this.full = false;
}
private void expandIfNeeded() {
if (this.full) {
// expand queue
final int oldLen = this.items.length;
final int newLen = oldLen << 1;
Object[] tmp = new Object[newLen];
if (this.first < this.last) {
System.arraycopy(this.items, this.first, tmp, 0, this.last - this.first);
} else {
System.arraycopy(this.items, this.first, tmp, 0, oldLen - this.first);
System.arraycopy(this.items, 0, tmp, oldLen - this.first, this.last);
}
this.first = 0;
this.last = oldLen;
this.items = tmp;
this.mask = tmp.length - 1;
if (newLen >>> 3 > this.initialCapacity) {
this.shrinkThreshold = newLen >>> 3;
}
}
}
private void shrinkIfNeeded() {
int size = size();
if (size <= this.shrinkThreshold) {
// shrink queue
final int oldLen = this.items.length;
int newLen = normalizeCapacity(size);
if (size == newLen) {
newLen <<= 1;
}
if (newLen >= oldLen) {
return;
}
if (newLen < this.initialCapacity) {
if (oldLen == this.initialCapacity) {
return;
} else {
newLen = this.initialCapacity;
}
}
Object[] tmp = new Object[newLen];
// Copy only when there's something to copy.
if (size > 0) {
if (this.first < this.last) {
System.arraycopy(this.items, this.first, tmp, 0, this.last - this.first);
} else {
System.arraycopy(this.items, this.first, tmp, 0, oldLen - this.first);
System.arraycopy(this.items, 0, tmp, oldLen - this.first, this.last);
}
}
this.first = 0;
this.last = size;
this.items = tmp;
this.mask = tmp.length - 1;
this.shrinkThreshold = 0;
}
}
@Override
public boolean add(E o) {
return offer(o);
}
@SuppressWarnings("unchecked")
@Override
public E set(int idx, E o) {
checkIndex(idx);
int realIdx = getRealIndex(idx);
Object old = this.items[realIdx];
this.items[realIdx] = o;
return (E) old;
}
@Override
public void add(int idx, E o) {
if (idx == size()) {
offer(o);
return;
}
checkIndex(idx);
expandIfNeeded();
int realIdx = getRealIndex(idx);
// Make a room for a new element.
if (this.first < this.last) {
System.arraycopy(this.items, realIdx, this.items, realIdx + 1, this.last - realIdx);
} else {
if (realIdx >= this.first) {
System.arraycopy(this.items, 0, this.items, 1, this.last);
this.items[0] = this.items[this.items.length - 1];
System.arraycopy(this.items, realIdx, this.items, realIdx + 1,
this.items.length - realIdx - 1);
} else {
System.arraycopy(this.items, realIdx, this.items, realIdx + 1, this.last - realIdx);
}
}
this.items[realIdx] = o;
increaseSize();
}
@SuppressWarnings("unchecked")
@Override
public E remove(int idx) {
if (idx == 0) {
return poll();
}
checkIndex(idx);
int realIdx = getRealIndex(idx);
Object removed = this.items[realIdx];
// Remove a room for the removed element.
if (this.first < this.last) {
System.arraycopy(this.items, this.first, this.items, this.first + 1, realIdx - this.first);
} else {
if (realIdx >= this.first) {
System.arraycopy(this.items, this.first, this.items, this.first + 1, realIdx - this.first);
} else {
System.arraycopy(this.items, 0, this.items, 1, realIdx);
this.items[0] = this.items[this.items.length - 1];
System.arraycopy(this.items, this.first, this.items, this.first + 1,
this.items.length - this.first - 1);
}
}
this.items[this.first] = null;
decreaseSize();
shrinkIfNeeded();
return (E) removed;
}
public E remove() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return poll();
}
public E element() {
if (isEmpty()) {
throw new NoSuchElementException();
}
return peek();
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A {@link ConcurrentHashMap}-backed {@link Set}.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 597692 $, $Date: 2007-11-23 08:56:32 -0700 (Fri, 23 Nov 2007) $
*/
public class ConcurrentHashSet<E> extends MapBackedSet<E> {
private static final long serialVersionUID = 8518578988740277828L;
public ConcurrentHashSet() {
super(new ConcurrentHashMap<E, Boolean>());
}
public ConcurrentHashSet(Collection<E> c) {
super(new ConcurrentHashMap<E, Boolean>(), c);
}
@Override
public boolean add(E o) {
Boolean answer = ((ConcurrentMap<E, Boolean>) map).putIfAbsent(o, Boolean.TRUE);
return answer == null;
}
}

View File

@@ -0,0 +1,22 @@
package com.google.code.yanf4j.util;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.TimeUnit;
import com.google.code.yanf4j.core.impl.PoolDispatcher;
/**
* Dispatcher Factory
*
* @author dennis
*
*/
public class DispatcherFactory {
public static com.google.code.yanf4j.core.Dispatcher newDispatcher(int size,
RejectedExecutionHandler rejectedExecutionHandler, String prefix) {
if (size > 0) {
return new PoolDispatcher(size, 60, TimeUnit.SECONDS, rejectedExecutionHandler, prefix);
} else {
return null;
}
}
}

View File

@@ -0,0 +1,780 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2008 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU General Public License
* Version 2 only ("GPL") or the Common Development and Distribution License("CDDL") (collectively,
* the "License"). You may not use this file except in compliance with the License. You can obtain a
* copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html or
* glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each file and include the
* License file at glassfish/bootstrap/legal/LICENSE.txt. Sun designates this particular file as
* subject to the "Classpath" exception as provided by Sun in the GPL Version 2 section of the
* License file that accompanied this code. If applicable, add the following below the License
* Header, with the fields enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* If you wish your version of this file to be governed by only the CDDL or only the GPL Version 2,
* indicate your decision by adding "[Contributor] elects to include this software in this
* distribution under the [CDDL or GPL Version 2] license." If you don't indicate a single choice of
* license, a recipient has the option to distribute your version of this file under either the
* CDDL, the GPL Version 2 or to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then the
* option applies only if the new code is made subject to such option by the copyright holder.
*/
/*
* Written by Doug Lea with assistance from members of JCP JSR-166 Expert Group and released to the
* public domain, as explained at http://creativecommons.org/licenses/publicdomain
*/
package com.google.code.yanf4j.util;
import java.util.AbstractQueue;
import java.util.Collection;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.LockSupport;
/**
* An unbounded <code>TransferQueue</code> based on linked nodes. This queue orders elements FIFO
* (first-in-first-out) with respect to any given producer. The <em>head</em> of the queue is that
* element that has been on the queue the longest time for some producer. The <em>tail</em> of the
* queue is that element that has been on the queue the shortest time for some producer.
*
* <p>
* Beware that, unlike in most collections, the <code>size</code> method is <em>NOT</em> a constant-time
* operation. Because of the asynchronous nature of these queues, determining the current number of
* elements requires a traversal of the elements.
*
* <p>
* This class and its iterator implement all of the <em>optional</em> methods of the
* {@link Collection} and {@link Iterator} interfaces.
*
* <p>
* Memory consistency effects: As with other concurrent collections, actions in a thread prior to
* placing an object into a {@code LinkedTransferQueue}
* <a href="package-summary.html#MemoryVisibility"><i>happen-before</i></a> actions subsequent to
* the access or removal of that element from the {@code LinkedTransferQueue} in another thread.
*
* @author Doug Lea
* @author The Netty Project (netty-dev@lists.jboss.org)
* @author Trustin Lee (tlee@redhat.com)
*
* @param <E> the type of elements held in this collection
*
*/
public class LinkedTransferQueue<E> extends AbstractQueue<E> implements BlockingQueue<E> {
/*
* This class extends the approach used in FIFO-mode SynchronousQueues. See the internal
* documentation, as well as the PPoPP 2006 paper "Scalable Synchronous Queues" by Scherer, Lea &
* Scott (http://www.cs.rice.edu/~wns1/papers/2006-PPoPP-SQ.pdf)
*
* The main extension is to provide different Wait modes for the main "xfer" method that puts or
* takes items. These don't impact the basic dual-queue logic, but instead control whether or how
* threads block upon insertion of request or data nodes into the dual queue. It also uses
* slightly different conventions for tracking whether nodes are off-list or cancelled.
*/
// Wait modes for xfer method
private static final int NOWAIT = 0;
private static final int TIMEOUT = 1;
private static final int WAIT = 2;
/** The number of CPUs, for spin control */
private static final int NCPUS = Runtime.getRuntime().availableProcessors();
/**
* The number of times to spin before blocking in timed waits. The value is empirically derived --
* it works well across a variety of processors and OSes. Empirically, the best value seems not to
* vary with number of CPUs (beyond 2) so is just a constant.
*/
private static final int maxTimedSpins = NCPUS < 2 ? 0 : 32;
/**
* The number of times to spin before blocking in untimed waits. This is greater than timed value
* because untimed waits spin faster since they don't need to check times on each spin.
*/
private static final int maxUntimedSpins = maxTimedSpins * 16;
/**
* The number of nanoseconds for which it is faster to spin rather than to use timed park. A rough
* estimate suffices.
*/
private static final long spinForTimeoutThreshold = 1000L;
/**
* Node class for LinkedTransferQueue. Opportunistically subclasses from AtomicReference to
* represent item. Uses Object, not E, to allow setting item to "this" after use, to avoid garbage
* retention. Similarly, setting the next field to this is used as sentinel that node is off list.
*/
private static final class QNode extends AtomicReference<Object> {
private static final long serialVersionUID = 5925596372370723938L;
transient volatile QNode next;
transient volatile Thread waiter; // to control park/unpark
final boolean isData;
public long p1, p2, p3, p4, p5, p6, p7;
QNode(Object item, boolean isData) {
super(item);
this.isData = isData;
}
private static final AtomicReferenceFieldUpdater<QNode, QNode> nextUpdater;
static {
AtomicReferenceFieldUpdater<QNode, QNode> tmp = null;
try {
tmp = AtomicReferenceFieldUpdater.newUpdater(QNode.class, QNode.class, "next");
// Test if AtomicReferenceFieldUpdater is really working.
QNode testNode = new QNode(null, false);
tmp.set(testNode, testNode);
if (testNode.next != testNode) {
// Not set as expected - fall back to the safe mode.
throw new Exception();
}
} catch (Throwable t) {
// Running in a restricted environment with a security manager.
tmp = null;
}
nextUpdater = tmp;
}
boolean casNext(QNode cmp, QNode val) {
if (nextUpdater != null) {
return nextUpdater.compareAndSet(this, cmp, val);
} else {
return alternativeCasNext(cmp, val);
}
}
private synchronized boolean alternativeCasNext(QNode cmp, QNode val) {
if (this.next == cmp) {
this.next = val;
return true;
} else {
return false;
}
}
}
/**
* Padded version of AtomicReference used for head, tail and cleanMe, to alleviate contention
* across threads CASing one vs the other.
*/
public static final class PaddedAtomicReference<T> extends AtomicReference<T> {
private static final long serialVersionUID = 4684288940772921317L;
// enough padding for 64bytes with 4byte refs
@SuppressWarnings("unused")
public Object p0, p1, p2, p3, p4, p5, p6, p7, p8, p9, pa, pb, pc, pd, pe;
public PaddedAtomicReference(T r) {
super(r);
}
}
/** head of the queue */
private final PaddedAtomicReference<QNode> head;
/** tail of the queue */
private final PaddedAtomicReference<QNode> tail;
/**
* Reference to a cancelled node that might not yet have been unlinked from queue because it was
* the last inserted node when it cancelled.
*/
private final PaddedAtomicReference<QNode> cleanMe;
/**
* Tries to cas nh as new head; if successful, unlink old head's next node to avoid garbage
* retention.
*/
private boolean advanceHead(QNode h, QNode nh) {
if (h == this.head.get() && this.head.compareAndSet(h, nh)) {
h.next = h; // forget old next
return true;
}
return false;
}
/**
* Puts or takes an item. Used for most queue operations (except poll() and tryTransfer()). See
* the similar code in SynchronousQueue for detailed explanation.
*
* @param e the item or if null, signifies that this is a take
* @param mode the wait mode: NOWAIT, TIMEOUT, WAIT
* @param nanos timeout in nanosecs, used only if mode is TIMEOUT
* @return an item, or null on failure
*/
private Object xfer(Object e, int mode, long nanos) {
boolean isData = e != null;
QNode s = null;
final PaddedAtomicReference<QNode> head = this.head;
final PaddedAtomicReference<QNode> tail = this.tail;
for (;;) {
QNode t = tail.get();
QNode h = head.get();
if (t != null && (t == h || t.isData == isData)) {
if (s == null) {
s = new QNode(e, isData);
}
QNode last = t.next;
if (last != null) {
if (t == tail.get()) {
tail.compareAndSet(t, last);
}
} else if (t.casNext(null, s)) {
tail.compareAndSet(t, s);
return awaitFulfill(t, s, e, mode, nanos);
}
}
else if (h != null) {
QNode first = h.next;
if (t == tail.get() && first != null && advanceHead(h, first)) {
Object x = first.get();
if (x != first && first.compareAndSet(x, e)) {
LockSupport.unpark(first.waiter);
return isData ? e : x;
}
}
}
}
}
/**
* Version of xfer for poll() and tryTransfer, which simplifies control paths both here and in
* xfer
*/
private Object fulfill(Object e) {
boolean isData = e != null;
final PaddedAtomicReference<QNode> head = this.head;
final PaddedAtomicReference<QNode> tail = this.tail;
for (;;) {
QNode t = tail.get();
QNode h = head.get();
if (t != null && (t == h || t.isData == isData)) {
QNode last = t.next;
if (t == tail.get()) {
if (last != null) {
tail.compareAndSet(t, last);
} else {
return null;
}
}
} else if (h != null) {
QNode first = h.next;
if (t == tail.get() && first != null && advanceHead(h, first)) {
Object x = first.get();
if (x != first && first.compareAndSet(x, e)) {
LockSupport.unpark(first.waiter);
return isData ? e : x;
}
}
}
}
}
/**
* Spins/blocks until node s is fulfilled or caller gives up, depending on wait mode.
*
* @param pred the predecessor of waiting node
* @param s the waiting node
* @param e the comparison value for checking match
* @param mode mode
* @param nanos timeout value
* @return matched item, or s if cancelled
*/
private Object awaitFulfill(QNode pred, QNode s, Object e, int mode, long nanos) {
if (mode == NOWAIT) {
return null;
}
long lastTime = mode == TIMEOUT ? System.nanoTime() : 0;
Thread w = Thread.currentThread();
int spins = -1; // set to desired spin count below
for (;;) {
if (w.isInterrupted()) {
s.compareAndSet(e, s);
}
Object x = s.get();
if (x != e) { // Node was matched or cancelled
advanceHead(pred, s); // unlink if head
if (x == s) { // was cancelled
clean(pred, s);
return null;
} else if (x != null) {
s.set(s); // avoid garbage retention
return x;
} else {
return e;
}
}
if (mode == TIMEOUT) {
long now = System.nanoTime();
nanos -= now - lastTime;
lastTime = now;
if (nanos <= 0) {
s.compareAndSet(e, s); // try to cancel
continue;
}
}
if (spins < 0) {
QNode h = this.head.get(); // only spin if at head
spins = h != null && h.next == s ? (mode == TIMEOUT ? maxTimedSpins : maxUntimedSpins) : 0;
}
if (spins > 0) {
--spins;
} else if (s.waiter == null) {
s.waiter = w;
} else if (mode != TIMEOUT) {
// LockSupport.park(this);
LockSupport.park(); // allows run on java5
s.waiter = null;
spins = -1;
} else if (nanos > spinForTimeoutThreshold) {
// LockSupport.parkNanos(this, nanos);
LockSupport.parkNanos(nanos);
s.waiter = null;
spins = -1;
}
}
}
/**
* Returns validated tail for use in cleaning methods
*/
private QNode getValidatedTail() {
for (;;) {
QNode h = this.head.get();
QNode first = h.next;
if (first != null && first.next == first) { // help advance
advanceHead(h, first);
continue;
}
QNode t = this.tail.get();
QNode last = t.next;
if (t == this.tail.get()) {
if (last != null) {
this.tail.compareAndSet(t, last); // help advance
} else {
return t;
}
}
}
}
/**
* Gets rid of cancelled node s with original predecessor pred.
*
* @param pred predecessor of cancelled node
* @param s the cancelled node
*/
void clean(QNode pred, QNode s) {
Thread w = s.waiter;
if (w != null) { // Wake up thread
s.waiter = null;
if (w != Thread.currentThread()) {
LockSupport.unpark(w);
}
}
/*
* At any given time, exactly one node on list cannot be deleted -- the last inserted node. To
* accommodate this, if we cannot delete s, we save its predecessor as "cleanMe", processing the
* previously saved version first. At least one of node s or the node previously saved can
* always be processed, so this always terminates.
*/
while (pred.next == s) {
QNode oldpred = reclean(); // First, help get rid of cleanMe
QNode t = getValidatedTail();
if (s != t) { // If not tail, try to unsplice
QNode sn = s.next; // s.next == s means s already off list
if (sn == s || pred.casNext(s, sn)) {
break;
}
} else if (oldpred == pred || // Already saved
oldpred == null && this.cleanMe.compareAndSet(null, pred)) {
break; // Postpone cleaning
}
}
}
/**
* Tries to unsplice the cancelled node held in cleanMe that was previously uncleanable because it
* was at tail.
*
* @return current cleanMe node (or null)
*/
private QNode reclean() {
/*
* cleanMe is, or at one time was, predecessor of cancelled node s that was the tail so could
* not be unspliced. If s is no longer the tail, try to unsplice if necessary and make cleanMe
* slot available. This differs from similar code in clean() because we must check that pred
* still points to a cancelled node that must be unspliced -- if not, we can (must) clear
* cleanMe without unsplicing. This can loop only due to contention on casNext or clearing
* cleanMe.
*/
QNode pred;
while ((pred = this.cleanMe.get()) != null) {
QNode t = getValidatedTail();
QNode s = pred.next;
if (s != t) {
QNode sn;
if (s == null || s == pred || s.get() != s || (sn = s.next) == s || pred.casNext(s, sn)) {
this.cleanMe.compareAndSet(pred, null);
}
} else {
break;
}
}
return pred;
}
@SuppressWarnings("unchecked")
E cast(Object e) {
return (E) e;
}
/**
* Creates an initially empty <code>LinkedTransferQueue</code>.
*/
public LinkedTransferQueue() {
QNode dummy = new QNode(null, false);
this.head = new PaddedAtomicReference<QNode>(dummy);
this.tail = new PaddedAtomicReference<QNode>(dummy);
this.cleanMe = new PaddedAtomicReference<QNode>(null);
}
/**
* Creates a <code>LinkedTransferQueue</code> initially containing the elements of the given
* collection, added in traversal order of the collection's iterator.
*
* @param c the collection of elements to initially contain
* @throws NullPointerException if the specified collection or any of its elements are null
*/
public LinkedTransferQueue(Collection<? extends E> c) {
this();
addAll(c);
}
public void put(E e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
xfer(e, NOWAIT, 0);
}
public boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
if (Thread.interrupted()) {
throw new InterruptedException();
}
xfer(e, NOWAIT, 0);
return true;
}
public boolean offer(E e) {
if (e == null) {
throw new NullPointerException();
}
xfer(e, NOWAIT, 0);
return true;
}
public void transfer(E e) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
if (xfer(e, WAIT, 0) == null) {
Thread.interrupted();
throw new InterruptedException();
}
}
public boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException {
if (e == null) {
throw new NullPointerException();
}
if (xfer(e, TIMEOUT, unit.toNanos(timeout)) != null) {
return true;
}
if (!Thread.interrupted()) {
return false;
}
throw new InterruptedException();
}
public boolean tryTransfer(E e) {
if (e == null) {
throw new NullPointerException();
}
return fulfill(e) != null;
}
public E take() throws InterruptedException {
Object e = xfer(null, WAIT, 0);
if (e != null) {
return cast(e);
}
Thread.interrupted();
throw new InterruptedException();
}
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
Object e = xfer(null, TIMEOUT, unit.toNanos(timeout));
if (e != null || !Thread.interrupted()) {
return cast(e);
}
throw new InterruptedException();
}
public E poll() {
return cast(fulfill(null));
}
public int drainTo(Collection<? super E> c) {
if (c == null) {
throw new NullPointerException();
}
if (c == this) {
throw new IllegalArgumentException();
}
int n = 0;
E e;
while ((e = poll()) != null) {
c.add(e);
++n;
}
return n;
}
public int drainTo(Collection<? super E> c, int maxElements) {
if (c == null) {
throw new NullPointerException();
}
if (c == this) {
throw new IllegalArgumentException();
}
int n = 0;
E e;
while (n < maxElements && (e = poll()) != null) {
c.add(e);
++n;
}
return n;
}
// Traversal-based methods
/**
* Return head after performing any outstanding helping steps
*/
QNode traversalHead() {
for (;;) {
QNode t = this.tail.get();
QNode h = this.head.get();
if (h != null && t != null) {
QNode last = t.next;
QNode first = h.next;
if (t == this.tail.get()) {
if (last != null) {
this.tail.compareAndSet(t, last);
} else if (first != null) {
Object x = first.get();
if (x == first) {
advanceHead(h, first);
} else {
return h;
}
} else {
return h;
}
}
}
}
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
/**
* Iterators. Basic strategy is to traverse list, treating non-data (i.e., request) nodes as
* terminating list. Once a valid data node is found, the item is cached so that the next call to
* next() will return it even if subsequently removed.
*/
class Itr implements Iterator<E> {
QNode nextNode; // Next node to return next
QNode currentNode; // last returned node, for remove()
QNode prevNode; // predecessor of last returned node
E nextItem; // Cache of next item, once commited to in next
Itr() {
this.nextNode = traversalHead();
advance();
}
E advance() {
this.prevNode = this.currentNode;
this.currentNode = this.nextNode;
E x = this.nextItem;
QNode p = this.nextNode.next;
for (;;) {
if (p == null || !p.isData) {
this.nextNode = null;
this.nextItem = null;
return x;
}
Object item = p.get();
if (item != p && item != null) {
this.nextNode = p;
this.nextItem = cast(item);
return x;
}
this.prevNode = p;
p = p.next;
}
}
public boolean hasNext() {
return this.nextNode != null;
}
public E next() {
if (this.nextNode == null) {
throw new NoSuchElementException();
}
return advance();
}
public void remove() {
QNode p = this.currentNode;
QNode prev = this.prevNode;
if (prev == null || p == null) {
throw new IllegalStateException();
}
Object x = p.get();
if (x != null && x != p && p.compareAndSet(x, p)) {
clean(prev, p);
}
}
}
public E peek() {
for (;;) {
QNode h = traversalHead();
QNode p = h.next;
if (p == null) {
return null;
}
Object x = p.get();
if (p != x) {
if (!p.isData) {
return null;
}
if (x != null) {
return cast(x);
}
}
}
}
@Override
public boolean isEmpty() {
for (;;) {
QNode h = traversalHead();
QNode p = h.next;
if (p == null) {
return true;
}
Object x = p.get();
if (p != x) {
if (!p.isData) {
return true;
}
if (x != null) {
return false;
}
}
}
}
public boolean hasWaitingConsumer() {
for (;;) {
QNode h = traversalHead();
QNode p = h.next;
if (p == null) {
return false;
}
Object x = p.get();
if (p != x) {
return !p.isData;
}
}
}
/**
* Returns the number of elements in this queue. If this queue contains more than
* <code>Integer.MAX_VALUE</code> elements, returns <code>Integer.MAX_VALUE</code>.
*
* <p>
* Beware that, unlike in most collections, this method is <em>NOT</em> a constant-time operation.
* Because of the asynchronous nature of these queues, determining the current number of elements
* requires an O(n) traversal.
*
* @return the number of elements in this queue
*/
@Override
public int size() {
int count = 0;
QNode h = traversalHead();
for (QNode p = h.next; p != null && p.isData; p = p.next) {
Object x = p.get();
if (x != null && x != p) {
if (++count == Integer.MAX_VALUE) {
break;
}
}
}
return count;
}
public int getWaitingConsumerCount() {
int count = 0;
QNode h = traversalHead();
for (QNode p = h.next; p != null && !p.isData; p = p.next) {
if (p.get() == null) {
if (++count == Integer.MAX_VALUE) {
break;
}
}
}
return count;
}
public int remainingCapacity() {
return Integer.MAX_VALUE;
}
}

View File

@@ -0,0 +1,60 @@
package com.google.code.yanf4j.util;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
/**
* A {@link Map}-backed {@link Set}.
*
* @author The Apache MINA Project (dev@mina.apache.org)
* @version $Rev: 597692 $, $Date: 2007-11-23 08:56:32 -0700 (Fri, 23 Nov 2007) $
*/
public class MapBackedSet<E> extends AbstractSet<E> implements Serializable {
private static final long serialVersionUID = -8347878570391674042L;
protected final Map<E, Boolean> map;
public MapBackedSet(Map<E, Boolean> map) {
this.map = map;
}
public MapBackedSet(Map<E, Boolean> map, Collection<E> c) {
this.map = map;
addAll(c);
}
@Override
public int size() {
return map.size();
}
@Override
public boolean contains(Object o) {
return map.containsKey(o);
}
@Override
public Iterator<E> iterator() {
return map.keySet().iterator();
}
@Override
public boolean add(E o) {
return map.put(o, Boolean.TRUE) == null;
}
@Override
public boolean remove(Object o) {
return map.remove(o) != null;
}
@Override
public void clear() {
map.clear();
}
}

View File

@@ -0,0 +1,52 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.Properties;
/**
* java.util.Property utils
*
* @author dennis
*
*/
public class PropertyUtils {
public static int getPropertyAsInteger(Properties props, String propName) {
return Integer.parseInt(PropertyUtils.getProperty(props, propName));
}
public static String getProperty(Properties props, String name) {
return props.getProperty(name).trim();
}
public static boolean getPropertyAsBoolean(Properties props, String name) {
return Boolean.valueOf(getProperty(props, name));
}
public static long getPropertyAsLong(Properties props, String name) {
return Long.parseLong(getProperty(props, name));
}
public static short getPropertyAsShort(Properties props, String name) {
return Short.parseShort(getProperty(props, name));
}
public static byte getPropertyAsByte(Properties props, String name) {
return Byte.parseByte(getProperty(props, name));
}
}

View File

@@ -0,0 +1,208 @@
/**
* Copyright [2008] [dennis zhuang] Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. You may obtain a copy of the
* License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed
* to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
* language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URL;
import java.util.Properties;
/**
* Resource utils
*
* @author dennis
*
*/
public class ResourcesUtils extends Object {
/** */
/**
* Returns the URL of the resource on the classpath
*
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static URL getResourceURL(String resource) throws IOException {
URL url = null;
ClassLoader loader = ResourcesUtils.class.getClassLoader();
if (loader != null) {
url = loader.getResource(resource);
}
if (url == null) {
url = ClassLoader.getSystemResource(resource);
}
if (url == null) {
throw new IOException("Could not find resource " + resource);
}
return url;
}
/** */
/**
* Returns the URL of the resource on the classpath
*
* @param loader The classloader used to load the resource
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static URL getResourceURL(ClassLoader loader, String resource) throws IOException {
URL url = null;
if (loader != null) {
url = loader.getResource(resource);
}
if (url == null) {
url = ClassLoader.getSystemResource(resource);
}
if (url == null) {
throw new IOException("Could not find resource " + resource);
}
return url;
}
/** */
/**
* Returns a resource on the classpath as a Stream object
*
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static InputStream getResourceAsStream(String resource) throws IOException {
InputStream in = null;
ClassLoader loader = ResourcesUtils.class.getClassLoader();
if (loader != null) {
in = loader.getResourceAsStream(resource);
}
if (in == null) {
in = ClassLoader.getSystemResourceAsStream(resource);
}
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
/** */
/**
* Returns a resource on the classpath as a Stream object
*
* @param loader The classloader used to load the resource
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static InputStream getResourceAsStream(ClassLoader loader, String resource)
throws IOException {
InputStream in = null;
if (loader != null) {
in = loader.getResourceAsStream(resource);
}
if (in == null) {
in = ClassLoader.getSystemResourceAsStream(resource);
}
if (in == null) {
throw new IOException("Could not find resource " + resource);
}
return in;
}
/** */
/**
* Returns a resource on the classpath as a Properties object
*
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static Properties getResourceAsProperties(String resource) throws IOException {
Properties props = new Properties();
InputStream in = null;
String propfile = resource;
in = getResourceAsStream(propfile);
props.load(in);
in.close();
return props;
}
/** */
/**
* Returns a resource on the classpath as a Properties object
*
* @param loader The classloader used to load the resource
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static Properties getResourceAsProperties(ClassLoader loader, String resource)
throws IOException {
Properties props = new Properties();
InputStream in = null;
String propfile = resource;
in = getResourceAsStream(loader, propfile);
props.load(in);
in.close();
return props;
}
/** */
/**
* Returns a resource on the classpath as a Reader object
*
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static InputStreamReader getResourceAsReader(String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(resource));
}
/** */
/**
* Returns a resource on the classpath as a Reader object
*
* @param loader The classloader used to load the resource
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException {
return new InputStreamReader(getResourceAsStream(loader, resource));
}
/** */
/**
* Returns a resource on the classpath as a File object
*
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static File getResourceAsFile(String resource) throws IOException {
return new File(getResourceURL(resource).getFile());
}
/** */
/**
* Returns a resource on the classpath as a File object
*
* @param loader The classloader used to load the resource
* @param resource The resource to find
* @throws IOException If the resource cannot be found or read
* @return The resource
*/
public static File getResourceAsFile(ClassLoader loader, String resource) throws IOException {
return new File(getResourceURL(loader, resource).getFile());
}
}

View File

@@ -0,0 +1,158 @@
/*
* The contents of this file are subject to the terms of the Common Development and Distribution
* License (the License). You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at https://glassfish.dev.java.net/public/CDDLv1.0.html or
* glassfish/bootstrap/legal/CDDLv1.0.txt. See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL Header Notice in each file and include the
* License file at glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable, add the following below
* the CDDL Header, with the fields enclosed by brackets [] replaced by you own identifying
* information: "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
*/
package com.google.code.yanf4j.util;
import java.io.IOException;
import java.nio.channels.Selector;
import java.util.EmptyStackException;
import java.util.Stack;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Temp selector factory,come from grizzly
*
* @author dennis zhuang
*/
public class SelectorFactory {
public static final int DEFAULT_MAX_SELECTORS = 20;
private static final Logger logger = LoggerFactory.getLogger(SelectorFactory.class);
/**
* The timeout before we exit.
*/
public final static long timeout = 5000;
/**
* The number of <code>Selector</code> to create.
*/
private static int maxSelectors;
/**
* Cache of <code>Selector</code>
*/
private final static Stack<Selector> selectors = new Stack<Selector>();
/**
* Creates the <code>Selector</code>
*/
static {
try {
setMaxSelectors(DEFAULT_MAX_SELECTORS);
} catch (IOException ex) {
logger.warn("SelectorFactory initializing Selector pool", ex);
}
}
/**
* Set max selector pool size.
*
* @param size max pool size
*/
public final static void setMaxSelectors(int size) throws IOException {
synchronized (selectors) {
if (size < maxSelectors) {
reduce(size);
} else if (size > maxSelectors) {
grow(size);
}
maxSelectors = size;
}
}
/**
* Returns max selector pool size
*
* @return max pool size
*/
public final static int getMaxSelectors() {
return maxSelectors;
}
/**
* Get a exclusive <code>Selector</code>
*
* @return <code>Selector</code>
*/
public final static Selector getSelector() {
synchronized (selectors) {
Selector s = null;
try {
if (selectors.size() != 0) {
s = selectors.pop();
}
} catch (EmptyStackException ex) {
}
int attempts = 0;
try {
while (s == null && attempts < 2) {
selectors.wait(timeout);
try {
if (selectors.size() != 0) {
s = selectors.pop();
}
} catch (EmptyStackException ex) {
break;
}
attempts++;
}
} catch (InterruptedException ex) {
}
return s;
}
}
/**
* Return the <code>Selector</code> to the cache
*
* @param s <code>Selector</code>
*/
public final static void returnSelector(Selector s) {
synchronized (selectors) {
selectors.push(s);
if (selectors.size() == 1) {
selectors.notify();
}
}
}
/**
* Increase <code>Selector</code> pool size
*/
private static void grow(int size) throws IOException {
for (int i = 0; i < size - maxSelectors; i++) {
selectors.add(Selector.open());
}
}
/**
* Decrease <code>Selector</code> pool size
*/
private static void reduce(int size) {
for (int i = 0; i < maxSelectors - size; i++) {
try {
Selector selector = selectors.pop();
selector.close();
} catch (IOException e) {
logger.error("SelectorFactory.reduce", e);
}
}
}
}

View File

@@ -0,0 +1,94 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.ArrayList;
import java.util.List;
import com.google.code.yanf4j.buffer.IoBuffer;
/**
* ByteBuffer matcher based on shift-and algorithm
*
* @author dennis
*
*/
public class ShiftAndByteBufferMatcher implements ByteBufferMatcher {
private int[] b;
private int mask;
private int patternLimit;
private int patternPos;
private int patternLen;
public ShiftAndByteBufferMatcher(IoBuffer pat) {
if (pat == null || pat.remaining() == 0) {
throw new IllegalArgumentException("blank buffer");
}
this.patternLimit = pat.limit();
this.patternPos = pat.position();
this.patternLen = pat.remaining();
preprocess(pat);
this.mask = 1 << (this.patternLen - 1);
}
/**
* Ԥ<><D4A4><EFBFBD><EFBFBD>
*
* @param pat
*/
private void preprocess(IoBuffer pat) {
this.b = new int[256];
for (int i = this.patternPos; i < this.patternLimit; i++) {
int p = ByteBufferUtils.uByte(pat.get(i));
this.b[p] = this.b[p] | (1 << i);
}
}
public final List<Integer> matchAll(IoBuffer buffer) {
List<Integer> matches = new ArrayList<Integer>();
int bufferLimit = buffer.limit();
int d = 0;
for (int pos = buffer.position(); pos < bufferLimit; pos++) {
d <<= 1;
d |= 1;
d &= this.b[ByteBufferUtils.uByte(buffer.get(pos))];
if ((d & this.mask) != 0) {
matches.add(pos - this.patternLen + 1);
}
}
return matches;
}
public final int matchFirst(IoBuffer buffer) {
if (buffer == null) {
return -1;
}
int bufferLimit = buffer.limit();
int d = 0;
for (int pos = buffer.position(); pos < bufferLimit; pos++) {
d <<= 1;
d |= 1;
d &= this.b[buffer.get(pos) & 0XFF];
if ((d & this.mask) != 0) {
return pos - this.patternLen + 1;
}
}
return -1;
}
}

View File

@@ -0,0 +1,93 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.ArrayList;
import java.util.List;
import com.google.code.yanf4j.buffer.IoBuffer;
/**
* ByteBuffer matcher based on shift-or algorithm
*
* @author dennis
*
*/
public class ShiftOrByteBufferMatcher implements ByteBufferMatcher {
private int[] b;
private int lim;
private int patternLen;
public ShiftOrByteBufferMatcher(IoBuffer pat) {
if (pat == null || pat.remaining() == 0) {
throw new IllegalArgumentException("blank buffer");
}
this.patternLen = pat.remaining();
preprocess(pat);
}
/**
* Ԥ<><D4A4><EFBFBD><EFBFBD>
*
* @param pat
*/
private void preprocess(IoBuffer pat) {
this.b = new int[256];
this.lim = 0;
for (int i = 0; i < 256; i++) {
this.b[i] = ~0;
}
for (int i = 0, j = 1; i < this.patternLen; i++, j <<= 1) {
this.b[ByteBufferUtils.uByte(pat.get(i))] &= ~j;
this.lim |= j;
}
this.lim = ~(this.lim >> 1);
}
public final List<Integer> matchAll(IoBuffer buffer) {
List<Integer> matches = new ArrayList<Integer>();
int bufferLimit = buffer.limit();
int state = ~0;
for (int pos = buffer.position(); pos < bufferLimit; pos++) {
state <<= 1;
state |= this.b[ByteBufferUtils.uByte(buffer.get(pos))];
if (state < this.lim) {
matches.add(pos - this.patternLen + 1);
}
}
return matches;
}
public final int matchFirst(IoBuffer buffer) {
if (buffer == null) {
return -1;
}
int bufferLimit = buffer.limit();
int state = ~0;
for (int pos = buffer.position(); pos < bufferLimit; pos++) {
state = (state <<= 1) | this.b[ByteBufferUtils.uByte(buffer.get(pos))];
if (state < this.lim) {
return pos - this.patternLen + 1;
}
}
return -1;
}
}

View File

@@ -0,0 +1,45 @@
package com.google.code.yanf4j.util;
import java.util.Iterator;
import java.util.LinkedList;
/**
* Simple queue. All methods are thread-safe.
*
* @author dennis zhuang
*/
public class SimpleQueue<T> extends java.util.AbstractQueue<T> {
protected final LinkedList<T> list;
public SimpleQueue(int initializeCapacity) {
this.list = new LinkedList<T>();
}
public SimpleQueue() {
this(100);
}
public synchronized boolean offer(T e) {
return this.list.add(e);
}
public synchronized T peek() {
return this.list.peek();
}
public synchronized T poll() {
return this.list.poll();
}
@Override
public Iterator<T> iterator() {
return this.list.iterator();
}
@Override
public int size() {
return this.list.size();
}
}

View File

@@ -0,0 +1,149 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.Selector;
import java.nio.channels.spi.SelectorProvider;
import java.util.Queue;
import java.util.Random;
/**
* System utils
*
* @author dennis
*
*/
public final class SystemUtils {
private SystemUtils() {
}
public static final String OS_NAME = System.getProperty("os.name");
private static boolean isLinuxPlatform = false;
static {
if (OS_NAME != null && OS_NAME.toLowerCase().indexOf("linux") >= 0) {
isLinuxPlatform = true;
}
}
public static final String JAVA_VERSION = System.getProperty("java.version");
private static boolean isAfterJava6u4Version = false;
static {
if (JAVA_VERSION != null) {
// java4 or java5
if (JAVA_VERSION.indexOf("1.4.") >= 0 || JAVA_VERSION.indexOf("1.5.") >= 0) {
isAfterJava6u4Version = false;
} else if (JAVA_VERSION.indexOf("1.6.") >= 0) {
int index = JAVA_VERSION.indexOf("_");
if (index > 0) {
String subVersionStr = JAVA_VERSION.substring(index + 1);
if (subVersionStr != null && subVersionStr.length() > 0) {
try {
int subVersion = Integer.parseInt(subVersionStr);
if (subVersion >= 4) {
isAfterJava6u4Version = true;
}
} catch (NumberFormatException e) {
}
}
}
// after java6
} else {
isAfterJava6u4Version = true;
}
}
}
public static final boolean isLinuxPlatform() {
return isLinuxPlatform;
}
public static final boolean isAfterJava6u4Version() {
return isAfterJava6u4Version;
}
public static void main(String[] args) {
System.out.println(isAfterJava6u4Version());
}
public static final int getSystemThreadCount() {
int cpus = getCpuProcessorCount();
if (cpus <= 8) {
return cpus;
} else {
return 8 + (cpus - 8) * 5 / 8;
}
}
public static final int getCpuProcessorCount() {
return Runtime.getRuntime().availableProcessors();
}
public static final Selector openSelector() throws IOException {
Selector result = null;
// check if it is linux os
if (SystemUtils.isLinuxPlatform()) {
try {
Class<?> providerClazz = Class.forName("sun.nio.ch.EPollSelectorProvider");
if (providerClazz != null) {
try {
Method method = providerClazz.getMethod("provider");
if (method != null) {
SelectorProvider selectorProvider = (SelectorProvider) method.invoke(null);
if (selectorProvider != null) {
result = selectorProvider.openSelector();
}
}
} catch (Exception e) {
// ignore
}
}
} catch (Exception e) {
// ignore
}
}
if (result == null) {
result = Selector.open();
}
return result;
}
public static final String getRawAddress(InetSocketAddress inetSocketAddress) {
InetAddress address = inetSocketAddress.getAddress();
return address != null ? address.getHostAddress() : inetSocketAddress.getHostName();
}
public static final Queue<?> createTransferQueue() {
try {
return (Queue<?>) Class.forName("java.util.concurrent.LinkedTransferQueue").newInstance();
} catch (Exception e) {
return new LinkedTransferQueue<Object>();
}
}
public static Random createRandom() {
return new Random();
}
}

View File

@@ -0,0 +1,101 @@
/*
* Written by Doug Lea with assistance from members of JCP JSR-166 Expert Group and released to the
* public domain, as explained at http://creativecommons.org/licenses/publicdomain
*/
package com.google.code.yanf4j.util;
import java.util.concurrent.*;
/**
* A {@link BlockingQueue} in which producers may wait for consumers to receive elements. A
* {@code TransferQueue} may be useful for example in message passing applications in which
* producers sometimes (using method {@code transfer}) await receipt of elements by consumers
* invoking {@code take} or {@code poll}, while at other times enqueue elements (via method
* {@code put}) without waiting for receipt. Non-blocking and time-out versions of
* {@code tryTransfer} are also available. A TransferQueue may also be queried via
* {@code hasWaitingConsumer} whether there are any threads waiting for items, which is a converse
* analogy to a {@code peek} operation.
*
* <p>
* Like any {@code BlockingQueue}, a {@code TransferQueue} may be capacity bounded. If so, an
* attempted {@code transfer} operation may initially block waiting for available space, and/or
* subsequently block waiting for reception by a consumer. Note that in a queue with zero capacity,
* such as {@link SynchronousQueue}, {@code put} and {@code transfer} are effectively synonymous.
*
* <p>
* This interface is a member of the
* <a href="{@docRoot} /../technotes/guides/collections/index.html"> Java Collections Framework</a>.
*
* @since 1.7
* @author Doug Lea
* @param <E> the type of elements held in this collection
*/
public interface TransferQueue<E> extends BlockingQueue<E> {
/**
* Transfers the specified element if there exists a consumer already waiting to receive it,
* otherwise returning {@code false} without enqueuing the element.
*
* @param e the element to transfer
* @return {@code true} if the element was transferred, else {@code false}
* @throws ClassCastException if the class of the specified element prevents it from being added
* to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified element prevents it from
* being added to this queue
*/
boolean tryTransfer(E e);
/**
* Inserts the specified element into this queue, waiting if necessary for space to become
* available and the element to be dequeued by a consumer invoking {@code take} or {@code poll}.
*
* @param e the element to transfer
* @throws InterruptedException if interrupted while waiting, in which case the element is not
* enqueued.
* @throws ClassCastException if the class of the specified element prevents it from being added
* to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified element prevents it from
* being added to this queue
*/
void transfer(E e) throws InterruptedException;
/**
* Inserts the specified element into this queue, waiting up to the specified wait time if
* necessary for space to become available and the element to be dequeued by a consumer invoking
* {@code take} or {@code poll}.
*
* @param e the element to transfer
* @param timeout how long to wait before giving up, in units of {@code unit}
* @param unit a {@code TimeUnit} determining how to interpret the {@code timeout} parameter
* @return {@code true} if successful, or {@code false} if the specified waiting time elapses
* before completion, in which case the element is not enqueued.
* @throws InterruptedException if interrupted while waiting, in which case the element is not
* enqueued.
* @throws ClassCastException if the class of the specified element prevents it from being added
* to this queue
* @throws NullPointerException if the specified element is null
* @throws IllegalArgumentException if some property of the specified element prevents it from
* being added to this queue
*/
boolean tryTransfer(E e, long timeout, TimeUnit unit) throws InterruptedException;
/**
* Returns {@code true} if there is at least one consumer waiting to dequeue an element via
* {@code take} or {@code poll}. The return value represents a momentary state of affairs.
*
* @return {@code true} if there is at least one waiting consumer
*/
boolean hasWaitingConsumer();
/**
* Returns an estimate of the number of consumers waiting to dequeue elements via {@code take} or
* {@code poll}. The return value is an approximation of a momentary state of affairs, that may be
* inaccurate if consumers have completed or given up waiting. The value may be useful for
* monitoring and heuristics, but not for synchronization control. Implementations of this method
* are likely to be noticeably slower than those for {@link #hasWaitingConsumer}.
*
* @return the number of consumers waiting to dequeue elements
*/
int getWaitingConsumerCount();
}

View File

@@ -0,0 +1,65 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package com.google.code.yanf4j.util;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Thread factory for worker thread
*
* @author dennis
*
*/
public class WorkerThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public WorkerThreadFactory(ThreadGroup group, String prefix) {
if (group == null) {
SecurityManager s = System.getSecurityManager();
this.group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
} else {
this.group = group;
}
if (prefix == null) {
this.namePrefix = "pool-" + poolNumber.getAndIncrement() + "-thread-";
} else {
this.namePrefix = prefix + "-" + poolNumber.getAndIncrement() + "-thread-";
}
}
public WorkerThreadFactory(String prefix) {
this(null, prefix);
}
public WorkerThreadFactory() {
this(null, null);
}
public Thread newThread(Runnable r) {
Thread t = new Thread(this.group, r, this.namePrefix + this.threadNumber.getAndIncrement(), 0);
t.setDaemon(true);
if (t.getPriority() != Thread.NORM_PRIORITY) {
t.setPriority(Thread.NORM_PRIORITY);
}
return t;
}
}

View File

@@ -0,0 +1,10 @@
<!--?xml version="1.0" encoding="UTF-8"?--><!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- Copyright (c) 2007 Dustin Sallings <dustin+html@spy.net> -->
<html>
<head>
<title>Yanf4j utilities</title>
</head>
<body>
<h1>Yanf4j utilities</h1>
</body>
</html>

View File

@@ -0,0 +1,11 @@
module com.googlecode.xmemcached {
requires org.slf4j;
requires static java.security.sasl;
requires static java.management;
requires static java.rmi;
requires static spring.beans;
requires static spring.context;
exports net.rubyeye.xmemcached;
exports net.rubyeye.xmemcached.exception;
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
/**
* CAS operation,encapsulate gets and cas commands,and supports retry times.
*
* @author dennis
*
*/
public interface CASOperation<T> {
/**
* Max retry times,If retry times is great than this value,xmemcached will throw TimeoutException
*
* @return
*/
public int getMaxTries();
/**
* Return the new value which you want to cas
*
* @param currentCAS
* @param currentValue
* @return expected new value
*/
public T getNewValue(long currentCAS, T currentValue);
}

View File

@@ -0,0 +1,294 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
import java.net.InetSocketAddress;
import java.util.Collection;
import java.util.concurrent.CountDownLatch;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.command.Command;
import net.rubyeye.xmemcached.command.CommandType;
import net.rubyeye.xmemcached.transcoders.Transcoder;
import net.rubyeye.xmemcached.utils.Protocol;
@SuppressWarnings("unchecked")
public interface CommandFactory {
/**
* set command factory's buffer allocator
*
* @since 1.2.0
* @param bufferAllocator
*/
public void setBufferAllocator(BufferAllocator bufferAllocator);
/**
* create a delete command
*
* @param key
* @param time
* @return
*/
public Command createDeleteCommand(final String key, final byte[] keyBytes, final int time,
long cas, boolean noreply);
/**
* create a version command
*
* @return
*/
public Command createVersionCommand(CountDownLatch latch, InetSocketAddress server);
/**
* create a flush_all command
*
* @return
*/
public Command createFlushAllCommand(CountDownLatch latch, int delay, boolean noreply);
/**
* create a stats command
*
* @return
*/
public Command createStatsCommand(InetSocketAddress server, CountDownLatch latch,
String itemName);
/**
* create a get/gets command
*
* @param key
* @param keyBytes
* @param cmdType 命令类型
* @param transcoder TODO
* @param cmdBytes 命令的字节数组,如"get".getBytes()
* @return
*/
public Command createGetCommand(final String key, final byte[] keyBytes,
final CommandType cmdType, Transcoder transcoder);
/**
* Create a multi-get command
*
* @param <T>
* @param keys
* @param latch
* @param result
* @param cmdBytes
* @param cmdType
* @param transcoder
* @return
*/
public <T> Command createGetMultiCommand(Collection<String> keys, CountDownLatch latch,
CommandType cmdType, Transcoder<T> transcoder);
/**
* create a incr/decr command
*
* @param key
* @param keyBytes
* @param delta
* @param initial
* @param expTime
* @param cmdType
* @param noreply
* @return
*/
public Command createIncrDecrCommand(final String key, final byte[] keyBytes, final long delta,
long initial, int expTime, CommandType cmdType, boolean noreply);
/**
* Create a cas command
*
* @param key
* @param keyBytes
* @param exp
* @param value
* @param cas
* @param noreply
* @param transcoder
* @return
*/
public Command createCASCommand(final String key, final byte[] keyBytes, final int exp,
final Object value, long cas, boolean noreply, Transcoder transcoder);
/**
* Create a set command
*
* @param key
* @param keyBytes
* @param exp
* @param value
* @param noreply
* @param transcoder
* @return
*/
public Command createSetCommand(final String key, final byte[] keyBytes, final int exp,
final Object value, boolean noreply, Transcoder transcoder);
/**
* create a add command
*
* @param key
* @param keyBytes
* @param exp
* @param value
* @param noreply
* @param transcoder
* @return
*/
public Command createAddCommand(final String key, final byte[] keyBytes, final int exp,
final Object value, boolean noreply, Transcoder transcoder);
/**
* create a replace command
*
* @param key
* @param keyBytes
* @param exp
* @param value
* @param noreply
* @param transcoder
* @return
*/
public Command createReplaceCommand(final String key, final byte[] keyBytes, final int exp,
final Object value, boolean noreply, Transcoder transcoder);
/**
* create a append command
*
* @param key
* @param keyBytes
* @param value
* @param noreply
* @param transcoder
* @return
*/
public Command createAppendCommand(final String key, final byte[] keyBytes, final Object value,
boolean noreply, Transcoder transcoder);
/**
* Create a prepend command
*
* @param key
* @param keyBytes
* @param value
* @param noreply
* @param transcoder
* @return
*/
public Command createPrependCommand(final String key, final byte[] keyBytes, final Object value,
boolean noreply, Transcoder transcoder);
/**
* Create a verbosity command
*
* @param latch
* @param level
* @param noreply
* @return
*/
public Command createVerbosityCommand(CountDownLatch latch, int level, boolean noreply);
/**
* Create a command for listing authentication mechanisms
*
* @param latch
* @return
*/
public Command createAuthListMechanismsCommand(CountDownLatch latch);
/**
* Create command for starting authentication
*
* @param mechanism
* @param latch
* @param authData
* @return
*/
public Command createAuthStartCommand(String mechanism, CountDownLatch latch, byte[] authData);
/**
* Create a command for stepping authentication
*
* @param mechanism
* @param latch
* @param authData
* @return
*/
public Command createAuthStepCommand(String mechanism, CountDownLatch latch, byte[] authData);
/**
* create a quit command
*
* @return
*/
public Command createQuitCommand();
/**
* Create a touch command
*
* @since 1.3.3
* @param key
* @param keyBytes
* @param latch TODO
* @param exp
* @param noreply
* @return
*/
public Command createTouchCommand(final String key, final byte[] keyBytes, CountDownLatch latch,
int exp, boolean noreply);
/**
* Create a get-and-touch command
*
* @since 1.3.3
* @param key
* @param keyBytes
* @param latch TODO
* @param exp
* @param noreply
* @return
*/
public Command createGetAndTouchCommand(final String key, final byte[] keyBytes,
CountDownLatch latch, int exp, boolean noreply);
/**
* Create a Auto discovery config command, only supports Cache Engine Version 1.4.14 or Higher.
* This method works the same for both AWS and GCP Auto Discovery.
*
* @see <a href=
* "http://docs.aws.amazon.com/AmazonElastiCache/latest/UserGuide/AutoDiscovery.AddingToYourClientLibrary.html">Adding
* AWS Auto Discovery To Your Client Library</a>
* @see <a href=
* "https://cloud.google.com/memorystore/docs/memcached/use-auto-discovery">Adding
* GCP Auto Discovery To Your Client Library</a>
* @param subCommand
* @param key
* @return
*/
public Command createAutoDiscoveryCacheConfigCommand(String subCommand, String key);
/**
* Get this client's protocol version
*
* @return
*/
public Protocol getProtocol();
}

View File

@@ -0,0 +1,149 @@
package net.rubyeye.xmemcached;
import java.util.concurrent.TimeoutException;
import net.rubyeye.xmemcached.exception.MemcachedClientException;
import net.rubyeye.xmemcached.exception.MemcachedException;
/**
* Counter,encapsulate the incr/decr methods.
*
* @author dennis
*
*/
public final class Counter {
private final MemcachedClient memcachedClient;
private final String key;
private final long initialValue;
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((this.key == null) ? 0 : this.key.hashCode());
result =
prime * result + ((this.memcachedClient == null) ? 0 : this.memcachedClient.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Counter other = (Counter) obj;
if (this.key == null) {
if (other.key != null) {
return false;
}
} else if (!this.key.equals(other.key)) {
return false;
}
if (this.memcachedClient == null) {
if (other.memcachedClient != null) {
return false;
}
} else if (!this.memcachedClient.equals(other.memcachedClient)) {
return false;
}
return true;
}
public final String getKey() {
return this.key;
}
/**
* Get current value
*
* @return
* @throws MemcachedException
* @throws InterruptedException
* @throws TimeoutException
*/
public long get() throws MemcachedException, InterruptedException, TimeoutException {
Object result = this.memcachedClient.get(this.key);
if (result == null) {
throw new MemcachedClientException("key is not existed.");
} else {
if (result instanceof Long)
return (Long) result;
else
return Long.valueOf(((String) result).trim());
}
}
/**
* Set counter's value to expected.
*
* @param value
* @throws MemcachedException
* @throws InterruptedException
* @throws TimeoutException
*/
public void set(long value) throws MemcachedException, InterruptedException, TimeoutException {
this.memcachedClient.set(this.key, 0, String.valueOf(value));
}
public Counter(MemcachedClient memcachedClient, String key, long initialValue) {
super();
this.memcachedClient = memcachedClient;
this.key = key;
this.initialValue = initialValue;
try {
this.memcachedClient.add(key, 0, String.valueOf(this.initialValue));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (Exception e) {
throw new IllegalStateException("Initialize counter failed", e);
}
}
/**
* Increase value by one
*
* @return
* @throws MemcachedException
* @throws InterruptedException
* @throws TimeoutException
*/
public long incrementAndGet() throws MemcachedException, InterruptedException, TimeoutException {
return this.memcachedClient.incr(this.key, 1, this.initialValue);
}
/**
* Decrease value by one
*
* @return
* @throws MemcachedException
* @throws InterruptedException
* @throws TimeoutException
*/
public long decrementAndGet() throws MemcachedException, InterruptedException, TimeoutException {
return this.memcachedClient.decr(this.key, 1, this.initialValue);
}
/**
* Add value and get the result
*
* @param delta
* @return
* @throws MemcachedException
* @throws InterruptedException
* @throws TimeoutException
*/
public long addAndGet(long delta)
throws MemcachedException, InterruptedException, TimeoutException {
if (delta >= 0) {
return this.memcachedClient.incr(this.key, delta, this.initialValue);
} else {
return this.memcachedClient.decr(this.key, -delta, this.initialValue);
}
}
}

View File

@@ -0,0 +1,37 @@
package net.rubyeye.xmemcached;
import java.util.concurrent.Semaphore;
/**
* Flow control for noreply operations.
*
* @author dennis<killme2008@gmail.com>
* @since 1.3.8
*
*/
public class FlowControl {
private Semaphore permits;
private int max;
public FlowControl(int permits) {
super();
this.max = permits;
this.permits = new Semaphore(permits);
}
public int max() {
return this.max;
}
public int permits() {
return this.permits.availablePermits();
}
public boolean aquire() {
return this.permits.tryAcquire();
}
public void release() {
this.permits.release();
}
}

View File

@@ -0,0 +1,75 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
/**
* Response for gets command.It's a value object.
*
* @author dennis
*
*/
public final class GetsResponse<T> {
private final long cas;
private final T value;
public GetsResponse(final long cas, final T value) {
super();
this.cas = cas;
this.value = value;
}
public long getCas() {
return this.cas;
}
public T getValue() {
return this.value;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + (int) (this.cas ^ (this.cas >>> 32));
result = prime * result + ((this.value == null) ? 0 : this.value.hashCode());
return result;
}
@Override
@SuppressWarnings("unchecked")
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
GetsResponse other = (GetsResponse) obj;
if (this.cas != other.cas) {
return false;
}
if (this.value == null) {
if (other.value != null) {
return false;
}
} else if (!this.value.equals(other.value)) {
return false;
}
return true;
}
@Override
public String toString() {
return "GetsResponse[cas=" + this.cas + ",value=" + this.value + "]";
}
}

View File

@@ -0,0 +1,223 @@
package net.rubyeye.xmemcached;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.CRC32;
import net.rubyeye.xmemcached.exception.MemcachedClientException;
import net.rubyeye.xmemcached.utils.ByteUtils;
/**
* Known hashing algorithms for locating a server for a key. Note that all hash algorithms return
* 64-bits of hash, but only the lower 32-bits are significant. This allows a positive 32-bit number
* to be returned for all cases.
*/
public enum HashAlgorithm {
/**
* Native hash (String.hashCode()).
*/
NATIVE_HASH,
/**
* CRC32_HASH as used by the perl API. This will be more consistent both across multiple API users
* as well as java versions, but is mostly likely significantly slower.
*/
CRC32_HASH,
/**
* FNV hashes are designed to be fast while maintaining a low collision rate. The FNV speed allows
* one to quickly hash lots of data while maintaining a reasonable collision rate.
*
* @see http://www.isthe.com/chongo/tech/comp/fnv/
* @see http://en.wikipedia.org/wiki/Fowler_Noll_Vo_hash
*/
FNV1_64_HASH,
/**
* Variation of FNV.
*/
FNV1A_64_HASH,
/**
* 32-bit FNV1.
*/
FNV1_32_HASH,
/**
* 32-bit FNV1a.
*/
FNV1A_32_HASH,
/**
* MD5-based hash algorithm used by ketama.
*/
KETAMA_HASH,
/**
* From mysql source
*/
MYSQL_HASH,
ELF_HASH,
RS_HASH,
/**
* From lua source,it is used for long key
*/
LUA_HASH,
ELECTION_HASH,
/**
* The Jenkins One-at-a-time hash ,please see http://www.burtleburtle.net/bob/hash/doobs.html
*/
ONE_AT_A_TIME;
private static final long FNV_64_INIT = 0xcbf29ce484222325L;
private static final long FNV_64_PRIME = 0x100000001b3L;
private static final long FNV_32_INIT = 2166136261L;
private static final long FNV_32_PRIME = 16777619;
/**
* Compute the hash for the given key.
*
* @return a positive integer hash
*/
public long hash(final String k) {
long rv = 0;
switch (this) {
case NATIVE_HASH:
rv = k.hashCode();
break;
case CRC32_HASH:
// return (crc32(shift) >> 16) & 0x7fff;
CRC32 crc32 = new CRC32();
crc32.update(ByteUtils.getBytes(k));
rv = crc32.getValue() >> 16 & 0x7fff;
break;
case FNV1_64_HASH: {
// Thanks to pierre@demartines.com for the pointer
rv = FNV_64_INIT;
int len = k.length();
for (int i = 0; i < len; i++) {
rv *= FNV_64_PRIME;
rv ^= k.charAt(i);
}
}
break;
case FNV1A_64_HASH: {
rv = FNV_64_INIT;
int len = k.length();
for (int i = 0; i < len; i++) {
rv ^= k.charAt(i);
rv *= FNV_64_PRIME;
}
}
break;
case FNV1_32_HASH: {
rv = FNV_32_INIT;
int len = k.length();
for (int i = 0; i < len; i++) {
rv *= FNV_32_PRIME;
rv ^= k.charAt(i);
}
}
break;
case FNV1A_32_HASH: {
rv = FNV_32_INIT;
int len = k.length();
for (int i = 0; i < len; i++) {
rv ^= k.charAt(i);
rv *= FNV_32_PRIME;
}
}
break;
case ELECTION_HASH:
case KETAMA_HASH:
byte[] bKey = computeMd5(k);
rv = (long) (bKey[3] & 0xFF) << 24 | (long) (bKey[2] & 0xFF) << 16
| (long) (bKey[1] & 0xFF) << 8 | bKey[0] & 0xFF;
break;
case MYSQL_HASH:
int nr2 = 4;
for (int i = 0; i < k.length(); i++) {
rv ^= ((rv & 63) + nr2) * k.charAt(i) + (rv << 8);
nr2 += 3;
}
break;
case ELF_HASH:
long x = 0;
for (int i = 0; i < k.length(); i++) {
rv = (rv << 4) + k.charAt(i);
if ((x = rv & 0xF0000000L) != 0) {
rv ^= x >> 24;
rv &= ~x;
}
}
rv = rv & 0x7FFFFFFF;
break;
case RS_HASH:
long b = 378551;
long a = 63689;
for (int i = 0; i < k.length(); i++) {
rv = rv * a + k.charAt(i);
a *= b;
}
rv = rv & 0x7FFFFFFF;
break;
case LUA_HASH:
int step = (k.length() >> 5) + 1;
rv = k.length();
for (int len = k.length(); len >= step; len -= step) {
rv = rv ^ (rv << 5) + (rv >> 2) + k.charAt(len - 1);
}
break;
case ONE_AT_A_TIME:
try {
int hash = 0;
for (byte bt : k.getBytes("utf-8")) {
hash += (bt & 0xFF);
hash += (hash << 10);
hash ^= (hash >>> 6);
}
hash += (hash << 3);
hash ^= (hash >>> 11);
hash += (hash << 15);
rv = hash;
} catch (UnsupportedEncodingException e) {
throw new IllegalStateException("Hash function error", e);
}
break;
default:
assert false;
}
return rv & 0xffffffffL; /* Convert to unsigned 32-bits */
}
private static ThreadLocal<MessageDigest> md5Local = new ThreadLocal<MessageDigest>();
/**
* Get the md5 of the given key.
*/
public static byte[] computeMd5(String k) {
MessageDigest md5 = md5Local.get();
if (md5 == null) {
try {
md5 = MessageDigest.getInstance("MD5");
md5Local.set(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("MD5 not supported", e);
}
}
md5.reset();
md5.update(ByteUtils.getBytes(k));
return md5.digest();
}
// public static void main(String[] args) {
// HashAlgorithm alg=HashAlgorithm.CRC32_HASH;
// long h=0;
// long start=System.currentTimeMillis();
// for(int i=0;i<100000;i++)
// h=alg.hash("MYSQL_HASH");
// System.out.println(System.currentTimeMillis()-start);
// }
}

View File

@@ -0,0 +1,61 @@
package net.rubyeye.xmemcached;
import java.net.InetSocketAddress;
import java.util.concurrent.TimeoutException;
import net.rubyeye.xmemcached.exception.MemcachedException;
/**
* Key Iterator for memcached,use 'stats items' and 'stats cachedump' to iterate all keys,it is
* inefficient and not thread-safe.The 'stats cachedump" has length limitation,then iterator could
* not visit all keys if you have many keys.</br>
* <p>
* <strong>Note</strong>: memcached 1.6.x will remove cachedump stats,so this feature will be
* invalid in memcached 1.6.x
* </p>
*
* @deprecated memcached 1.6.x will remove cachedump stats command,so this feature will be removed
* in the future
*
* @author dennis
*
*/
@Deprecated
public interface KeyIterator {
/**
* Get next key,if iterator has reached the end,throw ArrayIndexOutOfBoundsException
*
* @return
* @throws ArrayIndexOutOfBoundsException
* ,MemcachedException,TimeoutException,InterruptedException
*
*/
public String next() throws MemcachedException, TimeoutException, InterruptedException;
/**
* Check if the iterator has more keys.
*
* @return
*/
public boolean hasNext();
/**
* Close this iterator when you don't need it any more.It is not mandatory to call this method,
* but you might want to invoke this method for maximum performance.
*/
public void close();
/**
* Get current iterator's memcached server address
*
* @return
*/
public InetSocketAddress getServerAddress();
/**
* Set operation timeout,default is 1000 MILLISECONDS.
*
* @param opTimeout
*/
public void setOpTimeout(long opTimeout);
}

View File

@@ -0,0 +1,17 @@
package net.rubyeye.xmemcached;
/**
* Key provider to pre-process keys before sending to memcached.
*
* @author dennis<killme2008@gmail.com>
* @since 1.3.8
*/
public interface KeyProvider {
/**
* Processes key and returns a new key.
*
* @param key
* @return
*/
public String process(String key);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,294 @@
package net.rubyeye.xmemcached;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.List;
import java.util.Map;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.transcoders.Transcoder;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.SocketOption;
/**
* Builder pattern.Configure XmemcachedClient's options,then build it
*
* @author dennis
*
*/
public interface MemcachedClientBuilder {
/**
*
* @return net.rubyeye.xmemcached.MemcachedSessionLocator
*/
public MemcachedSessionLocator getSessionLocator();
/**
* Set the XmemcachedClient's session locator.Use ArrayMemcachedSessionLocator by default.If you
* want to choose consistent hash strategy,set it to KetamaMemcachedSessionLocator
*
* @param sessionLocator
*/
public void setSessionLocator(MemcachedSessionLocator sessionLocator);
/**
*
* @return net.rubyeye.xmemcached.MemcachedSessionComparator
*/
public MemcachedSessionComparator getSessionComparator();
/**
* Set the XmemcachedClient's session comparator.Use IndexMemcachedSessionComparator by default.
*
* @param sessionComparator
*/
public void setSessionComparator(MemcachedSessionComparator sessionComparator);
public BufferAllocator getBufferAllocator();
/**
* Set nio ByteBuffer's allocator.Use SimpleBufferAllocator by default.You can choose
* CachedBufferAllocator.
*
* @param bufferAllocator
*/
public void setBufferAllocator(BufferAllocator bufferAllocator);
/**
* Return the default networking's configuration,you can change them.
*
* @return
*/
public Configuration getConfiguration();
/**
* Set the XmemcachedClient's networking configuration(reuseAddr,receiveBufferSize,tcpDelay etc.)
*
* @param configuration
*/
public void setConfiguration(Configuration configuration);
/**
* Build MemcachedClient by current options.
*
* @return
* @throws IOException
*/
public MemcachedClient build() throws IOException;
/**
* In a high concurrent enviroment,you may want to pool memcached clients.But a xmemcached client
* has to start a reactor thread and some thread pools,if you create too many clients,the cost is
* very large. Xmemcached supports connection pool instead of client pool.you can create more
* connections to one or more memcached servers,and these connections share the same reactor and
* thread pools,it will reduce the cost of system.
*
* @param poolSize pool size,default is 1
*/
public void setConnectionPoolSize(int poolSize);
/**
* Set xmemcached's transcoder,it is used for seriailizing
*
* @return
*/
@SuppressWarnings("unchecked")
public Transcoder getTranscoder();
@SuppressWarnings("unchecked")
public void setTranscoder(Transcoder transcoder);
/**
* get xmemcached's command factory
*
* @return
*/
public CommandFactory getCommandFactory();
/**
* Add a state listener
*
* @param stateListener
*/
public void addStateListener(MemcachedClientStateListener stateListener);
/**
* Remove a state listener
*
* @param stateListener
*/
public void removeStateListener(MemcachedClientStateListener stateListener);
/**
* Set state listeners,replace current list
*
* @param stateListeners
*/
public void setStateListeners(List<MemcachedClientStateListener> stateListeners);
/**
* set xmemcached's command factory.Default is TextCommandFactory,which implements memcached text
* protocol.
*
* @param commandFactory
*/
public void setCommandFactory(CommandFactory commandFactory);
/**
* Set tcp socket option
*
* @param socketOption
* @param value
*/
@SuppressWarnings("unchecked")
public void setSocketOption(SocketOption socketOption, Object value);
/**
* Get all tcp socket options
*
* @return
*/
@SuppressWarnings("unchecked")
public Map<SocketOption, Object> getSocketOptions();
/**
* Configure auth info
*
* @param map Auth info map,key is memcached server address,and value is the auth info for the
* key.
*/
public void setAuthInfoMap(Map<InetSocketAddress, AuthInfo> map);
/**
* return current all auth info
*
* @return Auth info map,key is memcached server address,and value is the auth info for the key.
*/
public Map<InetSocketAddress, AuthInfo> getAuthInfoMap();
/**
* Add auth info for memcached server
*
* @param address
* @param authInfo
*/
public void addAuthInfo(InetSocketAddress address, AuthInfo authInfo);
/**
* Remove auth info for memcached server
*
* @param address
*/
public void removeAuthInfo(InetSocketAddress address);
/**
* Return the cache instance name
*
* @return
*/
public String getName();
/**
* Set cache instance name
*
* @param name
*/
public void setName(String name);
/**
* Configure wheather to set client in failure mode.If set it to true,that means you want to
* configure client in failure mode. Failure mode is that when a memcached server is down,it would
* not taken from the server list but marked as unavailable,and then further requests to this
* server will be transformed to standby node if configured or throw an exception until it comes
* back up.
*
* @param failureMode true is to configure client in failure mode.
*/
public void setFailureMode(boolean failureMode);
/**
* Returns if client is in failure mode.
*
* @return
*/
public boolean isFailureMode();
/**
* Returns connect timeout in milliseconds
*
* @return connect timeout
*/
public long getConnectTimeout();
/**
* Set connect timeout in milliseconds
*
* @see net.rubyeye.xmemcached.MemcachedClient#DEFAULT_CONNECT_TIMEOUT
*
* @param connectTimeout
*/
public void setConnectTimeout(long connectTimeout);
/**
* Enables/disables sanitizing keys by URLEncoding.
*
* @param sanitizeKey if true, then URLEncode all keys
*/
public void setSanitizeKeys(boolean sanitizeKeys);
/**
* Set a key provider for pre-processing keys before sending them to memcached.
*
* @since 1.3.8
* @param keyProvider
*/
public void setKeyProvider(KeyProvider keyProvider);
/**
* Set max queued noreply operations number
*
* @see MemcachedClient#DEFAULT_MAX_QUEUED_NOPS
* @param maxQueuedNoReplyOperations
* @since 1.3.8
*/
public void setMaxQueuedNoReplyOperations(int maxQueuedNoReplyOperations);
/**
* If the memcached dump or network error cause connection closed,xmemcached would try to heal the
* connection.The interval between reconnections is 2 seconds by default. You can change that
* value by this method.
*
* @since 1.3.9
* @param healConnectionInterval MILLISECONDS
*/
public void setHealSessionInterval(long healConnectionInterval);
/**
* If the memcached dump or network error cause connection closed,xmemcached would try to heal the
* connection.You can disable this behaviour by using this method:<br/>
* <code> client.setEnableHealSession(false); </code><br/>
* The default value is true.
*
* @param enableHealSession
* @since 1.3.9
*/
public void setEnableHealSession(boolean enableHealSession);
/**
* Set default operation timeout.
*
* @param opTimeout Operation timeout value in milliseconds.
* @since 1.4.1
*/
public void setOpTimeout(long opTimeout);
/**
* Returns the default operation timeout in milliseconds.
*
* @since 1.4.1
* @return
*/
public long getOpTimeout();
}

View File

@@ -0,0 +1,30 @@
package net.rubyeye.xmemcached;
import java.util.concurrent.TimeoutException;
import net.rubyeye.xmemcached.exception.MemcachedException;
/**
* MemcachedClient callable when using namespace in xmemcached.For example:
*
* <pre>
* memcachedClient.withNamespace(userId,new MemcachedClientCallable<Void>{
* public Void call(MemcachedClient client) throws MemcachedException,
* InterruptedException, TimeoutException{
* client.set("username",0,username);
* client.set("email",0,email);
* return null;
* }
* });
* //invalidate all items under the namespace.
* memcachedClient.invalidateNamespace(userId);
* </pre>
*
* @author dennis<killme2008@gmail.com>
* @see MemcachedClient#withNamespace(String, MemcachedClientCallable)
* @since 1.4.2
* @param <T>
*/
public interface MemcachedClientCallable<T> {
public T call(MemcachedClient client)
throws MemcachedException, InterruptedException, TimeoutException;
}

View File

@@ -0,0 +1,59 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
import java.net.InetSocketAddress;
/**
* MemcachedClient state listener.When client startup,shutdown,connected to a memcached server or
* disconnected happened,client will notify the listener instance which implemented this
* interface.Please don't do any operations which may block in these callback methods.
*
* @author dennis
*
*/
public interface MemcachedClientStateListener {
/**
* After client is started.
*
* @param memcachedClient
*/
public void onStarted(MemcachedClient memcachedClient);
/**
* After client is shutdown.
*
* @param memcachedClient
*/
public void onShutDown(MemcachedClient memcachedClient);
/**
* After a memcached server is connected,don't do any operations may block here.
*
* @param memcachedClient
* @param inetSocketAddress
*/
public void onConnected(MemcachedClient memcachedClient, InetSocketAddress inetSocketAddress);
/**
* After a memcached server is disconnected,don't do any operations may block here.
*
* @param memcachedClient
* @param inetSocketAddress
*/
public void onDisconnected(MemcachedClient memcachedClient, InetSocketAddress inetSocketAddress);
/**
* When exceptions occur
*
* @param memcachedClient
* @param throwable
*/
public void onException(MemcachedClient memcachedClient, Throwable throwable);
}

View File

@@ -0,0 +1,35 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
import java.util.Queue;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.command.Command;
/**
* xmemcached Optimizer
*
* @author dennis
*/
public interface MemcachedOptimizer {
@SuppressWarnings("unchecked")
Command optimize(final Command currentCommand, final Queue writeQueue,
final Queue<Command> executingCmds, int sendBufferSize);
@Deprecated
public void setBufferAllocator(BufferAllocator bufferAllocator);
}

View File

@@ -0,0 +1,28 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
import java.util.Comparator;
import com.google.code.yanf4j.core.Session;
/**
* Session comparator.
*
* @author Jungsub Shin
*
*/
public interface MemcachedSessionComparator extends Comparator<Session> {
/**
* Returns a session by special key.
*
* @param key
* @return
*/
public int compare(Session o1, Session o2);
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright [2009-2010] [dennis zhuang(killme2008@gmail.com)] Licensed under the Apache License,
* Version 2.0 (the "License"); you may not use this file except in compliance with the License. You
* may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by
* applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing permissions and limitations under the License
*/
package net.rubyeye.xmemcached;
import java.util.Collection;
import com.google.code.yanf4j.core.Session;
/**
* Session locator.Find session by key.
*
* @author dennis
*
*/
public interface MemcachedSessionLocator {
/**
* Returns a session by special key.
*
* @param key
* @return
*/
public Session getSessionByKey(final String key);
/**
* Update sessions when session was added or removed.
*
* @param list The newer sessions
*/
public void updateSessions(final Collection<Session> list);
/**
* Configure failure mode
*
* @param failureMode true is using failure mode
*/
public void setFailureMode(boolean failureMode);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,465 @@
package net.rubyeye.xmemcached;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import net.rubyeye.xmemcached.auth.AuthInfo;
import net.rubyeye.xmemcached.buffer.BufferAllocator;
import net.rubyeye.xmemcached.buffer.SimpleBufferAllocator;
import net.rubyeye.xmemcached.command.TextCommandFactory;
import net.rubyeye.xmemcached.impl.ArrayMemcachedSessionLocator;
import net.rubyeye.xmemcached.impl.DefaultKeyProvider;
import net.rubyeye.xmemcached.impl.IndexMemcachedSessionComparator;
import net.rubyeye.xmemcached.impl.RandomMemcachedSessionLocaltor;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
import net.rubyeye.xmemcached.transcoders.Transcoder;
import net.rubyeye.xmemcached.utils.AddrUtil;
import net.rubyeye.xmemcached.utils.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.code.yanf4j.config.Configuration;
import com.google.code.yanf4j.core.SocketOption;
import com.google.code.yanf4j.core.impl.StandardSocketOption;
/**
* Builder pattern.Configure XmemcachedClient's options,then build it
*
* @author dennis
*
*/
public class XMemcachedClientBuilder implements MemcachedClientBuilder {
private static final Logger log = LoggerFactory.getLogger(XMemcachedClientBuilder.class);
protected MemcachedSessionLocator sessionLocator = new ArrayMemcachedSessionLocator();
protected MemcachedSessionComparator sessionComparator = new IndexMemcachedSessionComparator();
protected BufferAllocator bufferAllocator = new SimpleBufferAllocator();
protected Configuration configuration = getDefaultConfiguration();
protected Map<InetSocketAddress, InetSocketAddress> addressMap =
new LinkedHashMap<InetSocketAddress, InetSocketAddress>();
protected int[] weights;
protected long connectTimeout = MemcachedClient.DEFAULT_CONNECT_TIMEOUT;
protected int connectionPoolSize = MemcachedClient.DEFAULT_CONNECTION_POOL_SIZE;
@SuppressWarnings("unchecked")
protected final Map<SocketOption, Object> socketOptions = getDefaultSocketOptions();
protected List<MemcachedClientStateListener> stateListeners =
new ArrayList<MemcachedClientStateListener>();
protected Map<InetSocketAddress, AuthInfo> authInfoMap =
new HashMap<InetSocketAddress, AuthInfo>();
protected String name;
protected boolean failureMode;
protected boolean sanitizeKeys;
protected KeyProvider keyProvider = DefaultKeyProvider.INSTANCE;
protected int maxQueuedNoReplyOperations = MemcachedClient.DEFAULT_MAX_QUEUED_NOPS;
protected long healSessionInterval = MemcachedClient.DEFAULT_HEAL_SESSION_INTERVAL;
protected boolean enableHealSession = true;
protected long opTimeout = MemcachedClient.DEFAULT_OP_TIMEOUT;
protected boolean resolveInetAddresses = true;
public boolean isResolveInetAddresses() {
return resolveInetAddresses;
}
public void setResolveInetAddresses(boolean resolveInetAddresses) {
this.resolveInetAddresses = resolveInetAddresses;
}
public void doNotResolveInetAddresses() {
this.resolveInetAddresses = false;
}
public long getOpTimeout() {
return opTimeout;
}
public void setOpTimeout(long opTimeout) {
if (opTimeout <= 0)
throw new IllegalArgumentException("Invalid opTimeout value:" + opTimeout);
this.opTimeout = opTimeout;
}
public int getMaxQueuedNoReplyOperations() {
return maxQueuedNoReplyOperations;
}
public long getHealSessionInterval() {
return healSessionInterval;
}
public void setHealSessionInterval(long healSessionInterval) {
this.healSessionInterval = healSessionInterval;
}
public boolean isEnableHealSession() {
return enableHealSession;
}
public void setEnableHealSession(boolean enableHealSession) {
this.enableHealSession = enableHealSession;
}
/**
* Set max queued noreply operations number
*
* @see MemcachedClient#DEFAULT_MAX_QUEUED_NOPS
* @param maxQueuedNoReplyOperations
* @since 1.3.8
*/
public void setMaxQueuedNoReplyOperations(int maxQueuedNoReplyOperations) {
this.maxQueuedNoReplyOperations = maxQueuedNoReplyOperations;
}
public void setSanitizeKeys(boolean sanitizeKeys) {
this.sanitizeKeys = sanitizeKeys;
}
public void addStateListener(MemcachedClientStateListener stateListener) {
this.stateListeners.add(stateListener);
}
@SuppressWarnings("unchecked")
public void setSocketOption(SocketOption socketOption, Object value) {
if (socketOption == null) {
throw new NullPointerException("Null socketOption");
}
if (value == null) {
throw new NullPointerException("Null value");
}
if (!socketOption.type().equals(value.getClass())) {
throw new IllegalArgumentException("Expected " + socketOption.type().getSimpleName()
+ " value,but givend " + value.getClass().getSimpleName());
}
this.socketOptions.put(socketOption, value);
}
@SuppressWarnings("unchecked")
public Map<SocketOption, Object> getSocketOptions() {
return this.socketOptions;
}
public final void setConnectionPoolSize(int poolSize) {
if (this.connectionPoolSize <= 0) {
throw new IllegalArgumentException("poolSize<=0");
}
this.connectionPoolSize = poolSize;
}
public void removeStateListener(MemcachedClientStateListener stateListener) {
this.stateListeners.remove(stateListener);
}
public long getConnectTimeout() {
return connectTimeout;
}
public void setConnectTimeout(long connectTimeout) {
this.connectTimeout = connectTimeout;
}
public void setStateListeners(List<MemcachedClientStateListener> stateListeners) {
if (stateListeners == null) {
throw new IllegalArgumentException("Null state listeners");
}
this.stateListeners = stateListeners;
}
protected CommandFactory commandFactory = new TextCommandFactory();
@SuppressWarnings("unchecked")
public static final Map<SocketOption, Object> getDefaultSocketOptions() {
Map<SocketOption, Object> map = new HashMap<SocketOption, Object>();
map.put(StandardSocketOption.TCP_NODELAY, MemcachedClient.DEFAULT_TCP_NO_DELAY);
map.put(StandardSocketOption.SO_RCVBUF, MemcachedClient.DEFAULT_TCP_RECV_BUFF_SIZE);
map.put(StandardSocketOption.SO_KEEPALIVE, MemcachedClient.DEFAULT_TCP_KEEPLIVE);
map.put(StandardSocketOption.SO_SNDBUF, MemcachedClient.DEFAULT_TCP_SEND_BUFF_SIZE);
map.put(StandardSocketOption.SO_LINGER, 0);
map.put(StandardSocketOption.SO_REUSEADDR, true);
return map;
}
public static final Configuration getDefaultConfiguration() {
final Configuration configuration = new Configuration();
configuration.setSessionReadBufferSize(MemcachedClient.DEFAULT_SESSION_READ_BUFF_SIZE);
configuration.setReadThreadCount(MemcachedClient.DEFAULT_READ_THREAD_COUNT);
configuration.setSessionIdleTimeout(MemcachedClient.DEFAULT_SESSION_IDLE_TIMEOUT);
configuration.setWriteThreadCount(0);
return configuration;
}
public boolean isFailureMode() {
return this.failureMode;
}
public void setFailureMode(boolean failureMode) {
this.failureMode = failureMode;
}
public final CommandFactory getCommandFactory() {
return this.commandFactory;
}
public final void setCommandFactory(CommandFactory commandFactory) {
this.commandFactory = commandFactory;
}
@SuppressWarnings({"rawtypes"})
protected Transcoder transcoder = new SerializingTranscoder();
public XMemcachedClientBuilder(String addressList) {
this(AddrUtil.getAddresses(addressList));
}
public XMemcachedClientBuilder(List<InetSocketAddress> addressList) {
if (addressList != null) {
for (InetSocketAddress addr : addressList) {
this.addressMap.put(addr, null);
}
}
}
public XMemcachedClientBuilder(List<InetSocketAddress> addressList, int[] weights) {
if (addressList != null) {
for (InetSocketAddress addr : addressList) {
this.addressMap.put(addr, null);
}
}
this.weights = weights;
}
public XMemcachedClientBuilder(Map<InetSocketAddress, InetSocketAddress> addressMap) {
this.addressMap = addressMap;
}
public XMemcachedClientBuilder(Map<InetSocketAddress, InetSocketAddress> addressMap,
int[] weights) {
this.addressMap = addressMap;
this.weights = weights;
}
public XMemcachedClientBuilder() {
this((Map<InetSocketAddress, InetSocketAddress>) null);
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#getSessionLocator()
*/
public MemcachedSessionLocator getSessionLocator() {
return this.sessionLocator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setSessionLocator(net. rubyeye
* .xmemcached.MemcachedSessionLocator)
*/
public void setSessionLocator(MemcachedSessionLocator sessionLocator) {
if (sessionLocator == null) {
throw new IllegalArgumentException("Null SessionLocator");
}
this.sessionLocator = sessionLocator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#getSessionComparator()
*/
public MemcachedSessionComparator getSessionComparator() {
return this.sessionComparator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setSessionComparator(net. rubyeye
* .xmemcached.MemcachedSessionComparator)
*/
public void setSessionComparator(MemcachedSessionComparator sessionComparator) {
if (sessionComparator == null) {
throw new IllegalArgumentException("Null SessionComparator");
}
this.sessionComparator = sessionComparator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#getBufferAllocator()
*/
public BufferAllocator getBufferAllocator() {
return this.bufferAllocator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setBufferAllocator(net.
* rubyeye.xmemcached.buffer.BufferAllocator)
*/
public void setBufferAllocator(BufferAllocator bufferAllocator) {
if (bufferAllocator == null) {
throw new IllegalArgumentException("Null bufferAllocator");
}
this.bufferAllocator = bufferAllocator;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#getConfiguration()
*/
public Configuration getConfiguration() {
return this.configuration;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setConfiguration(com.google
* .code.yanf4j.config.Configuration)
*/
public void setConfiguration(Configuration configuration) {
this.configuration = configuration;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#build()
*/
public MemcachedClient build() throws IOException {
XMemcachedClient memcachedClient;
// kestrel protocol use random session locator.
if (this.commandFactory.getProtocol() == Protocol.Kestrel) {
if (!(this.sessionLocator instanceof RandomMemcachedSessionLocaltor)) {
log.warn(
"Recommend to use `net.rubyeye.xmemcached.impl.RandomMemcachedSessionLocaltor` as session locator for kestrel protocol.");
}
}
if (this.weights == null) {
memcachedClient =
new XMemcachedClient(this.sessionLocator, this.sessionComparator, this.bufferAllocator,
this.configuration, this.socketOptions, this.commandFactory, this.transcoder,
this.addressMap, this.stateListeners, this.authInfoMap, this.connectionPoolSize,
this.connectTimeout, this.name, this.failureMode, this.resolveInetAddresses);
} else {
if (this.addressMap == null) {
throw new IllegalArgumentException("Null Address map");
}
if (this.addressMap.size() > this.weights.length) {
throw new IllegalArgumentException("Weights Array's length is less than server's number");
}
memcachedClient = new XMemcachedClient(this.sessionLocator, this.sessionComparator,
this.bufferAllocator, this.configuration, this.socketOptions, this.commandFactory,
this.transcoder, this.addressMap, this.weights, this.stateListeners, this.authInfoMap,
this.connectionPoolSize, this.connectTimeout, this.name, this.failureMode,
this.resolveInetAddresses);
}
this.configureClient(memcachedClient);
return memcachedClient;
}
protected void configureClient(XMemcachedClient memcachedClient) {
if (this.commandFactory.getProtocol() == Protocol.Kestrel) {
memcachedClient.setOptimizeGet(false);
}
memcachedClient.setConnectTimeout(connectTimeout);
memcachedClient.setSanitizeKeys(sanitizeKeys);
memcachedClient.setKeyProvider(this.keyProvider);
memcachedClient.setOpTimeout(this.opTimeout);
memcachedClient.setHealSessionInterval(this.healSessionInterval);
memcachedClient.setEnableHealSession(this.enableHealSession);
memcachedClient.setMaxQueuedNoReplyOperations(this.maxQueuedNoReplyOperations);
}
@SuppressWarnings("rawtypes")
public Transcoder getTranscoder() {
return this.transcoder;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setTranscoder(transcoder)
*/
public void setTranscoder(Transcoder transcoder) {
if (transcoder == null) {
throw new IllegalArgumentException("Null Transcoder");
}
this.transcoder = transcoder;
}
public Map<InetSocketAddress, AuthInfo> getAuthInfoMap() {
return this.authInfoMap;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setKeyProvider()
*/
public void setKeyProvider(KeyProvider keyProvider) {
if (keyProvider == null)
throw new IllegalArgumentException("null key provider");
this.keyProvider = keyProvider;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#addAuthInfo()
*/
public void addAuthInfo(InetSocketAddress address, AuthInfo authInfo) {
this.authInfoMap.put(address, authInfo);
}
public void removeAuthInfo(InetSocketAddress address) {
this.authInfoMap.remove(address);
}
public void setAuthInfoMap(Map<InetSocketAddress, AuthInfo> authInfoMap) {
this.authInfoMap = authInfoMap;
}
public String getName() {
return this.name;
}
/*
* (non-Javadoc)
*
* @see net.rubyeye.xmemcached.MemcachedClientBuilder#setName()
*/
public void setName(String name) {
this.name = name;
}
public void setSelectorPoolSize(int selectorPoolSize) {
getConfiguration().setSelectorPoolSize(selectorPoolSize);
}
}

View File

@@ -0,0 +1,61 @@
package net.rubyeye.xmemcached;
import java.io.IOException;
import java.util.List;
/**
* XMemcachedClientMBean.It is used for JMX to add/remove memcached server.
*
* @author dennis
*
*/
public interface XMemcachedClientMBean {
/**
* Add memcached servers
*
* @param host a String in the form of "[host1]:[port1],[host2]:[port2]
* [host3]:[port3],[host4]:[port4]"
*/
public void addServer(String hostList) throws IOException;
/**
* Add a memcached server
*
* @param server a String in the form of "[host1]:[port1],[host2]:[port2]"
* @param weight server's weight
*/
public void addOneServerWithWeight(String server, int weight) throws IOException;
/**
* Remove memcached servers
*
* @param host a string in the form of "[host1]:[port1],[host2]:[port2]
* [host3]:[port3],[host4]:[port4]"
*/
public void removeServer(String hostList);
/**
* Get all connected memcached servers
*
* @return a list of string,every string is in the form of "[host1]:[port1](weight=num1)
* [host2]:[port2](weight=num1)"
*/
public List<String> getServersDescription();
/**
* Set a memcached server's weight
*
* @param server
* @param weight
*/
public void setServerWeight(String server, int weight);
/**
* Return the cache instance name
*
* @return
*/
public String getName();
}

View File

@@ -0,0 +1,85 @@
package net.rubyeye.xmemcached.auth;
import javax.security.auth.callback.CallbackHandler;
/**
* Authentication infomation for a memcached server
*
* @author dennis
*
*/
public class AuthInfo {
private final CallbackHandler callbackHandler;
private final String[] mechanisms;
private final int maxAttempts =
Integer.parseInt(System.getProperty("net.rubyeye.xmemcached.auth_max_attempts", "-1"));
private int attempts;
public synchronized boolean isValid() {
return this.attempts <= this.maxAttempts || this.maxAttempts < 0;
}
public synchronized boolean isFirstTime() {
return this.attempts == 0;
}
public synchronized void increaseAttempts() {
this.attempts++;
}
public AuthInfo(CallbackHandler callbackHandler, String[] mechanisms) {
super();
this.callbackHandler = callbackHandler;
this.mechanisms = mechanisms;
}
public int getMaxAttempts() {
return maxAttempts;
}
/**
* Get a typical auth descriptor for PLAIN auth with the given username and password.
*
* @param u the username
* @param p the password
*
* @return an AuthInfo
*/
public static AuthInfo plain(String username, String password) {
return new AuthInfo(new PlainCallbackHandler(username, password), new String[] {"PLAIN"});
}
/**
* Get a typical auth descriptor for CRAM-MD5 auth with the given username and password.
*
* @param u the username
* @param p the password
*
* @return an AuthInfo
*/
public static AuthInfo cramMD5(String username, String password) {
return new AuthInfo(new PlainCallbackHandler(username, password), new String[] {"CRAM-MD5"});
}
/**
* Get a typical auth descriptor for CRAM-MD5 or PLAIN auth with the given username and password.
*
* @param u the username
* @param p the password
*
* @return an AuthInfo
*/
public static AuthInfo typical(String username, String password) {
return new AuthInfo(new PlainCallbackHandler(username, password),
new String[] {"CRAM-MD5", "PLAIN"});
}
public CallbackHandler getCallbackHandler() {
return callbackHandler;
}
public String[] getMechanisms() {
return mechanisms;
}
}

View File

@@ -0,0 +1,42 @@
package net.rubyeye.xmemcached.auth;
import java.net.InetSocketAddress;
import java.util.Map;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.impl.MemcachedTCPSession;
import net.rubyeye.xmemcached.networking.MemcachedSession;
import net.rubyeye.xmemcached.networking.MemcachedSessionConnectListener;
import net.rubyeye.xmemcached.utils.AddrUtil;
/**
* Client state listener for auth
*
* @author dennis
*
*/
public class AuthMemcachedConnectListener implements MemcachedSessionConnectListener {
public void onConnect(MemcachedSession session, MemcachedClient client) {
MemcachedTCPSession tcpSession = (MemcachedTCPSession) session;
Map<String, AuthInfo> authInfoMap = client.getAuthInfoStringMap();
if (authInfoMap != null) {
AuthInfo authInfo =
authInfoMap.get(AddrUtil.getServerString(tcpSession.getRemoteSocketAddress()));
if (authInfo != null) {
XMemcachedClient xMemcachedClient = (XMemcachedClient) client;
AuthTask task = new AuthTask(authInfo, xMemcachedClient.getCommandFactory(), tcpSession);
task.start();
// First time,try to wait
if (authInfo.isFirstTime()) {
try {
task.join(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More