mysql协议分析

完整代码git: https://git.oschina.net/kfeicat/protocolAnalyse.git


参考资料:

http://dev.mysql.com/doc/internals/en/client-server-protocol.html

http://www.haodaima.net/art/2574673

http://hutaow.com/blog/2013/11/06/mysql-protocol-analysis/#11

http://blog.csdn.net/gapaul/article/details/12046843



mysql起始握手,先由server发起,client分析并回应自已同意的特性,然后双方依照这些特性处理数据包。

通信时是否采用压缩会改变数据包的字节变化。

客户端的特性在首个回应(既握手包)服务器中体现,如:是否开启压缩、字符集、用户登录信息等。


1.未采用压缩时,客户端向服务器发送的包格式:

格式:3*byte,1*byte,1*byte,n*byte

表示:消息长度,包序号,请求类型,请求内容


2.采用压缩后,客户端向服务器发送的包格式:

格式:3*byte,1*byte,3*byte,n*byte

表示:消息长度,包序号,压缩包大小,压缩包内容

当压缩包大小为0x00时,表示当前包未采用压缩,则n*byte内容为1*byte,n*byte,既请求类型,请求内容

当压缩包大小大于0x00时,表示当前包已采用zlib压缩,则n*byte内容,先解压缩,解压后内容为1*byte,n*byte,既请求类型,请求内容。


package mysql;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Map;

/**
 * 支持mysql 4.1+,不支持ssl
 * @author shanl
 *
 */
public class MysqlAnalyse {
	/**传输包默认最大长度16M*/
	public static final int PACKET_MAXIMUM_LENGTH = 1024*1024*16;
		
	Map<String,Object> properties = new HashMap<String,Object>();
//	StringBuilder sql = new StringBuilder();
	long requestType = -1;
	boolean endPacket = true;
	
//	int packetLen = PACKET_MAXIMUM_LENGTH;	
	byte[] _packet = null;
	int _off = 0;
	int _len = 0;
	
	/**
	 * 默认开启压缩、utf-8编码
	 */
	public MysqlAnalyse(){
		this.setProperty("compress", true);
		this.setProperty("encoding", "utf-8");
	}
			
	/**
	 * <b>通过客户端向服务端回应的握手包,获得一个MysqlAnlayse实例。</b> <br/>
	 * 建立连接时,server先向client发送握手包,此包包括server支持的特性和编码等信息,<br/>
	 * client回应包中需要说明本地支持什么特性,比如是否支持(zlib)压缩。
	 * @param buff
	 * @param off
	 * @param len
	 * @return
	 * @see <a href="http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse">
	 * http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse<a/>	
	 */
	public static MysqlAnalyse handshake(byte[] buff,int off,int len){
		MysqlAnalyse anly = new MysqlAnalyse();
		//2 byte,capability_flags
		long capability_flags = Util.littleBys2Uint32(new byte[]{
				buff[off],buff[off+1]
		}, 0, 2);
		
		//4byte,前2byte表示mysql特性(如下:是否开启压缩),后2byte客户端扩展特性
		boolean compress = CapabilityFlag.CLIENT_COMPRESS==(CapabilityFlag.CLIENT_COMPRESS & capability_flags);
		anly.setProperty("compress", compress);		
		
		//4byte,最大发送包字节数
		long max_packet_size = Util.littleBys2Uint32(new byte[]{
				buff[off+4],buff[off+5],
				buff[off+6],buff[off+7]
		}, 0, 4);
		anly.setProperty("max.packet.size", max_packet_size);
		
		//1byte,字符编码
		short character_set_id = buff[8];
		String character_set = "utf-8"; //需要分析
		//SET NAMES utf8; 显示客户端发送的SQL语句中使用什么字符集
		anly.setProperty("encoding", character_set);
		
		return anly;
	}
	
	/**
	 * <b> 解析数据包  </b> <br/>
	 未压缩:指客户端已开启压缩功能,但数据包本身不值得压缩,所以未进行压缩。<br/>
	 未开启压缩:指客户端未开启压缩功能,比如:./mysql 没有加-C, --compress参数。<br/>
	 客户端开启压缩功能后,向服务器发送数据包时,将会在包头(3+1字节)后,额外多3个字节,<br/>
	 表示压缩包大小,如果此值为0x000000(小端字节顺),则表示数据未压缩。 
	 * @param buff
	 * @param off
	 * @param len
	 * @return 如果是最后一个包返回true
	 */
	public boolean parse(byte[] packet,int off,int len){
//		sql = endPacket? new StringBuilder():sql;				
//		_buff = (null==_buff? new byte[packetLen]: _buff);
//		_buff = packet;
//		_off = off;
//		_len = len;
		
		if((Boolean)properties.get("compress")){
			endPacket=compress(packet,off,len);
		}else{
			endPacket=noncompress(packet,off,len);
		}		
		
		return endPacket;
	}
	
