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;现在不一样了。内核的安装可以方…

MySQL coalesce()函数

转载自 MySQL coalesce()函数 MySQL COALESCE函数介绍 下面说明了COALESCE函数语法&#xff1a; COALESCE(value1,value2,...);COALESCE函数需要许多参数&#xff0c;并返回第一个非NULL参数。如果所有参数都为NULL&#xff0c;则COALESCE函数返回NULL。 以下显示了使用CO…

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)打开终端…

MySQL greatest()和least()函数

转载自 MySQL greatest()和least()函数 MySQL GREATEST和LEAST函数介绍 GREATEST和LEAST函数都使用N个参数&#xff0c;并分别返回最大和最小值。下面说明GREATEST和LEAST函数的语法&#xff1a; GREATEST(value1, value2, ...); LEAST(value1,value2,...);参数可能具有混合…

环东风尚注注意事项

1.先对钱&#xff08;现金&#xff09;。 2.看美团和飞猪上的订单。 3.根据订单在订单表上做预订。 4.对本子上入住的人是否和电脑上的一致。 5.对房卡和电脑上的是否一致。 6.查看退宿的身份证是否已退。 退房&#xff1a; 1.先退钱 2.退身份证 3.退卡 开房&#xff1a; 1.收…

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;瞄…

cs1.5 linux服务端,Linux下架设CS1.5服务器

所需文件&#xff1a;1、hlds_l_3110_full.bin2、cs_15_full.tar.gz3、engine_i386.so(据说这个东东可以免cdkey&#xff0c;可我不用也可免cdkey)把所有的文件都拷贝到/usr/cs目录下&#xff1a;# chmod x hlds_l_3110_full.bin# tar xzvf hlds_l_3110_full.tar.gz# tar xzvf …

MySQL isnull()函数基本指南

转载自 MySQL isnull()函数基本指南 MySQL ISNULL函数简介 ISNULL函数接受一个参数&#xff0c;并测试该参数是否为NULL。如果参数为NULL&#xff0c;则ISNULL函数返回1&#xff0c;否则返回0。 下面说明了ISNULL函数的语法&#xff1a; ISNULL(expr)请考虑以下示例&#…

ppt2010基础操作笔记

一、PPT2010的界面&#xff1a; 1.功能选项卡 2.大纲区 3.标题栏 4.备注页 5.状态栏 6.工作区 二、创建PPT演示文稿 1.开始–》PPT2010 2.在PPT里面新建–》文件–》新建–》样本模板–》选择模板。 三、PPT2010中的视图&#xff1a; 1.普通视图 2.大纲视图 3.幻灯…

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; …

java中input表单中的type=date返回类型【切记】

input表单中的type”date”返回类型为Date类型&#xff01; input表单中的type”date”返回类型为Date类型&#xff01; input表单中的type”date”返回类型为Date类型&#xff01; 重要的内容说三遍&#xff0c;今晚可算是及坑死我了&#xff0c;一直以为是String类型 &…

全自动安装 linux光盘,CentOS 7.1全自动安装光盘制作详解

安装系统的话&#xff0c;常用方式就是通过U盘&#xff0c;光盘&#xff0c;kickstart网络自动化安装&#xff0c;网络自动化安装固然好&#xff0c;但是有时候在现场做项目的话根本就没有kickstart环境&#xff0c;甚至连基本的网络建设都没有弄好&#xff0c;此时该如何安装系…

MySQL last_insert_id()函数

转载自 MySQL last_insert_id()函数 MySQL LAST_INSERT_ID函数简介 在数据库设计中&#xff0c;我们经常使用代理键使用AUTO_INCREMENT属性为主键列生成唯一的整数值。 当您将新行插入到具有AUTO_INCREMENT列的表中时&#xff0c;MySQL会自动生成一个唯一的整数&#xff0c…

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; 直接点击下一步&…