国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

MySQL協議.NET Core實現(一)

2019-11-08 02:27:55
字體:
來源:轉載
供稿:網友

導語

個高水平的研發團對,無論使用什么框架、什么工具、什么語言,團隊里應該有人有能力把控所使用框架、工具、語言的每一個核心功能的實現細節。團隊里的每個成員應該根據自身所長挑選其中一塊做深入研究,并把研究成果分享給團隊,力爭使整個所處團隊實力得到提升,達到同行業內頂尖水平。為了實現這個目標,不允許在團隊中出現黑盒子,對.NET生態而言,我們需要打開MSBuildRosylnCoreCLR等黑盒子。我作為團隊的一員,將花一些業余時間打開MySQL .NET驅動黑盒子,使用.NET Core實現MySQL Client/Server PRotocol,并把打開這個黑盒子的過程通過本站點記錄下來,包括知識儲備、實現思路,框架代碼等。

這是使用.NET Core實現MySQL協議系列文章的第一篇。

知識儲備(可跳過)

網絡中進程之間如何通信?

在網絡時代,網絡中進程間通信無處不在,在本地可以通過進程PID來唯一標識一個進程,但網絡中進程之間是如何通信的?其實TCP/ip協議族已經幫我們解決了這個問題,網絡層的IP地址可以唯一標識網絡中的主機,而傳輸層的協議+端口可以唯一標識主機中的應用程序(進程),因此,可以利用三元組(IP地址,協議,端口)標識網絡中的進程,網絡中的進程通信就可以利用這個標志與其它進程進行交互。無論應用層采用什么協議,最終都要經過傳輸層/網絡層的TCP/IP協議,而TCP/IP編程有一套標準編程接口——Socket。

什么是Socket?

套接字(Socket)是通信的基石,是支持TCP/IP協議的網絡通信的基本操作單元,但Socket跟TCP/IP并沒有必然的聯系,TCP/IP只是一個協議棧,就像操作系統的運行機制一樣,必須要具體實現,同時還要提供對外的操作接口,TCP/IP也必須對外提供編程接口——Berkeley sockets interface.,它是網絡通信過程中端點的抽象表示,包含進行網絡通信必須的五種信息:

連接使用的協議;本地主機的IP地址;本地進程的協議端口;遠地主機的IP地址;遠地進程的協議端口;

TCP三次握手建立連接

我們知道TCP建立連接要進行三次握手,大致流程如下:

客戶端向服務器發送一個SYN x服務器向客戶端響應一個SYN y,并對SYN x進行確認ACK x+1客戶端再向服務器發一個確認ACK y+1

TCP四次揮手斷開連接

TCP斷開連接要進行四次揮手,大致流程如下:

應用進程首先調用close主動關閉連接,這時TCP發送一個FIN x另一端接收到FIN x之后,執行被動關閉,對這個FIN進行確認一段時間之后,應用進程調用close關閉它的socket,這導致它的TCP也發送一個FIN y接收到這個FIN的源發送端TCP對它進行確認

這樣每個方向上都有一個FIN和ACK

MySQL協議分析

TCP/IP協議(上面的知識儲備就是為此準備的,所以,本系列文章假設TCP/IP協議)Unix Socket協議Share Memory協議Named Pipes協議

交互過程

MySQL客戶端與服務器的交互主要分為兩個階段

握手認證階段;命令執行階段;

握手認證階段

握手認證階段為客戶端與服務器通過TCP三次握手建立連接后進行,交互過程如下

服務器 -> 客戶端:握手初始化消息客戶端 -> 服務器:登陸認證消息服務器 -> 客戶端:認證結果消息

命令執行階段

客戶端認證成功后,會進入命令執行階段,交互過程如下

客戶端 -> 服務器:執行命令消息服務器 -> 客戶端:命令執行結果

MySQL客戶端與服務器的完整交互過程如下

MySQL協議中使用到的基本類型

MySQL官網鏈接

