此文已由做者易國強受權網易雲社區發佈。
前端
歡迎訪問網易雲社區,瞭解更多網易技術產品運營經驗。sql
Title:MySql鏈接創建以及認證過程client->MySql:1.TCP鏈接請求 MySql->client:2.接受TCP鏈接client->MySql:3.TCP鏈接創建MySql->client:4.握手包HandshakePacketclient->MySql:5.認證包AuthPacketMySql->client:6.若是驗證成功,則返回OkPacketclient->MySql:7.默認會發送查詢版本信息的包MySql->client:8.返回結果包
MySql客戶端在鏈接創建後,默認會發送查詢版本信息的包,這其實就是一個SQL查詢請求了。只不過這個請求不用路由到後臺某個數據庫^_^。 鏈接成功創建後,鏈接綁定的RW線程會監聽上面的讀事件。在客戶端發送查詢版本信息的包以後,會觸發RW線程去讀取對應鏈接,過程與以前接收AuthPacket相似: RW類代碼片斷數據庫
//監聽到有效讀if (key.isValid() && key.isReadable()) { try { //異步讀取數據並處理數據 con.asynRead(); } catch (IOException e) { con.close("program err:" + e.toString()); continue; } catch (Exception e) { LOGGER.debug("caught err:", e); con.close("program err:" + e.toString()); continue; } }
以後的讀取過程也是調用AbstractConnection的asynRead()方法,進行異步讀取。過程就再也不贅述,讀取到的數據交由FrontendCommandHandler處理。 查詢版本信息的包(是一種CommandPacket)內容: CommandPacket:安全
packet length (3)網絡
packet number (1)app
command (1)異步
statement (null terminated string)ide
FrontendCommandHandler的處理方法:ui
@Override public void handle(byte[] data) { if(source.getLoadDataInfileHandler()!=null&&source.getLoadDataInfileHandler().isStartLoadData()) { MySQLMessage mm = new MySQLMessage(data); int packetLength = mm.readUB3(); if(packetLength+4==data.length) { source.loadDataInfileData(data); } return; } switch (data[4]) { case MySQLPacket.COM_INIT_DB: commands.doInitDB(); source.initDB(data); break; case MySQLPacket.COM_QUERY: commands.doQuery(); source.query(data); break; case MySQLPacket.COM_PING: commands.doPing(); source.ping(); break; case MySQLPacket.COM_QUIT: commands.doQuit(); source.close("quit cmd"); break; case MySQLPacket.COM_PROCESS_KILL: commands.doKill(); source.kill(data); break; case MySQLPacket.COM_STMT_PREPARE: commands.doStmtPrepare(); source.stmtPrepare(data); break; case MySQLPacket.COM_STMT_EXECUTE: commands.doStmtExecute(); source.stmtExecute(data); break; case MySQLPacket.COM_STMT_CLOSE: commands.doStmtClose(); source.stmtClose(data); break; case MySQLPacket.COM_HEARTBEAT: commands.doHeartbeat(); source.heartbeat(data); break; default: commands.doOther(); source.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Unknown command"); } }
根據CommandPacket的第五字節判斷command類型,不一樣類型有不一樣的處理。 首先querycommand計數加1,以後調用對應FrontendConnection的query(byte[])方法:this
public void query(byte[] data) { if (queryHandler != null) { // 取得語句|get sql MySQLMessage mm = new MySQLMessage(data); //從第六字節開始讀取|read from the 6th byte mm.position(5); String sql = null; try { sql = mm.readString(charset); } catch (UnsupportedEncodingException e) { writeErrMessage(ErrorCode.ER_UNKNOWN_CHARACTER_SET, "Unknown charset '" + charset + "'"); return; } if (sql == null || sql.length() == 0) { writeErrMessage(ErrorCode.ER_NOT_ALLOWED_COMMAND, "Empty SQL"); return; } // sql = StringUtil.replace(sql, "`", ""); // 移除末尾';'|remove last ';' if (sql.endsWith(";")) { sql = sql.substring(0, sql.length() - 1); } // 記錄SQL|record SQL this.setExecuteSql(sql); // 執行查詢 queryHandler.setReadOnly(privileges.isReadOnly(user)); queryHandler.query(sql); } else { writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR, "Query unsupported!"); } }
執行查詢,調用對應的FrontendQueryHandler: 這裏,很明顯,是ServerQueryHandler。
public void query(String sql) { ServerConnection c = this.source; if (LOGGER.isDebugEnabled()) { LOGGER.debug(new StringBuilder().append(c).append(sql).toString()); } // int rs = ServerParse.parse(sql); int sqlType = rs & 0xff; switch (sqlType) { case ServerParse.EXPLAIN: ExplainHandler.handle(sql, c, rs >>> 8); break; case ServerParse.EXPLAIN2: Explain2Handler.handle(sql, c, rs >>> 8); break; case ServerParse.SET: SetHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SHOW: ShowHandler.handle(sql, c, rs >>> 8); break; case ServerParse.SELECT: if(QuarantineHandler.handle(sql, c)){ SelectHandler.handle(sql, c, rs >>> 8); } break; case ServerParse.START: StartHandler.handle(sql, c, rs >>> 8); break; case ServerParse.BEGIN: BeginHandler.handle(sql, c); break; case ServerParse.SAVEPOINT: SavepointHandler.handle(sql, c); break; case ServerParse.KILL: KillHandler.handle(sql, rs >>> 8, c); break; case ServerParse.KILL_QUERY: LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_UNKNOWN_COM_ERROR,"Unsupported command"); break; case ServerParse.USE: UseHandler.handle(sql, c, rs >>> 8); break; case ServerParse.COMMIT: c.commit(); break; case ServerParse.ROLLBACK: c.rollback(); break; case ServerParse.HELP: LOGGER.warn(new StringBuilder().append("Unsupported command:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_SYNTAX_ERROR, "Unsupported command"); break; case ServerParse.MYSQL_CMD_COMMENT: c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.MYSQL_COMMENT: c.write(c.writeToBuffer(OkPacket.OK, c.allocate())); break; case ServerParse.LOAD_DATA_INFILE_SQL: c.loadDataInfileStart(sql); break; default: if(readOnly){ LOGGER.warn(new StringBuilder().append("User readonly:").append(sql).toString()); c.writeErrMessage(ErrorCode.ER_USER_READ_ONLY, "User readonly"); break; } if(QuarantineHandler.handle(sql, c)){ c.execute(sql, rs & 0xff); } } }
針對每種command,都有不一樣的handler和處理方式。以後如何處理,就在以後的SQL解析器等章節進行分析。
更多網易技術、產品、運營經驗分享請點擊。
相關文章:
【推薦】 Hive中文註釋亂碼解決方案
【推薦】 知物由學|你真的瞭解網絡安全嗎?