	/**
	 * (客户端)支持压缩
	 * @param packet
	 * @param off
	 * @param len
	 */
	boolean compress(byte[] packet,int off,int len){
		long tranPackLen = Util.littleBys2Uint32(packet,off+0,3); 		//3 byte,传输包长度,此长度已经-7
		short tranPackNo = packet[off+3];								//1 byte,传输包序号
		long compressPackLen = Util.littleBys2Uint32(packet,off+4,3);	//3 byte,压缩后包长度
			
		//未压缩
		if(0x000000==compressPackLen){			
			long srcLen = Util.littleBys2Uint32(packet,off+3+1+3,4)-1;	//4byte,(包含类型信息的)源数据长度
			this.requestType = packet[off+3+1+3+4];						//1byte,类型信息
			_packet = packet;
			_off = off+3+1+3;
			_len = (int)srcLen;
//			sql.append(toSql(packet, off+3+1+3, (int)srcLen));
		}else{ //已压缩
			byte[] src = Util.decompress(packet,off+3+1+3,
					(int)tranPackLen,(int)compressPackLen);				//n byte,src为zlib解压后的源数据
			long srcLen = Util.littleBys2Uint32(src,0,4)-1;				//4byte,(包含类型信息的)源数据长度
			this.requestType = src[4];									//1byte,类型信息
			_packet = src;
			_off = 0;
			_len = (int) srcLen;
//			sql.append(toSql(src,0,(int)srcLen));
		}
		
		return 0x00==tranPackNo;
	}
	
	/**
	 * (客户端)不支持压缩
	 * @param packet
	 * @param off
	 * @param len
	 */
	boolean noncompress(byte[] packet,int off,int len){
		long tranPackLen = Util.littleBys2Uint32(packet,off+0,3); 	//3 byte
		short tranPackNo = packet[off+3];							//1 byte
		this.requestType = packet[off+4];							//1 byte
		
//		sql.append(toSql(packet,off,(int)tranPackLen));
		_packet = packet;
		_off = off;
		_len = (int)tranPackLen;
		
		return 0x00==tranPackNo;
	}
	
	/**
	 * 当前包中的sql语句<br/>
	 * 注:这不一定是一个完整的sql语句<br/>
	 * @param packet
	 * @param off
	 * @param len
	 * @return 当requestType!=ClientRequestType.COM_QUERY时,返回""
	 */
	String toSql(byte[] packet,int off,int len){		
		try {
			return new String(packet,off+5,len, (String)getProperty("encoding"));
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException(e);			
		}
	}
	
	/**
	 * 返回sql语句 <br/>
	 * 只有当requestType==ClientRequestType.COM_QUERY时可用
	 * @return 类型不符合时,返回""
	 */
	public String getSql(){
		if(ClientRequestType.COM_QUERY!=this.requestType){
			return "";
		}		
//		return sql.toString();
		return toSql(_packet,_off,_len);
	}
	
	/**
	 * 返回sql语句 <br/>
	 * 只有当requestType==ClientRequestType.COM_QUERY时可用
	 * @param data
	 * @param off
	 * @return
	 */
	public int getSql(byte[] data, int off){
		System.arraycopy(_packet, _off, data, off, _len);
		return _len;
	}
	
	public void writeSql(OutputStream out) throws IOException{
		out.write(_packet, _off, _len);
		out.flush();
	}
	
	/**
	 * 返回当前sql请求类型,参见ClientRequestType
	 * @return 未知请求返回-1
	 */
	public long getRequestType(){
		return requestType;
	}
	
	/**
	 * 设置特性
	 * @param name
	 * @param value
	 */
	public void setProperty(String name,Object value){
		this.properties.put(name, value);
	}
	
	/**
	 * 设置特性
	 * @param properties
	 */
	public void putProperty(Map<String,Object> properties){
		this.properties.putAll(properties);;
	}
	
	/**
	 * 取特性
	 * @param name
	 * @return
	 */
	public Object getProperty(String name){
		return this.properties.get(name);
	}
	
	/**
	 * 返回所有特性
	 * @return
	 */
	public Map<String,Object> getProperties(){
		return this.properties;
	}
		
//	/**
//	 * 设置传输包大小<br/>
//	 * 默认值   PACKET_MAXIMUM_LENGTH
//	 * @param len
//	 * @see #PACKET_MAXIMUM_LENGTH
//	 */
//	public void setTranPacketLength(int len){
//		this.packetLen = len;
//	}
}

package mysql;

/**
 * 特性
 * @author shanl
 * @see <a href="http://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_PROTOCOL_41">http://dev.mysql.com/doc/internals/en/capability-flags.html#flag-CLIENT_PROTOCOL_41</a> 
 */
public interface CapabilityFlag {
	long CLIENT_LONG_PASSWORD 		= 	0x00000001;	
	long CLIENT_FOUND_ROWS			= 	0x00000002;
	long CLIENT_LONG_FLAG			= 	0x00000004;	
	long CLIENT_CONNECT_WITH_DB 	= 	0x00000008;
	
	long CLIENT_NO_SCHEMA			= 	0x00000010;
	long CLIENT_COMPRESS 			=	0x00000020;
	long CLIENT_ODBC 				= 	0x00000040;
	long CLIENT_LOCAL_FILES 		= 	0x00000080;
	
	long CLIENT_IGNORE_SPACE		= 	0x00000100;
	long CLIENT_PROTOCOL_41			= 	0x00000200;
	long CLIENT_INTERACTIVE 		= 	0x00000400;
	long CLIENT_SSL					= 	0x00000800;
	
	long CLIENT_IGNORE_SIGPIPE		= 	0x00001000;
	long CLIENT_TRANSACTIONS		= 	0x00002000;
	long CLIENT_RESERVED 			= 	0x00004000;
	long CLIENT_SECURE_CONNECTION	=	0x00008000;
	
	long CLIENT_MULTI_STATEMENTS	= 	0x00010000;
	long CLIENT_MULTI_RESULTS		= 	0x00020000;
	long CLIENT_PS_MULTI_RESULTS	= 	0x00040000;
	long CLIENT_PLUGIN_AUTH			= 	0x00080000;
	
