/* * This program is free software; you can redistribute it and/or modify it under the terms of * the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, * or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for more details. * You should have received a copy of the GNU General Public License along with this program; * if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package com.meidusa.amoeba.mysql.net; import java.nio.channels.SocketChannel; import java.util.concurrent.locks.Lock; import org.apache.log4j.Level; import org.apache.log4j.Logger; import com.meidusa.amoeba.net.poolable.ObjectPool; import com.meidusa.amoeba.net.poolable.PoolableObject; import com.meidusa.amoeba.context.ProxyRuntimeContext; import com.meidusa.amoeba.mysql.context.MysqlProxyRuntimeContext; import com.meidusa.amoeba.mysql.io.MySqlPacketConstant; import com.meidusa.amoeba.mysql.net.packet.AuthenticationPacket; import com.meidusa.amoeba.mysql.net.packet.ErrorPacket; import com.meidusa.amoeba.mysql.net.packet.HandshakePacket; import com.meidusa.amoeba.mysql.net.packet.MysqlPacketBuffer; import com.meidusa.amoeba.mysql.net.packet.Scramble323Packet; import com.meidusa.amoeba.mysql.util.CharsetMapping; import com.meidusa.amoeba.net.Connection; import com.meidusa.amoeba.net.Sessionable; import com.meidusa.amoeba.util.Reporter; import com.meidusa.amoeba.util.StringUtil; /** * ���Ϊ����mysql server�Ŀͻ���Connection * @author <a href=mailto:piratebase@sina.com>Struct chen</a> * */ public class MysqlServerConnection extends MysqlConnection implements MySqlPacketConstant,Reporter.SubReporter,CommandListener,PoolableObject{ static Logger logger = Logger.getLogger(MysqlServerConnection.class); /** * Ĭ����mysql���������Ӳ���UTF8����mysqlServerConnection ������ mysqlClientConnection ���벻һ�µ�ʱ�� * ����query֮ǰ�ᷢ��set names charset(�ͻ��˵���Ӧ����) */ private static int DEFAULT_CHARSET_INDEX = 33; public static enum Status{WAITE_HANDSHAKE,AUTHING,COMPLETED}; private Status status = Status.WAITE_HANDSHAKE; private CommandInfo commandInfo = null; private CommandMessageQueueRunner commandRunner; private ObjectPool objectPool; private long createTime = System.currentTimeMillis(); private boolean active; private long serverCapabilities; private String serverVersion; private int serverMajorVersion; private int serverMinorVersion; private int serverSubMinorVersion; private String seed; public MysqlServerConnection(SocketChannel channel, long createStamp) { super(channel, createStamp); //commandRunner = new CommandMessageQueueRunner(this); } public void handleMessage(Connection conn,byte[] message) { if(!isAuthenticated()){ /** * ��һ������Ϊ handshake packet * �ڶ�������Ϊ OkPacket packet or ErrorPacket * */ MysqlPacketBuffer buffer = new MysqlPacketBuffer(message); if(MysqlPacketBuffer.isErrorPacket(message)){ setAuthenticated(false); ErrorPacket error = new ErrorPacket(); error.init(message,conn); logger.error("handShake with "+this._channel.socket().getRemoteSocketAddress()+" error:"+error.serverErrorMessage); return; } if(status == Status.WAITE_HANDSHAKE){ HandshakePacket handpacket = new HandshakePacket(); handpacket.init(buffer); this.serverCapabilities = handpacket.serverCapabilities; this.serverVersion = handpacket.serverVersion; splitVersion(); if (!versionMeetsMinimum(4, 1, 1) || handpacket.protocolVersion != 10){ logger.error("amoeba support version minimum 4.1.1 and protocol version 10"); System.exit(-1); } if(logger.isDebugEnabled()){ logger.debug("receive HandshakePacket packet from server:"+this.host +":"+this.port); } MysqlProxyRuntimeContext context = ((MysqlProxyRuntimeContext)MysqlProxyRuntimeContext.getInstance()); if(context.getServerCharset() == null && handpacket.serverCharsetIndex > 0){ context.setServerCharsetIndex(handpacket.serverCharsetIndex); logger.info("mysql server Handshake= "+handpacket.toString()); } AuthenticationPacket authing = new AuthenticationPacket(); authing.password = this.getPassword(); this.seed = authing.seed = handpacket.seed+handpacket.restOfScrambleBuff; authing.clientParam = CLIENT_FOUND_ROWS; authing.charsetNumber = (byte)(DEFAULT_CHARSET_INDEX & 0xff); this.setCharset(CharsetMapping.INDEX_TO_CHARSET[DEFAULT_CHARSET_INDEX]); if (versionMeetsMinimum(4, 1, 0)) { if (versionMeetsMinimum(4, 1, 1)) { authing.clientParam |= CLIENT_PROTOCOL_41; // Need this to get server status values authing.clientParam |= CLIENT_TRANSACTIONS; // We always allow multiple result sets authing.clientParam |= CLIENT_MULTI_RESULTS; // We allow the user to configure whether // or not they want to support multiple queries // (by default, this is disabled). /*if (this.connection.getAllowMultiQueries()) { this.clientParam |= CLIENT_MULTI_QUERIES; }*/ } else { authing.clientParam |= CLIENT_RESERVED; } } if (handpacket.protocolVersion > 9) { authing.clientParam |= CLIENT_LONG_PASSWORD; // for long passwords } else { authing.clientParam &= ~CLIENT_LONG_PASSWORD; } if ((this.serverCapabilities & CLIENT_LONG_FLAG) != 0) { authing.clientParam |= CLIENT_LONG_FLAG; } if ((this.serverCapabilities & CLIENT_SECURE_CONNECTION) != 0) { authing.clientParam |= CLIENT_SECURE_CONNECTION; } authing.user = this.getUser(); authing.packetId = 1; if(this.getSchema() != null){ authing.database = this.getSchema(); authing.clientParam |= CLIENT_CONNECT_WITH_DB; } authing.maxThreeBytes = 1073741824; status = Status.AUTHING; if(logger.isDebugEnabled()){ logger.debug("authing packet sent to server:"+this.host +":"+this.port); } this.postMessage(authing.toByteBuffer(conn).array()); }else if(status == Status.AUTHING){ if(logger.isDebugEnabled()){ logger.debug("authing result packet from server:"+this.host +":"+this.port); } if(MysqlPacketBuffer.isOkPacket(message)){ setAuthenticated(true); return; }else{ if(message.length<9 && MysqlPacketBuffer.isEofPacket(message)){ Scramble323Packet packet = new Scramble323Packet(); packet.packetId = 3; packet.seed323 = this.seed.substring(0, 8); packet.password = this.getPassword(); this.postMessage(packet.toByteBuffer(conn).array()); logger.debug("server request scrambled password in old format"); }else{ logger.warn("server response packet from :"+this._channel.socket().getRemoteSocketAddress()+" :\n"+StringUtil.dumpAsHex(message, message.length)); } } } }else{ logger.warn("server "+this._channel.socket().getRemoteSocketAddress()+" raw handler message:"+StringUtil.dumpAsHex(message, message.length)); } } public boolean isVersion(int major, int minor, int subminor) { return ((major == getServerMajorVersion()) && (minor == getServerMinorVersion()) && (subminor == getServerSubMinorVersion())); } public boolean versionMeetsMinimum(int major, int minor, int subminor) { if (getServerMajorVersion() >= major) { if (getServerMajorVersion() == major) { if (getServerMinorVersion() >= minor) { if (getServerMinorVersion() == minor) { return (getServerSubMinorVersion() >= subminor); } // newer than major.minor return true; } // older than major.minor return false; } // newer than major return true; } return false; } /** * Get the major version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerMajorVersion() { return this.serverMajorVersion; } /** * Get the minor version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerMinorVersion() { return this.serverMinorVersion; } /** * Get the sub-minor version of the MySQL server we are talking to. * * @return DOCUMENT ME! */ final int getServerSubMinorVersion() { return this.serverSubMinorVersion; } /** * Get the version string of the server we are talking to * * @return DOCUMENT ME! */ String getServerVersion() { return this.serverVersion; } private void splitVersion(){ // Parse the server version into major/minor/subminor int point = this.serverVersion.indexOf("."); //$NON-NLS-1$ if (point != -1) { try { int n = Integer.parseInt(this.serverVersion.substring(0, point)); this.serverMajorVersion = n; } catch (NumberFormatException NFE1) { ; } String remaining = this.serverVersion.substring(point + 1, this.serverVersion.length()); point = remaining.indexOf("."); //$NON-NLS-1$ if (point != -1) { try { int n = Integer.parseInt(remaining.substring(0, point)); this.serverMinorVersion = n; } catch (NumberFormatException nfe) { ; } remaining = remaining.substring(point + 1, remaining.length()); int pos = 0; while (pos < remaining.length()) { if ((remaining.charAt(pos) < '0') || (remaining.charAt(pos) > '9')) { break; } pos++; } try { int n = Integer.parseInt(remaining.substring(0, pos)); this.serverSubMinorVersion = n; } catch (NumberFormatException nfe) { ; } } } } /** * ���ڴ�����֤��Connection Idleʱ�����������Ӧ����һ�㡣 */ public boolean checkIdle(long now) { if(isAuthenticated()){ if(_handler instanceof Sessionable){ /** * �ô��ڸ߲���������¿��ܻᷢ��ClassCastException �쳣,Ϊ����������,��������������쳣. */ try{ Sessionable session = (Sessionable)_handler; return session.checkIdle(now); }catch(ClassCastException castException){ return false; } } return false; }else{ long idleMillis = now - _lastEvent; if (idleMillis < 15000) { return false; } logger.warn("Disconnecting non-communicative server [conn=" + this + (this.getChannel() != null?","+this.getChannel().socket():", socket closed!") +", idle=" + idleMillis + "ms]. life="+(System.currentTimeMillis()-createTime)); return true; } } /** * commandInfo ���ڣ�����runnerû����������Ҫִ��runner�� * runnerû����������command�Ѿ�����������Ҫ��blockedס * runner running and command not end��runner handle those messages */ protected void messageProcess(byte[] msg){ if(commandInfo != null){ if(commandRunner != null){ if(commandRunner.getRunnerStatus() != CommandMessageQueueRunner.RunnerStatus.RUNNING){ final Lock lock = commandRunner.getLock(); lock.lock(); try{ commandRunner.setRunnerStatus(CommandMessageQueueRunner.RunnerStatus.RUNNING); ProxyRuntimeContext.getInstance().getServerSideExecutor().execute(commandRunner); }finally{ lock.unlock(); } } commandRunner.handleMessage(this, msg); }else{ super.messageProcess(msg); } }else{ super.messageProcess(msg); } } public void appendReport(StringBuilder buffer, long now, long sinceLast, boolean reset,Level level) { if(commandRunner != null){ buffer.append(" -- Command: messageQueueSize:").append(commandRunner.getQueueSize()); buffer.append(",runner.Status:").append(commandRunner.getRunnerStatus()).append("\n"); } if(this._handler instanceof Reporter.SubReporter && this._handler != this){ Reporter.SubReporter reporter = (Reporter.SubReporter)(this._handler); reporter.appendReport(buffer, now, sinceLast, reset,level); } } public void finishedCommand(CommandInfo command) { if(commandRunner != null){ final Lock lock = commandRunner.getLock(); lock.lock(); try{ commandRunner.setRunnerStatus(CommandMessageQueueRunner.RunnerStatus.WAITTOEND); this.commandInfo = null; }finally{ lock.unlock(); } } } public void startCommand(CommandInfo command) { this.commandInfo = command; } public ObjectPool getObjectPool() { return objectPool; } public synchronized void setObjectPool(ObjectPool pool) { if(objectPool == null && pool == null){ if(this.isAuthenticated()){ logger.warn("Set pool null",new Exception()); } } this.objectPool = pool; } public boolean isActive() { return active; } public void setActive(boolean active) { this.active = active; } public boolean isRemovedFromPool() { return objectPool == null; } protected void close(Exception exception){ super.close(exception); final ObjectPool tmpPool = objectPool; objectPool = null; try { if(tmpPool != null){ /** * ����active ״̬�� poolableObject��������ObjectPool.invalidateObject ��ʽ��pool������ * ����ֻ�ܵȴ���borrow ���� idle time out */ if(isActive()){ tmpPool.invalidateObject(this); } } } catch (Exception e) { } if(commandRunner != null && commandRunner.getRunnerStatus() == CommandMessageQueueRunner.RunnerStatus.RUNNING){ commandRunner.interrupt(); } } }