在深刻了解SMProxy以後,發現SMProxy的奇妙之處就在於你並不須要對框架的數據庫模塊進行任何的修改,便可使用SMProxy架構,它是基於mysql客戶端與mysql服務端的中間件,經過swoole/server本身模擬與mysql報文交互並內部管理鏈接池對象來提高效率。php
swoole:
運用到的知識點 swoole/server以及swoole/client, 不作更多的介紹
tcp 粘包問題: https://www.cnblogs.com/JsonM...
client -> tcp buffer(等待cpu指令, 若是buffer緩存達到上限,就會直接發送到server, 全部有可能一次性接受多個數據) -> serverhtml
mysql 協議分析
https://www.cnblogs.com/davyg...mysql
// 位於SMProxy/src/Handler/Frontend/FrontendAuthticator public function getHandshakePacket(int $server_id) { $rand1 = RandomUtil::randomBytes(8); $rand2 = RandomUtil::randomBytes(12); $this->seed = array_merge($rand1, $rand2); $hs = new HandshakePacket(); $hs->packetId = 0; // 如下根據握手報文 // 協議版本號 $hs->protocolVersion = Versions::PROTOCOL_VERSION; // 服務器版本號信息 $hs->serverVersion = Versions::SERVER_VERSION; // 服務器線程 $hs->threadId = $server_id; // 隨機數 $hs->seed = $rand1; // 填充值,服務器權能標識, $hs->serverCapabilities = $this->getServerCapabilities(); // 字符編碼 $hs->serverCharsetIndex = (CharsetUtil::getIndex(CONFIG['server']['charset'] ?? 'utf8mb4') & 0xff); // 服務器狀態 $hs->serverStatus = 2; // 服務器權能標識+填充值 $hs->restOfScrambleBuff = $rand2; return getString($hs->write()); } //位於 SMProxy/src/HandshakePacket public function write() { // default init 256,so it can avoid buff extract $buffer = []; // 寫入消息頭長度 BufferUtil::writeUB3($buffer, $this->calcPacketSize()); // 寫入序號 -- 消息頭的 $buffer[] = $this->packetId; // 寫入協議版本號 $buffer[] = $this->protocolVersion; // 寫入服務器版本信息 BufferUtil::writeWithNull($buffer, getBytes($this->serverVersion)); // 寫入服務器線程ID BufferUtil::writeUB4($buffer, $this->threadId); // 挑戰隨機數 9個字節 包含一個填充值 BufferUtil::writeWithNull($buffer, $this->seed); // 服務器權能標識 BufferUtil::writeUB2($buffer, $this->serverCapabilities); // 1字節 字符編碼 $buffer[] = $this->serverCharsetIndex; // 服務器狀態 BufferUtil::writeUB2($buffer, $this->serverStatus); if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) { // 服務器權能標誌 16位 BufferUtil::writeUB2($buffer, $this->serverCapabilities); // 挑戰長度+填充值+挑戰隨機數 $buffer[] = max(13, count($this->seed) + count($this->restOfScrambleBuff) + 1); $buffer = array_merge($buffer, [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); } else { // 10字節填充數 $buffer = array_merge($buffer, self::$FILLER_13); } // +12字節挑戰隨機數 if ($this ->serverCapabilities & Capabilities::CLIENT_SECURE_CONNECTION) { BufferUtil::writeWithNull($buffer, $this->restOfScrambleBuff); } if ($this ->serverCapabilities & Capabilities::CLIENT_PLUGIN_AUTH) { BufferUtil::writeWithNull($buffer, getBytes($this->pluginName)); } return $buffer; }
// 位於SMProxy/src/SMProxyServer private function auth(BinaryPacket $bin, \swoole_server $server, int $fd) { // 若是數據長度是20, -- 可能自定義的, 4-20是密碼, 最後4位不知道幹啥 if ($bin->data[0] == 20) { // 密碼長度是16 , 判斷帳號密碼 $checkAccount = $this->checkAccount($server, $fd, $this->source[$fd]->user, array_copy($bin->data, 4, 20)); if (!$checkAccount) { // 發送ERROR報文 $this ->accessDenied($server, $fd, 4); } else { if ($server->exist($fd)) { // 發送OK報文 $server->send($fd, getString(OkPacket::$SWITCH_AUTH_OK)); } // 認證標誌設置爲true $this->source[$fd]->auth = true; } } elseif ($bin->data[4] == 14) { // 序號等於14 if ($server->exist($fd)) { // 無需認證即登陸 $server->send($fd, getString(OkPacket::$OK)); } } else { $authPacket = new AuthPacket(); // 讀取報文信息 登陸認證報文 $authPacket->read($bin); // 判斷帳號密碼 $checkAccount = $this->checkAccount($server, $fd, $authPacket->user ?? '', $authPacket->password ?? []); if (!$checkAccount) { // 密碼校驗失敗 if ($authPacket->pluginName == 'mysql_native_password') { // 發送ERROR報文 $this ->accessDenied($server, $fd, 2); } else { // 記錄用戶數據 $this->source[$fd]->user = $authPacket ->user; $this->source[$fd]->database = $authPacket->database; // 填充數 $this->source[$fd]->seed = RandomUtil::randomBytes(20); // 發送EOF報文 $authSwitchRequest = array_merge( [254], getBytes('mysql_native_password'), [0], $this->source[$fd]->seed, [0] ); if ($server->exist($fd)) { $server->send($fd, getString(array_merge(getMysqlPackSize(count($authSwitchRequest)), [2], $authSwitchRequest))); } } } else { // 帳號正確 發送OK報文, 並記錄數據 if ($server->exist($fd)) { $server->send($fd, getString(OkPacket::$AUTH_OK)); } $this->source[$fd]->auth = true; $this->source[$fd]->database = $authPacket->database; } } }
https://github.com/linjinmin/...git
https://www.cnblogs.com/JsonM... // tcp粘包問題
https://www.cnblogs.com/davyg... // mysql協議github