	long CLIENT_CONNECT_ATTRS						= 	0x00100000;
	long CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA		=	0x00200000;
	long CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS		= 	0x00400000;
	long CLIENT_SESSION_TRACK						= 	0x00800000;

	long CLIENT_DEPRECATE_EOF		=	0x01000000;	
}

package mysql;

/**
 * 客户端请求类型
 * @author shanl
 * @see <a href="http://dev.mysql.com/doc/internals/en/text-protocol.html">http://dev.mysql.com/doc/internals/en/text-protocol.html</a> 
 */
public interface ClientRequestType {
	/**(内部线程状态)*/
	int COM_SLEEP					= 	0x00;
	/**关闭连接*/
	int COM_QUIT 					= 	0x01;
	/**切换数据库*/
	int COM_INIT_DB 				= 	0x02;
	/**SQL查询请求*/
	int COM_QUERY 					= 	0x03;
	
	/**获取数据表字段信息*/
	int COM_FIELD_LIST 				= 	0x04;
	/**创建数据库*/
	int COM_CREATE_DB 				= 	0x05;
	/**删除数据库*/
	int COM_DROP_DB 				= 	0x06;
	/**清除缓存*/
	int COM_REFRESH 				= 	0x07;
	
	/**停止服务器*/
	int COM_SHUTDOWN 				= 	0x08;
	/**获取服务器统计信息*/
	int COM_STATISTICS 				= 	0x09;
	/**获取当前连接的列表*/
	int COM_PROCESS_INFO 			= 	0x0A;
	/**(内部线程状态)*/
	int COM_CONNECT 				= 	0x0B;
	
	/**中断某个连接*/
	int COM_PROCESS_KILL 			= 	0x0C;
	/**保存服务器调试信息*/
	int COM_DEBUG 					= 	0x0D;
	/**测试连通性*/
	int COM_PING 					= 	0x0E;
	/**(内部线程状态)*/
	int COM_TIME					= 	0x0F;
	
	/**(内部线程状态)*/
	int COM_DELAYED_INSERT 			= 	0x10;
	/**重新登陆(不断连接*/
	int COM_CHANGE_USER 			=   0x11;
	/**获取二进制日志信息*/
	int COM_BINLOG_DUMP				= 	0x12;
	/**获取数据表结构信息*/
	int COM_TABLE_DUMP				= 	0x13;
	
	/**(内部线程状态)*/
	int COM_CONNECT_OUT 			= 	0x14;
	/**从服务器向主服务器进行注册*/
	int COM_REGISTER_SLAVE			= 	0x15;
	/**预处理SQL语句*/
	int COM_STMT_PREPARE 			= 	0x16;
	/**执行预处理语句*/
	int COM_STMT_EXECUTE 			= 	0x17;
	
	/**发送BLOB类型的数据*/
	int COM_STMT_SEND_LONG_DATA		=	0x18;
	/**销毁预处理语句*/
	int COM_STMT_CLOSE				= 	0x19;
	/**清除预处理语句参数缓存*/
	int COM_STMT_RESET 				= 	0x1A;
	/**设置语句选项*/
	int COM_SET_OPTION 				= 	0x1B;
	
	/**获取预处理语句的执行结果*/
	int COM_STMT_FETCH				= 	0x1C;
}


package mysql;

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.zip.Inflater;

/**
 * 为mysql分析专用工具
 * @author shanl
 *
 */
public class Util {
	/**
	 * Big-Endian字节数组转uint32
	 * @param bys
	 * @param off
	 * @param len
	 * @return
	 */
	public static long bigBys2Uint32(byte[] bys,int off,int len){
		long uint32 = 0;
		for(int i=0,end=len-1,c=end; i<=end; i++,c--){
			uint32 |= (0xff&bys[off+i])<<(8*c);
		}
		return uint32;		
	}
	
	/**
	 * Little-Endian字节数组转uint32
	 * @param bys
	 * @param off
	 * @param len
	 * @return
	 */
	public static long littleBys2Uint32(byte[] bys,int off,int len){
		long uint32 = 0;
		for(int i=len-1; i>=0; i--){			
			uint32 |= (0xff&bys[off+i])<<(8*i);
		}
		return uint32;
	}
	
	/**
	 * 适用于mysql与客户端交互时zlib解压
	 * @param data
	 * @param off
	 * @param len
	 * @param out
	 * @throws Exception
	 */
	public static void decompress(byte[] data, int off, int len,OutputStream out){
		Inflater decompresser = new Inflater();  
		decompresser.reset();
		decompresser.setInput(data, off, len);
		byte[] buf = new byte[1024];
		
		try{
			while(!decompresser.finished()){
				int i = decompresser.inflate(buf);
				out.write(buf, 0, i);
				out.flush();
			}
		}catch(Exception ex){
			throw new RuntimeException(ex);
		}
	}
	
	/**
	 * 适用于mysql与客户端交互时zlib解压
	 * @param data 数据
	 * @param off 偏移量
	 * @param len 长度
	 * @return
	 */
	public static byte[] decompress(byte[] data, int off, int len) {  
		byte[] output = null; 
		Inflater decompresser = new Inflater();  
		decompresser.reset();  		
//		decompresser.setInput(data);
		decompresser.setInput(data, off, len);		
		  
        ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);  
        try {  
            byte[] buf = new byte[1024];  
            while (!decompresser.finished()) {  
                int i = decompresser.inflate(buf);  
                o.write(buf, 0, i);  
            }  
            output = o.toByteArray();  
        } catch (Exception e) {
            throw new RuntimeException(e);  
        } finally {  
            try {  
                o.close();  
            } catch (Exception e) {
            }  
        }  
  
