/*
* <p>
* 版权: ©2011
* </p>
*/
package org.young.isocket.client;
import java.io.IOException;
import java.net.SocketAddress;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.net.ssl.SSLEngineResult;
import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import org.glassfish.grizzly.Connection;
import org.glassfish.grizzly.GrizzlyFuture;
import org.glassfish.grizzly.WriteResult;
import org.glassfish.grizzly.filterchain.Filter;
import org.glassfish.grizzly.filterchain.FilterChainBuilder;
import org.glassfish.grizzly.filterchain.TransportFilter;
import org.glassfish.grizzly.impl.SafeFutureImpl;
import org.glassfish.grizzly.nio.transport.TCPNIOTransport;
import org.glassfish.grizzly.nio.transport.TCPNIOTransportBuilder;
import org.glassfish.grizzly.ssl.SSLContextConfigurator;
import org.glassfish.grizzly.ssl.SSLEngineConfigurator;
import org.glassfish.grizzly.ssl.SSLFilter;
import org.glassfish.grizzly.utils.StringFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.young.icore.util.Assert;
import org.young.icore.util.ClassLoaderUtils;
import org.young.isocket.filter.ClientAuthFilter;
import org.young.isocket.filter.ClientFutureFilter;
import org.young.isocket.filter.ClientSSLFilter;
import org.young.isocket.filter.ClientStringDataFilter;
import org.young.isocket.service.ServiceRequest;
import org.young.isocket.service.ServiceResponse;
import org.young.isocket.util.SocketKeys;
/**
* <p>
*
* </p>
*
* @see
* @author yangjun2
* @email yangjun1120@gmail.com
*
*/
public class NIOSocketClient {
private static final Logger logger = LoggerFactory
.getLogger(NIOSocketClient.class);
private TCPNIOTransportBuilder tcpNIOTransportBuilder;
private TCPNIOTransport transport;
private Connection connection;
private ClientFutureFilter clientFutureFilter;
private ClientSSLFilter clientSSLFilter;
private FilterChainBuilder filterChainBuilder;
private String userName;
private String password;
private boolean needSSL;
private boolean needAuth;
public boolean isNeedAuth() {
return needAuth;
}
public void setNeedAuth(boolean needAuth) {
this.needAuth = needAuth;
}
private String trustStoreFile;
private String trustStorePassword;
private String keyStoreFile;
private String keyStorePassword;
public String getTrustStoreFile() {
return trustStoreFile;
}
public void setTrustStoreFile(String trustStoreFile) {
this.trustStoreFile = trustStoreFile;
}
public String getTrustStorePassword() {
return trustStorePassword;
}
public void setTrustStorePassword(String trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}
public String getKeyStoreFile() {
return keyStoreFile;
}
public void setKeyStoreFile(String keyStoreFile) {
this.keyStoreFile = keyStoreFile;
}
public String getKeyStorePassword() {
return keyStorePassword;
}
public void setKeyStorePassword(String keyStorePassword) {
this.keyStorePassword = keyStorePassword;
}
public boolean isNeedSSL() {
return needSSL;
}
public void setNeedSSL(boolean needSSL) {
this.needSSL = needSSL;
}
public NIOSocketClient() {
this(null, null);
}
public NIOSocketClient(String userName, String password) {
this.userName = userName;
this.password = password;
tcpNIOTransportBuilder = TCPNIOTransportBuilder.newInstance();
}
private void init() {
clientFutureFilter = new ClientFutureFilter();
// Create a FilterChain using FilterChainBuilder
filterChainBuilder = FilterChainBuilder.stateless();
// Add TransportFilter, which is responsible
// for reading and writing data to the connection
filterChainBuilder.add(new TransportFilter());
SSLFilter sslFilter = null;
if (isNeedSSL()) {
// Initialize and add SSLFilter
final SSLEngineConfigurator serverConfig = initializeSSL();
final SSLEngineConfigurator clientConfig = serverConfig.copy()
.setClientMode(true);
sslFilter = new SSLFilter(serverConfig, clientConfig);
filterChainBuilder.add(sslFilter);
}
filterChainBuilder.add(new StringFilter(Charset.forName("UTF-8"),
SocketKeys.STRING_TERMINATING_SYMB));
filterChainBuilder.add(new ClientStringDataFilter());
if (isNeedAuth()) {
Assert.notNull(this.userName, "user name must not be null");
Assert.notNull(this.password, "password must not be null");
filterChainBuilder.add(new ClientAuthFilter(this.userName,
this.password));
}
if (isNeedSSL()) {
clientSSLFilter = new ClientSSLFilter(sslFilter);
filterChainBuilder.add(clientSSLFilter);
}
filterChainBuilder.add(clientFutureFilter);
}
/**
*
* <p>
* 描述:设置客户端超时时间 ,单位milliseconds
* </p>
*
* @param
* @return
* @throws
* @see
* @since %I%
*/
public void setSoTimeout(int clientSocketSoTimeout) {
tcpNIOTransportBuilder.setClientSocketSoTimeout(clientSocketSoTimeout);
}
public int getSoTimeout() {
return tcpNIOTransportBuilder.getClientSocketSoTimeout();
}
/**
*
* <p>
* 描述:建立连接的超时时间
* </p>
*
* @param
* @return
* @throws
* @see
* @since %I%
*/
public void setConnectionTimeout(int connectionTimeout) {
tcpNIOTransportBuilder.setConnectionTimeout(connectionTimeout);
}
/**
*
* <p>
* 描述:当 SO_KEEPALIVE 选项为 true 时, 表示底层的TCP 实现会监视该连接是否有效.
* 当连接处于空闲状态(连接的两端没有互相传送数据) 超过了 2 小时时, 本地的TCP 实现 会发送一个数据包给远程的 Socket.
* 如果远程Socket 没有发回响应, TCP实现就会持续 尝试 11 分钟, 直到接收到响应为止. 如果在 12 分钟内未收到响应, TCP
* 实现就会自动 关闭本地Socket, 断开连接. 在不同的网络平台上, TCP实现尝试与远程Socket 对话的时限有所差别.
* </p>
*
* @param
* @return
* @throws
* @see
* @since %I%
*/
public void setKeepAlive(boolean keepAlive) {
tcpNIOTransportBuilder.setKeepAlive(keepAlive);
}
/**
*
* <p>
* 描述:那么执行Socket 的 close() 方法, 该方法不会立即返回, 而是进入阻塞状态. 同时, 底层的 Socket
* 会尝试发送剩余的数据. 只有满足以下两个条件之一, close() 方法才返回: ⑴ 底层的 Socket 已经发送完所有的剩余数据; ⑵
* 尽管底层的 Socket 还没有发送完所有的剩余数据, 但已经阻塞了 x 秒(注意这里是秒, 而非毫秒), close() 方法的阻塞时间超过
* 3600 秒, 也会返回, 剩余未发送的数据被丢弃. 值得注意的是, 在以上两种情况内, 当close() 方法返回后, 底层的 Socket
* 会被关闭, 断开连接. 此外, setSoLinger(boolean on, int seconds) 方法中的 seconds
* 参数以秒为单位, 而不是以毫秒为单位. 单位:秒
* </p>
*
* @param
* @return
* @throws
* @see
* @since %I%
*/
public void setLinger(int linger) {
tcpNIOTransportBuilder.setLinger(linger);
}
/**
*
* <p>
* 描述:默认情况下, 发送数据采用Negale 算法. Negale 算法是指发送方发送的数据不会立即发出, 而是先放在缓冲区,
* 等缓存区满了再发出. 发送完一批数据后, 会等待接收方对这批数据的回应, 然后再发送下一批数据. Negale
* 算法适用于发送方需要发送大批量数据, 并且接收方会及时作出 回应的场合, 这种算法通过减少传输数据的次数来提高通信效率.
* </p>
*
* @param
* @return
* @throws
* @see
* @since %I%
*/
public void setTcpNoDelay(boolean tcpNoDelay) {
tcpNIOTransportBuilder.setTcpNoDelay(tcpNoDelay);
}
public void setReadBuffer(int readBufferSize) {
tcpNIOTransportBuilder.setReadBufferSize(readBufferSize);
}
public void setWriteBuffer(int writeBufferSize) {
tcpNIOTransportBuilder.setWriteBufferSize(writeBufferSize);
}
public void add(Filter filter) {
filterChainBuilder.addLast(filter);
}
public void connect(final String host, final int port) throws IOException {
// init
init();
// Create TCP NIO transport
transport = tcpNIOTransportBuilder.build();
transport.setProcessor(filterChainBuilder.build());
// start transport
try {
transport.start();
Future<Connection> future = transport.connect(host, port);
connection = future.get(10, TimeUnit.SECONDS);
if (isNeedSSL()) {
HandshakeStatus handshakeStatus = clientSSLFilter
.getHandshakeStatus(10, TimeUnit.SECONDS);
if (handshakeStatus == null
|| handshakeStatus != SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING) {
throw new IOException(String.format(
"SSL handshake status:%s error!", handshakeStatus));
}
}
// return connection;
} catch (IOException e) {
logger.error("client connect error!", e);
throw e;
} catch (TimeoutException e) {
logger.error("client connect error!", e);
throw new IOException(e);
} catch (InterruptedException e) {
logger.error("client connect error!", e);
throw new IOException(e);
} catch (ExecutionException e) {
logger.error("client connect error!", e);
throw new IOException(e);
}
}
// public HandshakeStatus getSSLHandshakeStatus(long timeout, TimeUnit unit)
// throws IOException {
// try {
// HandshakeStatus r = clientSSLFilter.getHandshakeStatus(timeout, unit);
// return r;
// } catch (IOException e) {
// logger.error("getServiceResponse error.", e);
// throw e;
// }
// }
//
// public HandshakeStatus getSSLHandshakeStatus() throws IOException {
// try {
// HandshakeStatus r = clientSSLFilter.getHandshakeStatus();
// return r;
// } catch (IOException e) {
// logger.error("getServiceResponse error.", e);
// throw e;
// }
// }
public ServiceResponse getServiceResponse(long timeout, TimeUnit unit)
throws IOException {
try {
ServiceResponse r = clientFutureFilter.getServiceResponse(timeout,
unit);
return r;
} catch (IOException e) {
logger.error("getServiceResponse error.", e);
throw e;
}
}
public ServiceResponse getServiceResponse() throws IOException {
try {
ServiceResponse r = clientFutureFilter.getServiceResponse();
return r;
} catch (IOException e) {
logger.error("getServiceResponse error.", e);
throw e;
}
}
public void write(ServiceRequest request) throws IOException {
try {
SafeFutureImpl<ServiceResponse> resultFuture = SafeFutureImpl
.create();
clientFutureFilter.setFuture(resultFuture);
GrizzlyFuture<WriteResult<ServiceRequest, SocketAddress>> write = connection
.write(request);
} catch (IOException e) {
logger.error("write service request error.", e);
throw e;
}
}
public void close() throws IOException {
if (connection != null) {
try {
connection.close();
} catch (IOException e) {
logger.error("client close connection error!", e);
throw e;
}
}
if (transport != null) {
try {
transport.stop();
transport = null;
} catch (IOException e) {
logger.error("client stop transport error!", e);
throw e;
}
}
}
/**
* Initialize server side SSL configuration.
*
* @return server side {@link SSLEngineConfigurator}.
*/
private SSLEngineConfigurator initializeSSL() {
// Initialize SSLContext configuration
SSLContextConfigurator sslContextConfig = new SSLContextConfigurator();
// Set key store
ClassLoader cl = ClassLoaderUtils.getDefaultClassLoader();
URL cacertsUrl = cl.getResource(getTrustStoreFile());// getTrustStoreFile()
// serverstore.jks
if (cacertsUrl != null) {
sslContextConfig.setTrustStoreFile(cacertsUrl.getFile());
sslContextConfig.setTrustStorePass(getTrustStorePassword());
}
// Set trust store
URL keystoreUrl = cl.getResource(getKeyStoreFile());
if (keystoreUrl != null) {
sslContextConfig.setKeyStoreFile(keystoreUrl.getFile());
sslContextConfig.setKeyStorePass(getKeyStorePassword());
}
// Create SSLEngine configurator
return new SSLEngineConfigurator(sslContextConfig.createSSLContext(),
false, false, false);
}
public static void main(String[] args) throws Exception {
// NIOSocketClient client = new NIOSocketClient();
NIOSocketClient client = new NIOSocketClient("EAUSER123", "aaaaa888");
client.setNeedSSL(false);
client.setNeedAuth(false);
client.setSoTimeout(1);
client.setKeepAlive(true);
client.setLinger(30);
client.setTcpNoDelay(false);
// client.setNeedSSL(true);
// client.setTrustStoreFile("ssltest-cacerts.jks");
// client.setTrustStorePassword("changeit");
// client.setKeyStoreFile("ssltest-keystore.jks");
// client.setKeyStorePassword("changeit");
// final FutureImpl<ServiceResponse> resultMessageFuture =
// SafeFutureImpl.create();
// client.add(new ClientFutureFilter(resultMessageFuture));
client.connect("localhost", 7777);
ServiceRequest message1 = new ServiceRequest();
message1.setId("11111111111111111111111111111122");
message1.setRequestObject("test string");
message1.setServiceId("1000010001");
message1.setTransformType(SocketKeys.TRANSFORM_JSON);
logger.debug("write message:");
client.write(message1);
ServiceResponse rcvMessage = client.getServiceResponse(10,
TimeUnit.SECONDS);
// ServiceResponse rcvMessage = client.getServiceResponse();
if (rcvMessage != null)
logger.info(rcvMessage.toString());
else
logger.info("receiveMessage is null!");
ServiceRequest message2 = new ServiceRequest();
message2.setId("11111111111111111111111111111122");
message2.setRequestObject("test string");
message2.setServiceId("1000010003");
message2.setTransformType(SocketKeys.TRANSFORM_JSON);
logger.debug("write message:");
client.write(message2);
client.close();
}
}