這里的信息很重要,能不能理解后續代碼,這里是關鍵點之一,務必銘記于心。

這里的信息很重要,能不能理解后續代碼,這里是關鍵點之一,務必銘記于心。

這里的信息很重要,能不能理解后續代碼,這里是關鍵點之一,務必銘記于心。

重要的事情說三遍。

整型值

MySQL報文中整型值分別有1、2、3、4、8字節長度,使用小字節序傳輸。 注意: 字節序

字符串(Null-Terminated String)

字符串長度不固定,當遇到'NULL'(0x00)字符時結束。

二進制數據(Length Coded Binary)

數據長度不固定,長度值由數據前的1-9個字節決定,其中長度值所占的字節數不定,字節數由第1個字節決定,如下表:

第一個字節值后續字節數長度值說明
0-2500第一個字節值即為數據的真實長度
2510空數據,數據的真實長度為零
2522后續額外2個字節標識了數據的真實長度
2533后續額外3個字節標識了數據的真實長度
2548后續額外8個字節標識了數據的真實長度

字符串(Length Coded String)

字符串長度不固定,無'NULL'(0x00)結束符,編碼方式與上面的Length Coded Binary相同。

報文結構

報文分為消息頭和消息體兩部分,其中消息頭占用固定的4個字節,消息體長度由消息頭中的長度字段決定,報文結構如下:

報文結構

消息頭

報文長度

用于標記當前請求消息的實際數據長度值,以字節為單位,占用3個字節,最大值為 0xFFFFFF,即接近 16 MB 大小(比16MB少1個字節)。

序號

在一次完整的請求/響應交互過程中,用于保證消息順序的正確,每次客戶端發起請求時,序號值都會從0開始計算。

消息體

消息體用于存放請求的內容及響應的數據,長度由消息頭中的長度值決定。

代碼實現

使用.NET Core實現MySQL協議,是一個C#類型與MySQL協議內容雙向Map的過程:

把MySQL協議報文包拆解成C#類型;把C#類型封裝成MySQL協議報文包;

這篇文章,我們先實現部分把MySQL協議報文包拆解成C#類型相關代碼,Socket會把MySQL報文包以buffer(byte[])的形式返回給我們,具體C#代碼我們留到下一篇文章。通過把buffer(byte[])一片一片地按照MySQL協議切下來(記錄已經被切掉的位置offset以及buffer的最大位置maxOffset),再把切片組裝成C#類型,代碼如下:

讀取報文長度

數據長度不固定,長度值由數據前的1-9個字節決定,其中長度值所占的字節數不定,字節數由第1個字節決定,如下表:

第一個字節值后續字節數長度值說明
0-2500第一個字節值即為數據的真實長度
2510空數據,數據的真實長度為零
2522后續額外2個字節標識了數據的真實長度
2533后續額外3個字節標識了數據的真實長度
2548后續額外8個字節標識了數據的真實長度

從上表可以看出,報文的最大長度是8個字節,8字節 * 每字節8bit = 64bit, 結合C#數據類型,能表示8個字節的基礎數據類是是Int64UInt64,因為報文長度不可能為負,所以使用UInt64, 可以使用以下代碼實現Protocol::LengthEncodedInteger