        decompresser.end();  
        return output;  
	}
	
	/**
	 * 适用于mysql与客户端交互时zlib解压
	 * @param data 数据
	 * @param off 偏移量
	 * @param len 长度
	 * @param srcLen 源数据长度
	 * @return
	 */
	public static byte[] decompress(byte[] data, int off, int len, int srcLen) {  
		byte[] output = null; 
		Inflater decompresser = new Inflater();  
		decompresser.reset();  		
//		decompresser.setInput(data);
		decompresser.setInput(data, off, len);		
		  
        ByteArrayOutputStream o = new ByteArrayOutputStream(srcLen);  
        try {  
        	o.reset();
            byte[] buf = new byte[1024];  
            while (!decompresser.finished()) {  
                int i = decompresser.inflate(buf);  
                o.write(buf, 0, i);  
            }  
            output = o.toByteArray();  
        } catch (Exception e) {
            throw new RuntimeException(e);  
        } finally {  
            try {  
                o.close();  
            } catch (Exception e) {
            }  
        }  
  
        decompresser.end();  
        return output;  
	}
}

package mysql;

/**
 * 支持的字符编码
 * @author shanl
 * @see <a href="http://dev.mysql.com/doc/internals/en/character-set.html#packet-Protocol::CharacterSet">
 * http://dev.mysql.com/doc/internals/en/character-set.html#packet-Protocol::CharacterSet</a>
 */
