MySQL协议.NET Core实现(一)

一个有技术追求的研发团对,无论使用什么框架、什么工具、什么语言,团队里应该有人有能力把控所使用框架、工具、语言的每一个核心功能的实现细节。团队里的每个成员应该根据自身所长挑选其中一块做深入研究,并把研究成果分享给团队,力争使整个所处团队实力得到提升,达到同行业内顶尖水平。为了实现这个目标,不允许在团队中出现黑盒子,对.NET生态而言,我们需要打开MSBuildRosylnCoreCLR等黑盒子。作为团队的一员,我将花一些业余时间打开.NET MySQL驱动黑盒子,使用.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.,它是网络通信过程中端点的抽象表示,包含进行网络通信必须的五种信息:

  1. 连接使用的协议;

  2. 本地主机的IP地址;

  3. 本地进程的协议端口;

  4. 远地主机的IP地址;

  5. 远地进程的协议端口;

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协议分析

MySQL客户端与服务器可以通过四种通信协议进行交互:

  • TCP/IP协议(上面的知识储备就是为此准备的,所以,本系列文章假设TCP/IP协议)

  • Unix Socket协议

  • Share Memory协议

  • Named Pipes协议

交互过程

MySQL客户端与服务器的交互主要分为两个阶段

  • 握手认证阶段;

  • 命令执行阶段;

握手认证阶段

握手认证阶段为客户端与服务器通过TCP三次握手建立连接后进行,交互过程如下

  • 服务器 -> 客户端:握手初始化消息

  • 客户端 -> 服务器:登陆认证消息

  • 服务器 -> 客户端:认证结果消息

命令执行阶段

客户端认证成功后,会进入命令执行阶段,交互过程如下

  • 客户端 -> 服务器:执行命令消息

  • 服务器 -> 客户端:命令执行结果

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字节长度,使用小字节序传输(注意上面代码位移操作符)。3字节长度的整数值,需要使用C#的Int32、UInt32来存储,由上面public uint ReadFixedLengthUInt32(int readByteCount)实现,其它实现代码如下:

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;}// 读取字符串(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;}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;}}// 读取字符串(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;}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#类型,以及实现报文OK_Packet。

原文地址:http://www.xyting.org/2017/02/18/.NET-Core-MySql-Protocol-1.html


.NET社区新闻,深度好文,微信中搜索dotNET跨平台或扫描二维码关注

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/326307.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2018蓝桥杯省赛---java---C---7(缩位求和)

题目描述 问题描述在电子计算机普及以前&#xff0c;人们经常用一个粗略的方法来验算四则运算是否正确。 比如&#xff1a;248 * 15 3720 把乘数和被乘数分别逐位求和&#xff0c;如果是多位数再逐位求和&#xff0c;直到是1位数&#xff0c;得 2 4 8 14 > 1 4 5; 1 …

龙芯linux内核,龙芯的linux kernel,内核开发与编译

在很久很久以前&#xff0c;linux被视为geek极客的玩具。自行升级Linux内核&#xff0c;对普通用户来说&#xff0c;简直是天方夜谭。曾经的曾经&#xff0c;升级内核需要很多纷繁复杂的步骤&#xff0c;也需要花费很多的时间。但是&#xff0c;现在不一样了。内核的安装可以方…

2018蓝桥杯省赛---java---C---8(等腰三角形)

题目描述 问题描述本题目要求你在控制台输出一个由数字组成的等腰三角形。 具体的步骤是&#xff1a;先用1,2,3&#xff0c;…的自然数拼一个足够长的串 用这个串填充三角形的三条边。从上方顶点开始&#xff0c;逆时针填充。 比如&#xff0c;当三角形高度是8时&#xff1a;1…

Git,Git Flow,GitLab使用指南

高效利用一次蹲坑时间&#xff0c;看看如何使用Git Flow进行高效开发&#xff0c;什么才是Git提交的正确姿势&#xff0c;怎样使用GitLab进行Code Review&#xff1a; 使用Git Flow高效开发&#xff1b;Git提交正确姿势&#xff0c;Commit message编写指南&#xff1b;使用Git…

arm linux gcc 编译,Linux arm-linux-gcc交叉编译环境配置

Linux下的arm-linux-gcc交叉编译环境安装安装arm-linux-gcc(1) 打开终端&#xff0c;使用sudo命令进入从超级管理员&#xff1a;sudo su输入超级管理员密码。(2) 使用cd命令进入桌面&#xff1a;cd Desktop(3)复制arm-linux-gcc-4.4.3.tar.gz安装包到Ubuntu桌面下面(4)打开终端…

2019蓝桥杯省赛---java---C---1(求和)