public UInt64 ReadLengthEncodedInteger(){    // https://dev.mysql.com/doc/internals/en/integer.html    byte encodedLength = buffer[offset++];    switch (encodedLength)    {        case 0xFC:            return ReadFixedLengthUInt32(readByteCount: 2);        case 0xFD:            return ReadFixedLengthUInt32(readByteCount: 3);        case 0xFE:            return ReadFixedLengthUInt64(readByteCount: 8);        case 0xFF:            throw new FormatException("Length-encoded integer cannot have 0xFF prefix byte.");        default:            return encodedLength;    }}public uint ReadFixedLengthUInt32(int readByteCount){    if (readByteCount <= 0 || readByteCount > 4)        throw new ArgumentOutOfRangeException(nameof(readByteCount));    uint result = 0;    for (int i = 0; i < readByteCount; i++)        result |= ((uint)buffer[offset + i]) << (8 * i);    offset += readByteCount;    return result;}public ulong ReadFixedLengthUInt64(int readByteCount){    if (readByteCount <= 0 || readByteCount > 8)        throw new ArgumentOutOfRangeException(nameof(readByteCount));    ulong result = 0;    for (int i = 0; i < readByteCount; i++)        result |= ((ulong)buffer[offset + i]) << (8 * i);    offset += readByteCount;    return result;}

讀取整型值

MySQL報文中整型值分別有1、2、3、4、8字節長度,使用小字節序傳輸(注意上面代碼位移操作符)。

public byte ReadByte(){    return buffer[offset++];}public void ReadByte(byte value){    if (ReadByte() != value)        throw new FormatException("Expected to read 0x{0:X2} but got 0x{1:X2}".FormatInvariant(value, buffer[offset - 1]));}public short ReadInt16(){    var result = BitConverter.ToInt16(buffer, offset);    offset += 2;    return result;}public ushort ReadUInt16(){    var result = BitConverter.ToUInt16(buffer, offset);    offset += 2;    return result;}public int ReadInt32(){    var result = BitConverter.ToInt32(buffer, offset);    offset += 4;    return result;}public uint ReadUInt32(){    var result = BitConverter.ToUInt32(buffer, offset);    offset += 4;    return result;}

讀取字符串(Null-Terminated String)

public byte[] ReadNullTerminatedByteString(){    // https://dev.mysql.com/doc/internals/en/string.html    // Protocol::NulTerminatedString: Strings that are terminated by a 0x00 byte.    int index = offset;    while (index < maxOffset && buffer[index] != 0x00)        index++;    if (index == maxOffset)        throw new FormatException("Read past end of buffer looking for NUL.");    byte[] substring = new byte[index - offset];    Buffer.BlockCopy(buffer, offset, substring, 0, substring.Length);    offset = index + 1;    return substring;}

讀取字符串(Length Coded String)

public ArraySegment<byte> ReadLengthEncodedByteString(){    // https://dev.mysql.com/doc/internals/en/string.html    // Protocol::LengthEncodedString    var length = checked((int)ReadLengthEncodedInteger());    var result = new ArraySegment<byte>(buffer, offset, length);    offset += length;    return result;}

總結

把以上代碼匯總成ByteArrayReader

using System;namespace MySql.Data{    internal sealed class ByteArrayReader    {        public ByteArrayReader(byte[] buffer, int offset, int length)        {            if (buffer == null)                throw new ArgumentNullException(nameof(buffer));            if (offset < 0)                throw new ArgumentOutOfRangeException(nameof(offset));            if (offset + length > buffer.Length)                throw new ArgumentOutOfRangeException(nameof(length));            this.buffer = buffer;            maxOffset = offset + length;            this.offset = offset;        }        public ByteArrayReader(ArraySegment<byte> arraySegment)            : this(arraySegment.Array, arraySegment.Offset, arraySegment.Count)        {        }        public int Offset        {            get { return offset; }            set            {                if (value < 0 || value > maxOffset)                    throw new ArgumentOutOfRangeException(nameof(value), "value must be between 0 and {0}".FormatInvariant(maxOffset));                offset = value;            }        }        public byte ReadByte()        {            VerifyRead(1);            return buffer[offset++];        }        public void ReadByte(byte value)        {            if (ReadByte() != value)                throw new FormatException("Expected to read 0x{0:X2} but got 0x{1:X2}".FormatInvariant(value, buffer[offset - 1]));        }        public short ReadInt16()        {            VerifyRead(2);            var result = BitConverter.ToInt16(buffer, offset);            offset += 2;            return result;        }        public ushort ReadUInt16()        {            VerifyRead(2);            var result = BitConverter.ToUInt16(buffer, offset);            offset += 2;            return result;        }        public int ReadInt32()        {            VerifyRead(4);            var result = BitConverter.ToInt32(buffer, offset);            offset += 4;            return result;        }        public uint ReadUInt32()        {            VerifyRead(4);            var result = BitConverter.ToUInt32(buffer, offset);            offset += 4;            return result;        }        public uint ReadFixedLengthUInt32(int readByteCount)        {            if (readByteCount <= 0 || readByteCount > 4)                throw new ArgumentOutOfRangeException(nameof(readByteCount));            VerifyRead(readByteCount);            uint result = 0;            for (int i = 0; i < readByteCount; i++)                result |= ((uint) buffer[offset + i]) << (8 * i);            offset += readByteCount;            return result;        }        public ulong ReadFixedLengthUInt64(int readByteCount)        {            if (readByteCount <= 0 || readByteCount > 8)                throw new ArgumentOutOfRangeException(nameof(readByteCount));            VerifyRead(readByteCount);            ulong result = 0;            for (int i = 0; i < readByteCount; i++)                result |= ((ulong) buffer[offset + i]) << (8 * i);            offset += readByteCount;            return result;        }        public byte[] ReadNullTerminatedByteString()        {            // https://dev.mysql.com/doc/internals/en/string.html            // Protocol::NulTerminatedString: Strings that are terminated by a 0x00 byte.            int index = offset;            while (index < maxOffset && buffer[index] != 0x00)                index++;            if (index == maxOffset)                throw new FormatException("Read past end of buffer looking for NUL.");            byte[] substring = new byte[index - offset];            Buffer.BlockCopy(buffer, offset, substring, 0, substring.Length);            offset = index + 1;            return substring;        }        public byte[] ReadByteString(int readByteCount)        {            VerifyRead(readByteCount);            var result = new byte[readByteCount];            Buffer.BlockCopy(buffer, offset, result, 0, result.Length);            offset += readByteCount;            return result;        }        public UInt64 ReadLengthEncodedInteger()        {            // https://dev.mysql.com/doc/internals/en/integer.html            byte encodedLength = buffer[offset++];            switch (encodedLength)            {            case 0xFC:                return ReadFixedLengthUInt32(readByteCount: 2);            case 0xFD:                return ReadFixedLengthUInt32(readByteCount: 3);            case 0xFE:                return ReadFixedLengthUInt64(readByteCount: 8);            case 0xFF:                throw new FormatException("Length-encoded integer cannot have 0xFF prefix byte.");            default:                return encodedLength;            }        }        public ArraySegment<byte> ReadLengthEncodedByteString()        {            // https://dev.mysql.com/doc/internals/en/string.html            // Protocol::LengthEncodedString            var length = checked((int) ReadLengthEncodedInteger());            var result = new ArraySegment<byte>(buffer, offset, length);            offset += length;            return result;        }        public int BytesRemaining => maxOffset - offset;        private void VerifyRead(int length)        {            if (offset + length > maxOffset)                throw new InvalidOperationException("Read past end of buffer.");        }        private readonly byte[] buffer;        private readonly int maxOffset;        private int offset;    }}ByteArrayReader已經實現了把報文包按照MySQL協議所使用到的基本類型切成片組裝成C#類型,接下來就可以根據MySQL各種報文類型,把MySQL協議報文包實現成C#報文類型。下一篇文章,我們將實現如何從Socket中獲取報文內容封裝成C# classPayload,以及實現報文OK_Packet。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 河间市| 鱼台县| 威远县| 苏尼特右旗| 嘉定区| 徐水县| 马山县| 巴马| 舞钢市| 侯马市| 临邑县| 永福县| 宝清县| 抚远县| 图木舒克市| 奉化市| 湘西| 桐柏县| 大厂| 民权县| 贵阳市| 东乌| 姚安县| 郎溪县| 乐昌市| 盐源县| 竹北市| 中西区| 紫阳县| 广河县| 财经| 松潘县| 文昌市| 阿拉善右旗| 张家港市| 寿光市| 翼城县| 女性| 托克逊县| 宁城县| 韩城市|