public class CharacterSet {
	/***
	  int big5_chinese_ci              =1;  
	  int latin2_czech_cs              =2;  
	  int dec8_swedish_ci              =3;  
	  int cp850_general_ci             =4;  
	  int latin1_german1_ci            =5;  
	  int hp8_english_ci               =6;  
	  int koi8r_general_ci             =7;  
	  int latin1_swedish_ci            =8;  
	  int latin2_general_ci            =9;  
	  int swe7_swedish_ci             =10;  
	  int ascii_general_ci            =11;  
	  int ujis_japanese_ci            =12;  
	  int sjis_japanese_ci            =13;  
	  int cp1251_bulgarian_ci         =14;  
	  int latin1_danish_ci            =15;  
	  int hebrew_general_ci           =16;  
	  int tis620_thai_ci              =18;  
	  int euckr_korean_ci             =19;  
	  int latin7_estonian_cs          =20;  
	  int latin2_hungarian_ci         =21;  
	  int koi8u_general_ci            =22;  
	  int cp1251_ukrainian_ci         =23;  
	  int gb2312_chinese_ci           =24;  
	  int greek_general_ci            =25;  
	  int cp1250_general_ci           =26;  
	  int latin2_croatian_ci          =27;  
	  int gbk_chinese_ci              =28;  
	  int cp1257_lithuanian_ci        =29;  
	  int latin5_turkish_ci           =30;  
	  int latin1_german2_ci           =31;  
	  int armscii8_general_ci         =32;  
	  int utf8_general_ci             =33;  
	  int cp1250_czech_cs             =34;  
	  int ucs2_general_ci             =35;  
	  int cp866_general_ci            =36;  
	  int keybcs2_general_ci          =37;  
	  int macce_general_ci            =38;  
	  int macroman_general_ci         =39;  
	  int cp852_general_ci            =40;  
	  int latin7_general_ci           =41;  
	  int latin7_general_cs           =42;  
	  int macce_bin                   =43;  
	  int cp1250_croatian_ci          =44;  
	  int utf8mb4_general_ci          =45;  
	  int utf8mb4_bin                 =46;  
	  int latin1_bin                  =47;  
	  int latin1_general_ci           =48;  
	  int latin1_general_cs           =49;  
	  int cp1251_bin                  =50;  
	  int cp1251_general_ci           =51;  
	  int cp1251_general_cs           =52;  
	  int macroman_bin                53  
	  int utf16_general_ci            54  
	  int utf16_bin                   55  
	  int cp1256_general_ci           57  
	  int cp1257_bin                  58  
	  int cp1257_general_ci           59  
	  int utf32_general_ci            60  
	  int utf32_bin                   61  
	  int binary                      63  
	  int armscii8_bin                64  
	  int ascii_bin                   65  
	  int cp1250_bin                  66  
	  int cp1256_bin                  67  
	  int cp866_bin                   68  
	  int dec8_bin                    69  
	  int greek_bin                   70  
	  int hebrew_bin                  71  
	  int  hp8_bin                     72  
	  int keybcs2_bin                 73  
	  int koi8r_bin                   74  
	  int koi8u_bin                   75  
	  int latin2_bin                  77  
	  int latin5_bin                  78  
	  int latin7_bin                  79  
	  int cp850_bin                   80  
	  int cp852_bin                   81  
	  int swe7_bin                    82  
	  int utf8_bin                    83  
	  int big5_bin                    84  
	  int euckr_bin                   85  
	  int gb2312_bin                  86  
	  int gbk_bin                     87  
	  int sjis_bin                    88  
	  int tis620_bin                  89  
	  int ucs2_bin                    90  
	  int ujis_bin                    91  
	  int geostd8_general_ci          92  
	  int geostd8_bin                 93  
	  latin1_spanish_ci           94  
	  cp932_japanese_ci           95  
	  cp932_bin                   96  
	  eucjpms_japanese_ci         97  
	  eucjpms_bin                 98  
	  cp1250_polish_ci            99  
	  utf16_unicode_ci           101  
	  utf16_icelandic_ci         102  
	  utf16_latvian_ci           103  
	  utf16_romanian_ci          104  
	  utf16_slovenian_ci         105  
	  utf16_polish_ci            106  
	  utf16_estonian_ci          107  
	  utf16_spanish_ci           108  
	  utf16_swedish_ci           109  
	  utf16_turkish_ci           110  
	  utf16_czech_ci             111  
	  utf16_danish_ci            112  
	  utf16_lithuanian_ci        113  
	  utf16_slovak_ci            114  
	  utf16_spanish2_ci          115  
	  utf16_roman_ci             116  
	  utf16_persian_ci           117  
	  utf16_esperanto_ci         118  
	  utf16_hungarian_ci         119  
	  utf16_sinhala_ci           120  
	  ucs2_unicode_ci            128  
	  ucs2_icelandic_ci          129  
	  ucs2_latvian_ci            130  
	  ucs2_romanian_ci           131  
	  ucs2_slovenian_ci          132  
	  ucs2_polish_ci             133  
	  ucs2_estonian_ci           134  
	  ucs2_spanish_ci            135  
	  ucs2_swedish_ci            136  
	  ucs2_turkish_ci            137  
	  ucs2_czech_ci              138  
	  ucs2_danish_ci             139  
	  ucs2_lithuanian_ci         140  
	  ucs2_slovak_ci             141  
	  ucs2_spanish2_ci           142  
	  ucs2_roman_ci              143  
	  ucs2_persian_ci            144  
	  ucs2_esperanto_ci          145  
	  ucs2_hungarian_ci          146  
	  ucs2_sinhala_ci            147  
	  ucs2_general_mysql500_ci   159  
	  utf32_unicode_ci           160  
	  utf32_icelandic_ci         161  
	  utf32_latvian_ci           162  
	  utf32_romanian_ci          163  
	  utf32_slovenian_ci         164  
	  utf32_polish_ci            165  
	  utf32_estonian_ci          166  
	  utf32_spanish_ci           167  
	  utf32_swedish_ci           168  
	  utf32_turkish_ci           169  
	  utf32_czech_ci             170  
	  utf32_danish_ci            171  
	  utf32_lithuanian_ci        172  
	  utf32_slovak_ci            173  
	  utf32_spanish2_ci          174  
	  utf32_roman_ci             175  
	  utf32_persian_ci           176  
	  utf32_esperanto_ci         177  
	  utf32_hungarian_ci         178  
	  utf32_sinhala_ci           179  
	  utf8_unicode_ci            192  
	  utf8_icelandic_ci          193  
	  utf8_latvian_ci            194  
	  utf8_romanian_ci           195  
	  utf8_slovenian_ci          196  
	  utf8_polish_ci             197  
	  utf8_estonian_ci           198  
	  utf8_spanish_ci            199  
	  utf8_swedish_ci            200  
	  utf8_turkish_ci            201  
	  utf8_czech_ci              202  
	  utf8_danish_ci             203  
	  utf8_lithuanian_ci         204  
	  utf8_slovak_ci             205  
	  utf8_spanish2_ci           206  
	  utf8_roman_ci              207  
	  utf8_persian_ci            208  
	  utf8_esperanto_ci          209  
	  utf8_hungarian_ci          210  
	  utf8_sinhala_ci            211  
	  utf8_general_mysql500_ci   223  
	  utf8mb4_unicode_ci         224  
	  utf8mb4_icelandic_ci       225  
	  utf8mb4_latvian_ci         226  
	  utf8mb4_romanian_ci        227  
	  utf8mb4_slovenian_ci       228  
	  utf8mb4_polish_ci          229  
	  utf8mb4_estonian_ci        230  
	  utf8mb4_spanish_ci         231  
	  utf8mb4_swedish_ci         232  
	  utf8mb4_turkish_ci         233  
	  utf8mb4_czech_ci           234  
	  utf8mb4_danish_ci          235  
	  utf8mb4_lithuanian_ci      236  
	  utf8mb4_slovak_ci          237  
	  utf8mb4_spanish2_ci        238  
	  utf8mb4_roman_ci           239  
	  utf8mb4_persian_ci         240  
	  utf8mb4_esperanto_ci       241  
	  utf8mb4_hungarian_ci       242  
	  utf8mb4_sinhala_ci         243  
	  
	  ***/
}

package test2;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;

import mysql.ClientRequestType;
import mysql.MysqlAnalyse;
import mysql.MysqlSimpleProxy;
import mysql.Util;

/**
 * 这是测试类 
 * @author shanl
 *
 */
public class Test4 {
	public static void main(String[] args){
//		t1();
//		t2();
//		t3();
//		t4();
//		t5();
//		t6();
//		t7();
//		t8();
		t9();
	}
	
	static void t9(){
//		InetSocketAddress address = new InetSocketAddress(1024);
//		System.out.println(address.getHostName());
		MysqlSimpleProxy proxy = new MysqlSimpleProxy("192.168.2.182:3306");
		proxy.setListenPort(3306);
		proxy.setRecordDir("e:/a/2");
//		proxy.start();
		proxy.service(null);
	}
	