题目描述 代码实现 package TEST;public class Main {public static void main(String[] args) {int cnt 0;for (int i 1; i < 2019; i)if (check(i)) cnt i;System.out.print(cnt);}static boolean check(int n) {String an"";if (a.contains("2")…

使用EntityFrameworkCore实现Repository, UnitOfWork,支持MySQL分库分表

昨天&#xff08;星期五&#xff09;下班&#xff0c;19&#xff1a;00左右回到家&#xff0c;洗个澡&#xff0c;然后20&#xff1a;30左右开始写代码&#xff0c;写完代码之后&#xff0c;上床看了《生活大爆炸10季》17、18两集&#xff0c;发现没有更新到19集&#xff0c;瞄…

2019蓝桥杯省赛---java---C---2(矩阵切割)

题目描述 代码实现 package TEST;import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);int max sc.nextInt();int min sc.nextInt();int sum 0, temp;while (max ! 0 && min ! 0) {if (max …

期待微软平台即服务技术Service Fabric 开源

微软的Azure Service Fabric的官方博客在3.24日发布了一篇博客 Service Fabric .NET SDK goes open source &#xff0c;介绍了社区呼声最高的Service Fabric开源的情况以及当前的情况&#xff0c;这次开源了Service Fabric的.NET SDK部分&#xff0c;主要是两个&#xff1a; …

2019蓝桥杯省赛---java---C---4(质数)

题目描述 代码实现 package TEST; public class Main {public static void main(String[] args) {int cnt 0;for (int i 2; ; i )if(check(i)){cnt ;if(cnt 2019){System.out.println(i);break;}}}public static boolean check(int n){//判断一个数是否为质数for (int i 2…

eclipse下载与安装步骤详解,包含解决错误(最全最详细)

以前一直用的是myeclipse,今天有幸接触eclipse,那我们就先来安装的配置一下&#xff0c;下载地址&#xff1a;点击下载密码&#xff1a;h0kg&#xff0c;下载完成以后就可以安装了&#xff0c;首先我们来先安装jdk1.7, 打开jdk的安装包 双击即可&#xff0c; 直接点击下一步&…

CoreCLR文档翻译 - GC的设计

此文档来源于CoreCLR的BOTR(The Book of the Runtime), 点击打开原文一切著作权归微软公司所有 GC的设计 作者: Maoni Stephens (maoni0) - 2015 提示: 推荐看 The Garbage Collection Handbook 这本书学习更多关于GC的知识 (在文章底部的链接中) 组件结构 在GC中有两个主…

CoreCLR源码探索(四) GC内存收集器的内部实现 分析篇

在这篇中我将讲述GC Collector内部的实现, 这是CoreCLR中除了JIT以外最复杂部分&#xff0c;下面一些概念目前尚未有公开的文档和书籍讲到。 为了分析这部分我花了一个多月的时间&#xff0c;期间也多次向CoreCLR的开发组提问过&#xff0c;我有信心以下内容都是比较准确的&am…

vue开源项目

转载自 vue开源项目 一、前台UI组件库 1.Element 优点&#xff1a;中文文档&#xff0c;ui种类比较全&#xff0c;ui设计简洁清晰 缺点&#xff1a;不够有特点 2.iView 优点&#xff1a;和element的UI很相似&#xff0c;有一些多的补充&#xff0c;可以相互替换 缺点&am…

linux跑循环脚本占内存,Linux下实现脚本监测特定进程占用内存情况

Linux系统下&#xff0c;我们可以利用以下命令来获取特定进程的运行情况&#xff1a;cat /proc/$PID/status其中PID是具体的进程号&#xff0c;这个命令打印出/proc/特定进程/status文件的内容&#xff0c;信息比较多&#xff0c;包含了物理内存/虚拟内存的使用状况&#xff0c…

如何在vm虚拟机里面安装win10操作系统

首先打开虚拟机&#xff0c;点击创建虚拟机 然后选择典型即可&#xff01; 选择稍后安装操作系统 然后选择win10 64位 . 然后在找个路径&#xff1a; 默认60GB即可&#xff0c;也可以更改大小&#xff1a; 最后点击完成&#xff1a; 接下来我们需要用U盘制作一个启动盘…

CoreCLR源码探索(五) GC内存收集器的内部实现 调试篇

在上一篇中我分析了CoreCLR中GC的内部处理&#xff0c; 在这一篇我将使用LLDB实际跟踪CoreCLR中GC&#xff0c;关于如何使用LLDB调试CoreCLR的介绍可以看: 微软官方的文档&#xff0c;地址我在第3篇中的介绍&#xff0c;地址LLDB官方的入门文档&#xff0c;地址 源代码 本篇…

小米路由器青春版装linux,比较费心的折腾 篇二:小米路由器青春版折腾负载均衡...

比较费心的折腾 篇二&#xff1a;小米路由器青春版折腾负载均衡2020-06-04 11:00:233点赞10收藏8评论创作立场声明&#xff1a;本文的核心目的是对于比较久远的硬件进行折腾&#xff0c;提及的软件可以在官网、软件包或者github找到。开篇预警本文截图较多&#xff0c;但是前期…

2019蓝桥杯省赛---java---C---6(旋转)

题目描述 问题描述图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时 针旋转 90 度。 我们用一个 nm 的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a;1 3 5 7 9 8 7 6 3 5 9 7这个图片顺时针旋转 9…