MySQL协议.NET Core实现(一)
来源:互联网 发布:java调用数组sort函数 编辑:程序博客网 时间:2024/06/03 07:32
导语
个高水平的研发团对,无论使用什么框架、什么工具、什么语言,团队里应该有人有能力把控所使用框架、工具、语言的每一个核心功能的实现细节。团队里的每个成员应该根据自身所长挑选其中一块做深入研究,并把研究成果分享给团队,力争使整个所处团队实力得到提升,达到同行业内顶尖水平。为了实现这个目标,不允许在团队中出现黑盒子,对.NET生态而言,我们需要打开MSBuild, Rosyln, CoreCLR等黑盒子。我作为团队的一员,将花一些业余时间打开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个字节决定,如下表:
字符串(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个字节决定,如下表:
从上表可以看出,报文的最大长度是8个字节,8字节 * 每字节8bit = 64bit, 结合C#数据类型,能表示8个字节的基础数据类是是Int64、UInt64,因为报文长度不可能为负,所以使用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。- MySQL协议.NET Core实现(一)
- MySQL协议.NET Core实现(一)
- .net core使用MySQL笔记
- Net Core mvc 使用mysql
- Asp.Net Core 连接MySQL
- ASP.NET Core(一)【介绍】
- 基于.Net Core 2.0 + SqlSugar ORM + MySql快速实现网站开发
- 一起学ASP.NET Core 2.0学习笔记(一)- CentOS下 .net core2 sdk nginx、supervisor、mysql环境搭建
- Orchard Core一分钟搭建ASP.NET Core CMS
- .NET Core 使用Dapper 操作MySQL
- ASP.NET Core 折腾笔记一
- NET Core-学习笔记(一)
- NET Core-学习笔记(一)
- .NET Core 已经实现了PHP JIT,现在PHP是.NET上的一门开发语言
- ASP.NET Core Web API下事件驱动型架构的实现(一):一个简单的实现
- .NET Core 使用 grpc 实现微服务
- 重新实现.NET Core的 double.ToString()
- asp.net core WebAPI实现CRUD
- linux bash脚本监控启动停止weblogic服务
- 数据库索引的实现原理(面试问题:请说出数据库索引实现原理)
- S2SH整合SpringMvc报错FlushMode.MANUAL( 只读模式)
- java 读取excel 文件 Unable to recognize OLE stream 错误
- podspec文件的写法详解
- MySQL协议.NET Core实现(一)
- 视觉SLAM整理(2)数学篇
- queue 队列
- Star UML 九种图 分析
- [netty]--最通用TCP黏包解决方案:LengthFieldBasedFrameDecoder和LengthFieldPrepender
- EMW3162 AT固件的使用【1】
- map
- 765C Table Tennis Game 2
- 复杂的1秒--图解Google搜索技术