/** * Copyright (c) 2013 by 苏州科大国创信息技术有限公司. */ package com.ustcinfo.netty.client; import io.netty.bootstrap.Bootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioSocketChannel; import java.net.InetSocketAddress; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.alibaba.dubbo.common.utils.NetUtils; /** * Create on @2013-8-24 @下午6:48:30 * @author bsli@ustcinfo.com */ public class Netty4Client { private static final Logger logger = LoggerFactory.getLogger(Netty4Client.class); private String host; private int port; private int timeout = 1000; private int connectTimeout = 3000; private final EventLoopGroup group = new NioEventLoopGroup(); private ClientChannelInitializer channelInitializer; private Bootstrap bootstrap; private volatile Channel channel; private volatile ChannelFuture future; private volatile ScheduledFuture<?> reconnectExecutorFuture = null; private long lastConnectedTime = System.currentTimeMillis(); private final AtomicInteger reconnect_count = new AtomicInteger(0); private final AtomicBoolean reconnect_error_log_flag = new AtomicBoolean(false) ; //重连warning的间隔.(waring多少次之后,warning一次) private final int reconnect_warning_period = 1800; private final long shutdown_timeout = 1000 * 60 * 15; private static final ScheduledThreadPoolExecutor reconnectExecutorService = new ScheduledThreadPoolExecutor(2, new NamedThreadFactory("ClientReconnectTimer", true)); public Netty4Client(String host, int port, ClientChannelInitializer channelInitializer) throws Exception { this.host = host; this.port = port; this.channelInitializer = channelInitializer; try { doOpen(); } catch (Throwable t) { close(); throw new Exception("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + host + ", cause: " + t.getMessage(), t); } try { connect(); logger.info("Start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + host); } catch (Throwable t){ throw new Exception("Failed to start " + getClass().getSimpleName() + " " + NetUtils.getLocalAddress() + " connect to the server " + host + ", cause: " + t.getMessage(), t); } } public void sendMessage(String message) { channel.writeAndFlush(message); } /* * 使用时,循环调用该方法获取服务端返回的信息。 * receiveMessage是阻塞方法,如果没有消息会等待。 */ public String receiveMessage() { return channelInitializer.getClientHandler().getMessage(); } private void doOpen() throws Throwable { bootstrap = new Bootstrap(); bootstrap.option(ChannelOption.SO_KEEPALIVE, true); bootstrap.option(ChannelOption.TCP_NODELAY, true); bootstrap.group(group) .channel(NioSocketChannel.class) .handler(channelInitializer); } private void doConnect() throws Throwable { long start = System.currentTimeMillis(); future = bootstrap.connect(getConnectAddress()); try{ boolean ret = future.awaitUninterruptibly(getConnectTimeout(), TimeUnit.MILLISECONDS); if (ret && future.isSuccess()) { Channel newChannel = future.channel(); try { // 关闭旧的连接 Channel oldChannel = Netty4Client.this.channel; if (oldChannel != null) { logger.info("Close old netty channel " + oldChannel + " on create new netty channel " + newChannel); oldChannel.close(); } } finally { Netty4Client.this.channel = newChannel; } } else if (future.cause() != null) { throw new Exception("client failed to connect to server " + getRemoteAddress() + ", error message is:" + future.cause().getMessage(), future.cause()); } else { throw new Exception("client failed to connect to server " + getRemoteAddress() + " client-side timeout " + getConnectTimeout() + "ms (elapsed: " + (System.currentTimeMillis() - start) + "ms) from netty client " + NetUtils.getLocalHost()); } }finally{ if (! isConnected()) { future.cancel(true); } } } private void connect() throws Exception { try { if (isConnected()) { return; } initConnectStatusCheckCommand(); doConnect(); if (! isConnected()) { throw new Exception("Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " " + NetUtils.getLocalHost() + ", cause: Connect wait timeout: " + getTimeout() + "ms."); } else { logger.info("Successed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " " + NetUtils.getLocalHost() + ", channel is " + this.channel); } reconnect_count.set(0); reconnect_error_log_flag.set(false); } catch (Throwable e) { logger.error("Failed connect to server " + getRemoteAddress() + " from " + getClass().getSimpleName() + " " + NetUtils.getLocalHost()); } } public void close() { destroyConnectStatusCheckCommand(); try { if (channel != null) { channel.close(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); } try { group.shutdownGracefully(); } catch (Throwable t) { logger.warn(t.getMessage()); } } private synchronized void destroyConnectStatusCheckCommand(){ try { if (reconnectExecutorFuture != null && ! reconnectExecutorFuture.isDone()){ reconnectExecutorFuture.cancel(true); reconnectExecutorService.purge(); } } catch (Throwable e) { logger.warn(e.getMessage(), e); } } public boolean isConnected() { if (channel == null) return false; return channel.isActive(); } private synchronized void initConnectStatusCheckCommand(){ if(reconnectExecutorFuture == null || reconnectExecutorFuture.isCancelled()){ Runnable connectStatusCheckCommand = new Runnable() { public void run() { try { if (! isConnected()) { connect(); } else { lastConnectedTime = System.currentTimeMillis(); } } catch (Throwable t) { String errorMsg = "client reconnect to "+getRemoteAddress()+" find error . "; if (System.currentTimeMillis() - lastConnectedTime > shutdown_timeout){ if (!reconnect_error_log_flag.get()){ reconnect_error_log_flag.set(true); logger.error(errorMsg, t); return ; } } if ( reconnect_count.getAndIncrement() % reconnect_warning_period == 0){ logger.warn(errorMsg, t); } } } }; reconnectExecutorFuture = reconnectExecutorService.scheduleWithFixedDelay(connectStatusCheckCommand, 2 * 1000, 2 * 1000, TimeUnit.MILLISECONDS); } } private InetSocketAddress getConnectAddress() { return new InetSocketAddress(this.host, this.port); } private String getRemoteAddress() { return host + ":" + port; } public int getTimeout() { return timeout; } public int getConnectTimeout() { return connectTimeout; } }