package com.github.ompc.greys.core.server; import com.github.ompc.greys.core.ClassDataSource; import com.github.ompc.greys.core.Configure; import com.github.ompc.greys.core.manager.ReflectManager; import com.github.ompc.greys.core.manager.TimeFragmentManager; import com.github.ompc.greys.core.util.GaCheckUtils; import com.github.ompc.greys.core.util.LogUtil; import org.slf4j.Logger; import java.io.IOException; import java.lang.instrument.Instrumentation; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.*; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicBoolean; import static com.github.ompc.greys.core.server.LineDecodeState.READ_CHAR; import static com.github.ompc.greys.core.server.LineDecodeState.READ_EOL; import static com.github.ompc.greys.core.util.GaStringUtils.getLogo; import static java.nio.channels.SelectionKey.OP_ACCEPT; import static java.nio.channels.SelectionKey.OP_READ; import static org.apache.commons.io.IOUtils.closeQuietly; /** * GaServer操作的附件 * Created by oldmanpushcart@gmail.com on 15/5/3. */ class GaAttachment { private final int bufferSize; private final Session session; private LineDecodeState lineDecodeState; private ByteBuffer lineByteBuffer; GaAttachment(int bufferSize, Session session) { this.lineByteBuffer = ByteBuffer.allocate(bufferSize); this.bufferSize = bufferSize; this.lineDecodeState = READ_CHAR; this.session = session; } public LineDecodeState getLineDecodeState() { return lineDecodeState; } public void setLineDecodeState(LineDecodeState lineDecodeState) { this.lineDecodeState = lineDecodeState; } public void put(byte data) { if (lineByteBuffer.hasRemaining()) { lineByteBuffer.put(data); } else { final ByteBuffer newLineByteBuffer = ByteBuffer.allocate(lineByteBuffer.capacity() + bufferSize); lineByteBuffer.flip(); newLineByteBuffer.put(lineByteBuffer); newLineByteBuffer.put(data); this.lineByteBuffer = newLineByteBuffer; } } public String clearAndGetLine(Charset charset) { lineByteBuffer.flip(); final byte[] dataArray = new byte[lineByteBuffer.limit()]; lineByteBuffer.get(dataArray); final String line = new String(dataArray, charset); lineByteBuffer.clear(); return line; } public Session getSession() { return session; } } /** * 行解码 */ enum LineDecodeState { // 读字符 READ_CHAR, // 读换行 READ_EOL } /** * Greys 服务端<br/> * Created by oldmanpushcart@gmail.com on 15/5/2. */ public class GaServer { private final Logger logger = LogUtil.getLogger(); private static final int BUFFER_SIZE = 4 * 1024; private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private static final byte CTRL_D = 0x04; private static final byte CTRL_X = 0x18; private static final byte EOT = 0x04; private static final int EOF = -1; private final AtomicBoolean isBindRef = new AtomicBoolean(false); private final SessionManager sessionManager; private final CommandHandler commandHandler; private final int javaPid; private final Thread jvmShutdownHooker = new Thread("ga-shutdown-hooker") { @Override public void run() { GaServer.this._destroy(); } }; private final ExecutorService executorService = Executors.newCachedThreadPool(new ThreadFactory() { @Override public Thread newThread(Runnable r) { final Thread t = new Thread(r, "ga-command-execute-daemon"); t.setDaemon(true); return t; } }); private GaServer(int javaPid, Instrumentation inst) { this.javaPid = javaPid; this.sessionManager = new DefaultSessionManager(); this.commandHandler = new DefaultCommandHandler(this, inst); initForManager(inst); Runtime.getRuntime().addShutdownHook(jvmShutdownHooker); } /* * 初始化各种manager */ private void initForManager(final Instrumentation inst) { TimeFragmentManager.Factory.getInstance(); ReflectManager.Factory.initInstance(new ClassDataSource() { @Override public Collection<Class<?>> allLoadedClasses() { final Class<?>[] classArray = inst.getAllLoadedClasses(); return null == classArray ? new ArrayList<Class<?>>() : Arrays.asList(classArray); } }); } /** * 判断服务端是否已经启动 * * @return true:服务端已经启动;false:服务端关闭 */ public boolean isBind() { return isBindRef.get(); } private ServerSocketChannel serverSocketChannel = null; private Selector selector = null; /** * 启动Greys服务端 * * @param configure 配置信息 * @throws IOException 服务器启动失败 */ public void bind(Configure configure) throws IOException { if (!isBindRef.compareAndSet(false, true)) { throw new IllegalStateException("already bind"); } try { serverSocketChannel = ServerSocketChannel.open(); selector = Selector.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().setSoTimeout(configure.getConnectTimeout()); serverSocketChannel.socket().setReuseAddress(true); serverSocketChannel.register(selector, OP_ACCEPT); // 服务器挂载端口 serverSocketChannel.socket().bind(getInetSocketAddress(configure.getTargetIp(), configure.getTargetPort()), 24); logger.info("ga-server listening on network={};port={};timeout={};", configure.getTargetIp(), configure.getTargetPort(), configure.getConnectTimeout()); activeSelectorDaemon(selector, configure); } catch (IOException e) { unbind(); throw e; } } /* * 获取绑定网络地址信息<br/> * 这里做个小修正,如果targetIp为127.0.0.1(本地环回口),则需要绑定所有网卡 * 否则外部无法访问,只能通过127.0.0.1来进行了 */ private InetSocketAddress getInetSocketAddress(String targetIp, int targetPort) { if (GaCheckUtils.isEquals("127.0.0.1", targetIp)) { return new InetSocketAddress(targetPort); } else { return new InetSocketAddress(targetIp, targetPort); } } private void activeSelectorDaemon(final Selector selector, final Configure configure) { final ByteBuffer byteBuffer = ByteBuffer.allocate(BUFFER_SIZE); final Thread gaServerSelectorDaemon = new Thread("ga-selector-daemon") { @Override public void run() { while (!isInterrupted() && isBind()) { try { while (selector.isOpen() && selector.select() > 0) { final Iterator<SelectionKey> it = selector.selectedKeys().iterator(); while (it.hasNext()) { final SelectionKey key = it.next(); it.remove(); // do ssc accept if (key.isValid() && key.isAcceptable()) { doAccept(key, selector, configure); } // do sc read if (key.isValid() && key.isReadable()) { doRead(byteBuffer, key); } } } } catch (IOException e) { logger.warn("selector failed.", e); } catch (ClosedSelectorException e) { logger.debug("selector closed.", e); } } } }; gaServerSelectorDaemon.setDaemon(true); gaServerSelectorDaemon.start(); } private void doAccept(SelectionKey key, Selector selector, Configure configure) throws IOException { final ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel(); acceptSocketChannel(selector, serverSocketChannel, configure); } private SocketChannel acceptSocketChannel(Selector selector, ServerSocketChannel serverSocketChannel, Configure configure) throws IOException { final SocketChannel socketChannel = serverSocketChannel.accept(); socketChannel.configureBlocking(false); socketChannel.socket().setSoTimeout(configure.getConnectTimeout()); socketChannel.socket().setTcpNoDelay(true); final Session session = sessionManager.newSession(javaPid, socketChannel, DEFAULT_CHARSET); socketChannel.register(selector, OP_READ, new GaAttachment(BUFFER_SIZE, session)); logger.info("accept new connection, client={}@session[{}]", socketChannel, session.getSessionId()); if (!session.isSilent()) { // 这里输出Logo writeToSocketChannel(socketChannel, session.getCharset(), getLogo()); // 绘制提示符 writeToSocketChannel(socketChannel, session.getCharset(), session.prompt()); // Logo结束之后输出传输中止符 writeToSocketChannel(socketChannel, ByteBuffer.wrap(new byte[]{EOT})); } return socketChannel; } private void doRead(final ByteBuffer byteBuffer, SelectionKey key) { final GaAttachment attachment = (GaAttachment) key.attachment(); final SocketChannel socketChannel = (SocketChannel) key.channel(); final Session session = attachment.getSession(); try { // 若读到EOF,则说明SocketChannel已经关闭 if (EOF == socketChannel.read(byteBuffer)) { logger.info("client={}@session[{}] was closed.", socketChannel, session.getSessionId()); // closeSocketChannel(key, socketChannel); session.destroy(); if(session.isLocked()) { session.unLock(); } return; } // decode for line byteBuffer.flip(); while (byteBuffer.hasRemaining()) { switch (attachment.getLineDecodeState()) { case READ_CHAR: { final byte data = byteBuffer.get(); if ('\n' == data) { attachment.setLineDecodeState(READ_EOL); } // 遇到中止命令(CTRL_D),则标记会话为不可写,让后台任务停下 else if (CTRL_D == data || CTRL_X == data) { session.unLock(); break; } // 普通byte则持续放入到缓存中 else { if ('\r' != data) { attachment.put(data); } break; } } case READ_EOL: { final String line = attachment.clearAndGetLine(session.getCharset()); executorService.execute(new Runnable() { @Override public void run() { // 会话只有未锁定的时候才能响应命令 if (session.tryLock()) { try { // 命令执行 commandHandler.executeCommand(line, session); // 命令结束之后需要传输EOT告诉client命令传输已经完结,可以展示提示符 socketChannel.write(ByteBuffer.wrap(new byte[]{EOT})); } catch (IOException e) { logger.info("network communicate failed, session[{}] will be close.", session.getSessionId()); session.destroy(); } finally { session.unLock(); } } else { logger.info("session[{}] was locked, ignore this command.", session.getSessionId()); } } }); attachment.setLineDecodeState(READ_CHAR); break; } } }//while for line decode byteBuffer.clear(); } // 处理 catch (IOException e) { logger.warn("read/write data failed, session[{}] will be close.", session.getSessionId(), e); closeSocketChannel(key, socketChannel); session.destroy(); } } private void writeToSocketChannel(SocketChannel socketChannel, Charset charset, String message) throws IOException { writeToSocketChannel(socketChannel, ByteBuffer.wrap(message.getBytes(charset))); } private void writeToSocketChannel(SocketChannel socketChannel, ByteBuffer buffer) throws IOException { while (buffer.hasRemaining()) { socketChannel.write(buffer); } } private void closeSocketChannel(SelectionKey key, SocketChannel socketChannel) { closeQuietly(socketChannel); key.cancel(); } /** * 关闭Greys服务端 */ public void unbind() { closeQuietly(serverSocketChannel); closeQuietly(selector); if (!isBindRef.compareAndSet(true, false)) { throw new IllegalStateException("already unbind"); } } private void _destroy() { if (isBind()) { unbind(); } if (!sessionManager.isDestroy()) { sessionManager.destroy(); } executorService.shutdown(); logger.info("ga-server destroy completed."); } public void destroy() { Runtime.getRuntime().removeShutdownHook(jvmShutdownHooker); _destroy(); } private static volatile GaServer gaServer; /** * 单例 * * @param instrumentation JVM增强 * @return GaServer单例 */ public static GaServer getInstance(final int javaPid, final Instrumentation instrumentation) { if (null == gaServer) { synchronized (GaServer.class) { if (null == gaServer) { gaServer = new GaServer(javaPid, instrumentation); } } } return gaServer; } }