	/**
	 * 调用MysqlAnlyse进行分析
	 */
	static void t8(){
		long packLen = 0; 	//3byte
		long number = 0;	//1byte
		long compress = 0;	//3byte
		
		File srcDir = new File("e://a//2");
		File compressFile = new File(srcDir,"fc_103533_0.out.compress.3"); //uncompress
//		File compressFile = new File(srcDir,"fc_103533_0.out.compress.2"); //uncompress
//		File compressFile = new File(srcDir,"fc_103533_0.out.compress.1"); //compress
//		File compressFile = new File(srcDir,"fc_103533_0.out.decompress"); //non-compress
		
		int readLen = 0;
		byte[] buff = new byte[1024*4];
		
		try {
			FileInputStream fin = new FileInputStream(compressFile);
			readLen = fin.read(buff);			
			fin.close();
		} catch (Exception e) {		
			e.printStackTrace();
		}
		
		try{
			MysqlAnalyse anly = new MysqlAnalyse();
			anly.setProperty("compress", true);
			anly.setProperty("encoding", "utf-8");
			anly.parse(buff, 0, readLen);
			
			System.out.println(anly.getRequestType());
			System.out.println(anly.getSql());
		}catch(Exception e){
			e.printStackTrace();
		}
		
	}
	
	static void t7(){		
		byte[] capability_flags_lower = {
				(byte) 0xff,(byte) 0xf7
		};
		byte[] capability_flags_upper = {
				(byte) 7f,(byte) 0x80
		};
//		byte[] bys = {
//				capability_flags_upper[1],capability_flags_upper[0],
//				capability_flags_lower[1],capability_flags_lower[0]
//		};
//		byte[] bys = {
//				capability_flags_lower[0],capability_flags_lower[1],
//				capability_flags_upper[0],capability_flags_upper[1],				
//		};		
		byte[] bys = {				
				capability_flags_upper[0],capability_flags_upper[1],
				capability_flags_lower[0],capability_flags_lower[1],
		};
//		byte[] bys = {
////				(byte) 0x85,(byte) 0xa6
//				(byte) 0xa5,(byte) 0xa6
//		};
		
		long capability_flags = bigBys2Uint32(bys,0,bys.length);
//		System.out.println(capability_flags);
		
		long compress_flag = 0x00000020;		
		long odbc_flag = 0x00000040;
		long l = capability_flags & compress_flag;
		System.out.println(l);
		System.out.println(compress_flag==l);
	}
	
	static void t6(){
		byte[] bys = "中".getBytes();
		long i32 = bigBys2Uint32(bys,0,bys.length);
		System.out.println(i32);
	}
	
	static void t5(){
		long packLen = 0; 	//3byte
		long number = 0;	//1byte
		long compress = 0;	//3byte
		
		File srcDir = new File("e://a//2");
		File compressFile = new File(srcDir,"fc_103533_0.out.compress.1"); //uncompress
//		File compressFile = new File(srcDir,"fc_103533_0.out.compress.2"); //compress
		
		
		int readLen = 0;
		byte[] buff = new byte[1024*4];
		
		try {
			FileInputStream fin = new FileInputStream(compressFile);
			readLen = fin.read(buff);			
			fin.close();
		} catch (Exception e) {		
			e.printStackTrace();
		}
		
		try{
			packLen = littleBys2Uint32(buff,0,3);
			number = littleBys2Uint32(buff,3,1);
			compress = littleBys2Uint32(buff,4,3);
			
			System.out.println("readLen: "+readLen);
			System.out.println("packLen: "+packLen);
			System.out.println("number: " +number);
			System.out.println("compress: "+compress);
		}catch(Exception e){
			e.printStackTrace();
		}
		
		//decompress
		long srcLen = 0;
		long srcReqType = 0;
		byte[] srcData = null;
		try{
			if(compress>0){
				byte[] decompressData = Util.decompress(buff,3+1+3,readLen-7);
				srcLen = Util.littleBys2Uint32(decompressData,0,4)-1;//-1去掉clientRequestType
				srcReqType = Util.littleBys2Uint32(decompressData, 4, 1);
				srcData = new byte[(int) srcLen];
				System.arraycopy(decompressData, 5, srcData, 0, (int)srcLen);//
			}else{
				srcLen = Util.littleBys2Uint32(buff,3+1+3,4)-1; //-1去掉clientRequestType				
				srcReqType = Util.littleBys2Uint32(buff, 3+1+3+4, 1);				
				srcData = new byte[(int) srcLen];
				System.arraycopy(buff, 3+1+3+4+1, srcData, 0, (int)srcLen);				
			}
			
			System.out.println("srcLen: "+srcLen);
			System.out.println("srcReqType: "+srcReqType);
			if((int)srcReqType==ClientRequestType.COM_QUERY){
				System.out.println("sql: "+new String(srcData));
			}else{
				System.out.println("non-sql request.");
			}
			
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	static void t4(){
		byte[] bys = {
				(byte) 0xda,0x00,0x00,
		};
		byte[] bys1 = {
				0x00,0x00,(byte) 0xda,
		};
		
		System.out.println(littleBys2Uint32(bys,0,bys.length));
		System.out.println(bigBys2Uint32(bys1,0,bys1.length));
	}
	
	/**
	 * Big-Endian字节数组转uint32
	 * @param bys
	 * @param off
	 * @param len
	 * @return
	 */
	static long bigBys2Uint32(byte[] bys,int off,int len){
		long uint32 = 0;
		for(int i=0,end=len-1,c=end; i<=end; i++,c--){
			uint32 |= (0xff&bys[off+i])<<(8*c);
		}
		return uint32;		
	}
	
	/**
	 * Little-Endian字节数组转uint32
	 * @param bys
	 * @param off
	 * @param len
	 * @return
	 */
	static long littleBys2Uint32(byte[] bys,int off,int len){
		long uint32 = 0;
		for(int i=len-1; i>=0; i--){			
			uint32 |= (0xff&bys[off+i])<<(8*i);
		}
		return uint32;
	}
	
	static void t3(){
		short[] bys = {
				0xda,0x00,0x00,0x00
		};
		
		long uint32 = 0;
		for(int i=bys.length-1; i>=0; i--){			
			uint32 |= (0xff&bys[i])<<(8*i);
		}
		
		System.out.println(uint32);
//		System.out.println(0xff&0xda);
	}
	
	public static byte[] decompress(byte[] data, int off, int len) {  
		byte[] output = new byte[0]; 
		Inflater decompresser = new Inflater();  
		decompresser.reset();  		
//		decompresser.setInput(data);
		decompresser.setInput(data, off, len);		
		  
        ByteArrayOutputStream o = new ByteArrayOutputStream(data.length);  
        try {  
            byte[] buf = new byte[1024];  
            while (!decompresser.finished()) {  
                int i = decompresser.inflate(buf);  
                o.write(buf, 0, i);  
            }  
            output = o.toByteArray();  
        } catch (Exception e) {  
            output = data;  
            e.printStackTrace();  
        } finally {  
            try {  
                o.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
  
        decompresser.end();  
        return output;  
	}
	
	public static void decompress(byte[] data, int off, int len,OutputStream out) throws Exception{
		Inflater decompresser = new Inflater();  
		decompresser.reset();
		decompresser.setInput(data, off, len);
		byte[] buf = new byte[1024];
		
		while(!decompresser.finished()){
			int i = decompresser.inflate(buf);
			out.write(buf, 0, i);
			out.flush();
		}
	}
	
	static void t2(){
		File recordDir = new File("e://a//2");
		String srcFile = "fc_103533_0.out";
		File cf = new File(recordDir,srcFile+".compress");
//		File cf1 = new File(recordDir,"fc_103533_0.out.compress.1");
		File df = new File(recordDir,srcFile+".decompress");
		int readLen = 0;
		byte[] buff = new byte[1024*4];
		
		try {
			FileInputStream fin = new FileInputStream(cf);			
			readLen = fin.read(buff);
			fin.close();			
		} catch (Exception e) {		
			e.printStackTrace();
		}
		
//		try{
//			FileOutputStream fout = new FileOutputStream(cf1);
//			fout.write(buff,7,readLen-7);
//			fout.close();
//		}catch(Exception ex){
//			ex.printStackTrace();
//		}
		
		try{
			FileOutputStream fout = new FileOutputStream(df);
			decompress(buff,7,readLen-7,fout);
			fout.close();
		}catch(Exception ex){
			ex.printStackTrace();
		}
	}
	
	static final SimpleDateFormat sdf = new SimpleDateFormat("[HH:mm:ss]");
	static final SimpleDateFormat fsdf = new SimpleDateFormat("HHmmss");	
	/**
	 * 简单的代理,主要用于本地存储通信字节
	 */
	static void t1(){
		File recordDir = new File("e://a//2");
		String tsip = "192.168.2.182";		
		int tsport = 3306;
		int listenPort = 3306;
		ServerSocket ss = null;		
		Socket fcsocket = null;
		Socket tssocket = null;
		int readTimeout = 1000*60*5;
				
		try{
			System.out.println("listen port:"+listenPort);
			ss = new ServerSocket(listenPort);
			for(int i=0;;i++){				
				fcsocket = ss.accept();
				fcsocket.setSoTimeout(readTimeout);
				System.out.println("new connected "+i);
				tssocket = new Socket(tsip,tsport);
				tssocket.setSoTimeout(readTimeout);				
				String fname_padding = fsdf.format(new Date())+"_"+i+".out";
				new Recording(fcsocket,tssocket,new File(recordDir,"fc_"+fname_padding)).start();
				new Recording(tssocket,fcsocket,new File(recordDir,"ts_"+fname_padding)).start();
			}
		}catch(Exception ex){			
		}
	}
	
	static class Recording extends Thread{
		Socket fcsocket = null;
		Socket tssocket = null;
		File recordFile = null;
				
		public Recording(Socket from,Socket to,File file){
			this.fcsocket = from;
			this.tssocket = to;
			this.recordFile = file;
		}		
		
		public void run(){
			InputStream fcin = null;
			OutputStream tsout = null;
			int readLen = 0;
			byte[] buff = new byte[1024*4];
			FileOutputStream fileOut = null;
			
			try{
				recordFile.createNewFile();
				fcin = fcsocket.getInputStream();
				tsout = tssocket.getOutputStream();				
				fileOut = new FileOutputStream(recordFile);				
			}catch(Exception ex){
				try { fileOut.close(); } catch (IOException e) { }
				try { fcsocket.close(); } catch (IOException e) { }
				try { tssocket.close(); } catch (IOException e) { }
				return;
			}			
			
			for(;;){
				try{
					readLen = fcin.read(buff);
					tsout.write(buff,0,readLen);
					tsout.flush();
					
					fileOut.write(sdf.format(new Date()).getBytes());
					fileOut.write(buff,0,readLen);
					fileOut.flush();
				}catch(SocketTimeoutException ste){
					continue;
				}catch(Exception ex){		
					break;
				}
			}
			
			try{fileOut.close();}catch(Exception ex){}
		}
		
	}
}

package mysql;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;

import org.apache.log4j.Logger;

/**
 * 一个简单的mysql代理 <br/>
 * 主要用于分析和截获sql语句
 * @author shanl
 *
 */
public class MysqlSimpleProxy {	
	ExecutorService threadPool = null;
	InetSocketAddress targetAddress = null;
	int listenPort = 3306;
	static final Logger logger = Logger.getLogger(MysqlSimpleProxy.class);
	String recordDir = ".";
	
	/**
	 * 
	 * @param targetAddress 目标服务器地址,格式 <ip>[:port] port默认3306
	 */
	public MysqlSimpleProxy(String targetAddress){
		String[] ss = targetAddress.split(":");
		this.targetAddress = new InetSocketAddress(ss[0],
				1==ss.length? 3306: Integer.parseInt(ss[1]));
	}
		
	public void start(){
		ServerSocket ss = null;
						
		try {			
			ss = new ServerSocket(listenPort);
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
			return;
		}
		
		logger.info("MysqlPort listen port:" + listenPort);
		System.err.println("MysqlPort listen port:" + listenPort);
		Socket fromClient = null;
		for(int i=0;;i++){
			try{				
				fromClient = ss.accept();
				connectHandler(fromClient);
				logger.info("MysqlPort new connect:"+i);
			}catch(Exception e){
				logger.error(e.getMessage(), e);
				break;
			}			
		}
		
		try{ss.close();}catch(Exception ex){}
	}
		
	/**
	 * 以服务方式启动
	 * @param threadPool 可以为null
	 */
	public void service(ExecutorService threadPool){
		Thread t = new Thread(
			new Runnable(){
				@Override
				public void run() {
					start();				
				}},
			"MysqlProxy");
				
		if(null==threadPool) t.start();
		else threadPool.execute(t);
	}
	
	static final SimpleDateFormat sdf = new SimpleDateFormat("yyMMddHHmmss");
	static final SimpleDateFormat head = new SimpleDateFormat("[dd HH:mm:ss]");
	void connectHandler(Socket fromClient){
		Socket toServer = null;
		
		try{
			toServer = new Socket();
			toServer.connect(targetAddress, 1000*10);
			
			FileOutputStream out = new FileOutputStream(new File(recordDir,
					"c_"+sdf.format(new Date())+"_"+fromClient.getInetAddress().getHostAddress()
					+".record"));
			InetAddress fcAddress = fromClient.getInetAddress();
			out.write(head.format(new Date()).getBytes());
			out.write(("client:"+fcAddress.getHostAddress()).getBytes());
			out.write((",server:"+targetAddress.getHostString()+":"+targetAddress.getPort()).getBytes());			
			out.flush();
			
			Thread c2s = new Thread(new Worker(fromClient, toServer, out));
			Thread s2c = new Thread(new Worker(toServer, fromClient, null));
			if(null==threadPool){
				c2s.start();
				s2c.start();
			}else{
				threadPool.execute(c2s);
				threadPool.execute(s2c);
			}
		}catch(Exception e){
			logger.error(e.getMessage(), e);
			try{if(null!=toServer)toServer.close();}catch(Exception ex){}
		}
	}
	
	/**
	 * 设置本地监听地址,默认3306
	 * @param port
	 */
	public void setListenPort(int port){
		this.listenPort = port;
	}
	
	public void setThreadPool(ExecutorService threadPool){
		this.threadPool = threadPool;
	}
	
	/**
	 * 设置截获数据的存储目录
	 * @param dir 默认'./'
	 */
	public void setRecordDir(String dir){
		this.recordDir = dir;
	}
	
	static class Worker extends Thread{
		Socket from = null;
		Socket to = null;
		OutputStream record = null;
		
		public Worker(Socket from,Socket to,OutputStream record){
			this.from = from;
			this.to = to;
			this.record = record;
		}
		
		public void run(){
			int readLen = 0;
			byte[] buff = new byte[1024*1024*16];
			MysqlAnalyse analy = null;
			StringBuilder sb = new StringBuilder();
			
			try{
				InputStream in = from.getInputStream();
				OutputStream out = to.getOutputStream();
				
				readLen = in.read(buff);
				out.write(buff,0,readLen);
				out.flush();
				if(null!=record)analy = MysqlAnalyse.handshake(buff, 0, readLen);
				
				while(-1!=(readLen=in.read(buff)) ){
					if(null!=record){
						boolean endPack = analy.parse(buff, 0, readLen);
						if(ClientRequestType.COM_QUERY==analy.getRequestType()){
							sb.append(analy.getSql());
							if(endPack){
								record.write((MysqlSimpleProxy.head.format(new Date())).getBytes());
								record.write(sb.toString().getBytes());
								record.flush();
								sb = new StringBuilder();
							}
						}									
//						logger.info("requestType:"+analy.getRequestType()+",sql:"+analy.getSql());
					}
					
					out.write(buff,0,readLen);
					out.flush();
				}
			}catch(Exception e){
				logger.error(e.getMessage(), e);
			}finally{
				try{if(null!=from)from.close();}catch(Exception e){}
				try{if(null!=to)to.close();}catch(Exception e){}
			}
		}
		
	}
	
}


你可能感兴趣的:(mysql协议分析)