io.github.icodegarden.nutrient.nio.java.JavaNioClient Maven / Gradle / Ivy
package io.github.icodegarden.nutrient.nio.java;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.SelectionKey;
import java.nio.channels.SocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.github.icodegarden.nutrient.lang.exception.remote.ClientClosedRemoteException;
import io.github.icodegarden.nutrient.lang.exception.remote.ConnectFailedRemoteException;
import io.github.icodegarden.nutrient.lang.exception.remote.ExceedExpectedRemoteException;
import io.github.icodegarden.nutrient.lang.exception.remote.RemoteException;
import io.github.icodegarden.nutrient.lang.util.SystemUtils;
import io.github.icodegarden.nutrient.nio.AbstractNioClient;
import io.github.icodegarden.nutrient.nio.Channel;
import io.github.icodegarden.nutrient.nio.ExchangeMessage;
import io.github.icodegarden.nutrient.nio.health.Heartbeat;
import io.github.icodegarden.nutrient.nio.health.NioClientHeartbeat;
import io.github.icodegarden.nutrient.nio.task.HeartbeatTimerTask;
import io.github.icodegarden.nutrient.nio.task.ReconnectTimerTask;
import io.github.icodegarden.nutrient.nio.task.ScheduleCancelableRunnable;
/**
*
* @author Fangfang.Xu
*
*/
public class JavaNioClient extends AbstractNioClient implements ClientNioEventListener {
private static final Logger log = LoggerFactory.getLogger(JavaNioClient.class);
private static volatile ClientNioSelector commonClientNioSelector;
private long lastErrorTime = 0;
private SocketChannelSpace socketChannelSpace;
private final ClientNioSelector clientNioSelector;
private final HeartbeatTimerTask heartbeatTimerTask;
private final ReconnectTimerTask reconnectTimerTask;
private volatile boolean closed = true;
private SocketChannel socketChannel;
private NioClientHeartbeat heartbeat;
private final InetSocketAddress address;
private ScheduleCancelableRunnable heartbeatTask;
private ScheduleCancelableRunnable reconnectTask;
public JavaNioClient(InetSocketAddress address) {
this(address, getCommonClientNioSelector(), HeartbeatTimerTask.DEFAULT, ReconnectTimerTask.DEFAULT);
}
public JavaNioClient(InetSocketAddress address, ClientNioSelector clientNioSelector) {
this(address, clientNioSelector, HeartbeatTimerTask.DEFAULT, ReconnectTimerTask.DEFAULT);
}
public JavaNioClient(InetSocketAddress address, ClientNioSelector clientNioSelector, long heartbeatIntervalMillis) {
this(address, clientNioSelector, new HeartbeatTimerTask(heartbeatIntervalMillis),
new ReconnectTimerTask(heartbeatIntervalMillis));
}
JavaNioClient(InetSocketAddress address, ClientNioSelector clientNioSelector, HeartbeatTimerTask heartbeatTimerTask,
ReconnectTimerTask reconnectTimerTask) {
this.address = address;
this.clientNioSelector = clientNioSelector;
this.heartbeatTimerTask = heartbeatTimerTask;
this.reconnectTimerTask = reconnectTimerTask;
}
private static synchronized ClientNioSelector getCommonClientNioSelector() {
if (commonClientNioSelector == null) {
commonClientNioSelector = ClientNioSelector.openNew("CommonClientNioSelector");
}
return commonClientNioSelector;
}
@Override
public synchronized void connect() throws ConnectFailedRemoteException {
try {
socketChannel = SocketChannel.open();
socketChannel.configureBlocking(true);
socketChannel.socket().setSoLinger(false, -1);
socketChannel.socket().setTcpNoDelay(true);
socketChannel.socket().setReceiveBufferSize(1024 * 64);
socketChannel.socket().setSendBufferSize(1024 * 64);
// socketChannel.socket().setSoTimeout(timeout);//对read
socketChannel.configureBlocking(false);
if (log.isInfoEnabled()) {
log.info("client connecting {}", address);
}
socketChannel.connect(address);// non blocking模式结果一定是false
// int retry = 3;
// while (!finishConnect()) {
// if (++retry == 3) {
// throw new ConnectFailedRemoteException("NOT finishConnect, retry:" + retry);
// }
// }
/**
* 模拟connectTimeout
*/
long start = System.currentTimeMillis();
while (!finishConnect()) {
if (System.currentTimeMillis() - start > connectTimeout) {
throw new ConnectFailedRemoteException("NOT finishConnect, connectTimeout:" + connectTimeout);
}
}
if (log.isInfoEnabled()) {
log.info("client connected {}", address);
}
socketChannelSpace = new SocketChannelSpace("client");
closed = false;
} catch (IOException e) {
throw new ConnectFailedRemoteException("Connect Failed", e);
}
}
private boolean finishConnect() throws IOException, ClosedChannelException {
boolean connected = socketChannel.isConnected();
if (connected || (connected = socketChannel.finishConnect())) {
clientNioSelector.registerRead(this);
if (heartbeat == null) {// IMPT 这些不能(也不需要)重新创建,否则task pool没有清除
heartbeat = new NioClientHeartbeat("client", this, new Channel() {
@Override
public void write(Object obj) throws RemoteException {
try {
socketChannelSpace.write(socketChannel(), (ExchangeMessage) obj);
} catch (Exception e) {
throw new ExceedExpectedRemoteException(e);
}
}
@Override
public void close() throws IOException {
socketChannel.close();
}
});
heartbeatTask = heartbeatTimerTask.register(heartbeat);
reconnectTask = reconnectTimerTask.register(heartbeat);
}
if (log.isInfoEnabled()) {
log.info("client finishConnect {}", address);
}
}
return connected;
}
@Override
public SocketChannel socketChannel() {
return socketChannel;
}
@Override
public void onRead(SelectionKey key) throws IOException {
try {
ExchangeMessage message = socketChannelSpace.read(socketChannel);
if (message == null) {
return;// wait for more read
}
if (message.isEvent()) {
heartbeat.receive();
} else {
if (log.isDebugEnabled()) {
log.debug("client read message:{}", message);
}
Future.received(message.getRequestId(), message.getBody());
}
} catch (ClosedChannelException e) {
/**
* JDK NIO 空轮询?
*/
if (lastErrorTime + heartbeatTimerTask.getHeartbeatIntervalMillis() < SystemUtils.currentTimeMillis()) {
// 通常是client自身网络断开,实测证明也可能是server下线等
if (log.isWarnEnabled()) {
log.warn("client channel was closed, may be client disconnect or server was Not Available");
}
// reconnect();
heartbeat.markReconnect();// 重连时机交给ReconnectTimerTask
lastErrorTime = SystemUtils.currentTimeMillis();
}
} catch (IOException e) {
/**
* IMPT 通常由于server主动关闭,客户端要close自己,后续NioClientPool就能够在获取连接时识别已关闭并移除
*/
if (log.isWarnEnabled()) {
log.warn("client channel was closed, that more means server was closed, close client.");
}
close();
}
}
@Override
public void onWrite(SelectionKey key) throws IOException {
throw new IOException("unuse");
}
@Override
protected void doSend(ExchangeMessage message) throws RemoteException {
if (log.isDebugEnabled()) {
log.debug("client send message:{}", message);
}
// 不做判断与重连,让heartbeat处理重连、监听close事件处理连接池
// try {
// if(isClosed()) {
// reconnect();
// }
// } catch (IOException e) {
// throw new NioException(e);
// }
/**
* nio不需要关注ServerError,
*/
try {
socketChannelSpace.write(socketChannel, message);
} catch (IOException e) {
/**
* 客户端已人工关闭
* 网络故障等,让heartbeat处理重连、监听close事件处理连接池
*/
if (isClosed()) {
throw new ClientClosedRemoteException("client closed", e);
}
throw new ExceedExpectedRemoteException(e);
}
}
@Override
public boolean isClosed() {
return closed;
}
@Override
public synchronized void reconnect() throws ConnectFailedRemoteException {
if (log.isInfoEnabled()) {
log.info("client do reconnect...");
}
// 由于heartbeatTask reconnectTask 不能关闭,不能调用close
try {
socketChannel.close();
} catch (IOException e) {
throw new ConnectFailedRemoteException("Connect Failed", e);
}
closed = true;
connect();
}
@Override
public synchronized void close() throws IOException {
if (log.isInfoEnabled()) {
log.info("client do close...");
}
try {
if (socketChannel != null) {
socketChannel.close();
}
if (heartbeatTask != null) {
heartbeatTask.cancel();
}
if (reconnectTask != null) {
reconnectTask.cancel();
}
} finally {
closed = true;
}
}
@Override
public String toString() {
return "[closed=" + closed + ", socketChannel=" + socketChannel + "]";
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy