/* * This program is free software; you can redistribute it and/or modify it under the terms of * the GNU AFFERO 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 AFFERO GENERAL PUBLIC LICENSE for more details. * You should have received a copy of the GNU AFFERO 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 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.MysqlRuntimeContext; 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,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 ObjectPool objectPool; private boolean active; private long serverCapabilities; private String serverVersion; private int serverMajorVersion; private int serverMinorVersion; private int serverSubMinorVersion; private String seed; /** * query timeout (TimeUnit:second.) */ private long queryTimeout; public MysqlServerConnection(SocketChannel channel, long createStamp) { super(channel, createStamp); } public void handleMessage(Connection conn) { byte[] message = null; while((message = this.getInQueue().getNonBlocking()) != null){ 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+",hashCode="+this.hashCode()); return; } if(status == Status.WAITE_HANDSHAKE){ if(logger.isDebugEnabled()){ logger.debug("1. handShake with "+this.getSocketId()+",hashCode="+this.hashCode()); } 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("2. receive HandshakePacket packet from server:"+this.getSocketId()+",hashCode="+this.hashCode()); } if(ProxyRuntimeContext.getInstance() != null){ MysqlRuntimeContext context = (MysqlRuntimeContext)ProxyRuntimeContext.getInstance().getRuntimeContext(); if(context!=null && context.getServerCharset() == null && handpacket.serverCharsetIndex > 0){ context.setServerCharsetIndex(handpacket.serverCharsetIndex); if (logger.isDebugEnabled()) { logger.debug("mysql server Handshake packet= "+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("3. authing packet sent to server:"+this.getSocketId()+",hashCode="+this.hashCode()); } this.postMessage(authing.toByteBuffer(conn)); }else if(status == Status.AUTHING){ if(logger.isDebugEnabled()){ logger.debug("4. authing result packet from server:"+this.getSocketId()+",hashCode="+this.hashCode()); } 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)); if(logger.isDebugEnabled()){ logger.debug("5. server request scrambled password in old format"+",hashCode="+this.hashCode()); } }else{ logger.warn("5. server response packet from :"+this.getSocketId()+" :\n"+StringUtil.dumpAsHex(message, message.length)+",hashCode="+this.hashCode(),new Exception()); } } }else{ logger.error("handShake with "+this._channel.socket().getRemoteSocketAddress()+" stat:"+status); } }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 (isClosed()) { return true; } if(isAuthenticated()){ //处于使用中的链接, 如果超过5分钟没有发生网络IO,则需要关闭该链接 long idleMillis = now - _lastEvent; if(isActive()){ if (idleMillis > getQueryTimeout() * 1000) { return true; } } if(_handler instanceof Sessionable){ /** * 该处在高并发的情况下可能会发生ClassCastException 异常,为了提升性能,这儿将忽略这种异常. */ try{ Sessionable session = (Sessionable)_handler; boolean sessionIdle = session.checkIdle(now); if(sessionIdle){ logger.error("Session timeout. conn="+this.toString()+" ,idleMillis="+idleMillis+",_handler="+_handler.toString()); } return sessionIdle; }catch(ClassCastException castException){ return false; } } return false; }else{ long idleMillis = now - _lastEvent; if (idleMillis < 15000) { return false; } return true; } } public void appendReport(StringBuilder buffer, long now, long sinceLast, boolean reset,Level level) { if(this._handler instanceof Reporter.SubReporter && this._handler != this){ Reporter.SubReporter reporter = (Reporter.SubReporter)(this._handler); reporter.appendReport(buffer, now, sinceLast, reset,level); } } 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._lastEvent = System.currentTimeMillis(); this.active = active; } public long getQueryTimeout() { return queryTimeout; } public void setQueryTimeout(long queryTimeout) { this.queryTimeout = queryTimeout; } 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); } if(_handler instanceof Sessionable){ /** * TODO 该处在高并发的情况下可能会发生ClassCastException 异常,为了提升性能,这儿将忽略这种异常. */ Sessionable session = (Sessionable)_handler; if(!session.isEnded()){ session.endSession(true, exception); } } } } catch (Exception e) { // TODO handle exception logger.warn(e); } } }