准备:
结构 | 说明 |
---|---|
[Result Set Header] | 列数量 |
[Field] | 列信息(多个) |
[EOF] | 列结束 |
[Row Data] | 行数据(多个) |
[EOF] | 数据结束 |
Result Set Header 结构 🔗
字节 | 说明 |
---|---|
1-9 | Field结构计数(Length Coded Binary)用于标识Field结构的数量,取值范围0x00-0xFA。LengthEncodeInteger表示结果中含有列数量。Number of fields显示该返回字段列数量。 |
1-9 | 额外信息(Length Coded Binary)可选字段,一般情况下不应该出现。只有像SHOW COLUMNS这种语句的执行结果才会用到额外信息(标识表格的列数量)。 |
Field 结构 🔗
该包前四个字节表示的包的字节长度以及包的序列号。后四字节为以下内容。
MySQL 4.1 及之后的版本:
相对包内容的位置 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0 | 不定长 | catalog | 目录,通常为def |
不定长 | 不定长 | schema | 操作的数据库 |
不定长 | 不定长 | table | 操作的虚拟表名 |
不定长 | 不定长 | org_table | 操作的物理表名 |
不定长 | 不定长 | name | 虚拟列字段名 |
不定长 | 不定长 | org_name | 物理列字段名 |
不定长 | 0x0c | length of fixed-length fields | 以下字段长度 |
不定长 | 2 | character | 列字符集 |
不定长 | 4 | length | 字段最大长度 |
不定长 | 1 | type | 字段类型 |
不定长 | 2 | flags | 标志 |
不定长 | 1 | decimals | ?? |
不定长 | 2 | 预留字节数 | 预留字节数 |
Go代码实现:
type ResultField struct {
DefLen uint16
Def string
PreFixLen uint16
Database string
TableLen uint16
TableName string //操作的虚拟表名
OrgTableLen uint16
OrgTableName string //操作的物理表名
FieldLen uint16
FieldName string //操作的虚拟字段名
OrgFieldLen uint16
OrgFieldName string //操作的物理字段名
}
func (s *SelectInfo) ResultSetField(data []byte, fieldsInfo *FieldsInfo) {
var idx uint16 = 1
defLen := make([]byte, 2)
copy(defLen, data[:idx])
s.ResultField.DefLen = binary.LittleEndian.Uint16(defLen)
idx = idx + s.ResultField.DefLen
s.ResultField.Def = string(data[1:idx])
//------------------------------------------
preFixLen := make([]byte, 2)
copy(preFixLen, data[idx:idx+1])
s.ResultField.PreFixLen = binary.LittleEndian.Uint16(preFixLen)
idx++
s.ResultField.Database = string(data[idx : idx+s.ResultField.PreFixLen])
//------------------------------------------
tableLen := make([]byte, 2)
idx = idx + s.ResultField.PreFixLen
copy(tableLen, data[idx:idx+1])
s.ResultField.TableLen = binary.LittleEndian.Uint16(tableLen)
idx++
s.ResultField.TableName = string(data[idx : idx+s.ResultField.TableLen])
//------------------------------------------
orgTableLen := make([]byte, 2)
idx = idx + s.ResultField.TableLen
copy(orgTableLen, data[idx:idx+1])
s.ResultField.OrgTableLen = binary.LittleEndian.Uint16(orgTableLen)
idx++
s.ResultField.OrgTableName = string(data[idx : idx+s.ResultField.OrgTableLen])
//------------------------------------------
fieldLen := make([]byte, 2)
idx = idx + s.ResultField.OrgTableLen
copy(fieldLen, data[idx:idx+1])
s.ResultField.FieldLen = binary.LittleEndian.Uint16(fieldLen)
idx++
s.ResultField.FieldName = string(data[idx : idx+s.ResultField.FieldLen])
//------------------------------------------
orgFieldLen := make([]byte, 2)
idx = idx + s.ResultField.FieldLen
copy(orgFieldLen, data[idx:idx+1])
s.ResultField.OrgFieldLen = binary.LittleEndian.Uint16(orgFieldLen)
idx++
s.ResultField.OrgFieldName = string(data[idx : idx+s.ResultField.OrgFieldLen])
fieldsInfo.FieldMap = append(fieldsInfo.FieldMap, s.ResultField.OrgFieldName)
//fmt.Println(s.ResultField.OrgFieldName)
}
EOF 结构 🔗
MySQL 4.1 及之后的版本:
相对包内容的位置 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0 | 1 | 包头标识 | 0xFE 代表这是一个EOF 包 |
1 | 2 | 警告数 | 上次命令引起的警告数 |
3 | 2 | 服务器状态 | 服务器状态 |
告警计数:服务器告警数量,在所有数据都发送给客户端后该值才有效。
状态标志位:包含类似SERVER_MORE_RESULTS_EXISTS这样的标志位。
注: 由于EOF值与其它Result Set结构共用1字节,所以在收到报文后需要对EOF包的真实性进行校验,校验条件为:
第1字节值为0xFE 包长度小于9字节 附:EOF结构的相关处理函数:
服务器:protocol.cc源文件中的send_eof函数
Go代码实现:
type Eof struct {
ResponseCode []byte
EofMarker uint16
ServerStatus uint16
Payload uint16
}
func NewEof() *Eof {
return &Eof{}
}
func (e *Eof) Eof(packet []byte) {
e.ResponseCode = packet[0:1]
eofMarker := make([]byte, 2)
copy(eofMarker, e.ResponseCode)
e.EofMarker = binary.LittleEndian.Uint16(eofMarker)
e.ServerStatus = binary.LittleEndian.Uint16(packet[1:3])
e.Payload = binary.LittleEndian.Uint16(packet[3:5])
//fmt.Println(e)
}
Row Data 结构 🔗
Row Data含着的是我们需要获取的数据,一个Result Set包里面包含着多个Row Data结构。每一个值前面有长度字节值,可以帮助我们区分开不同的列。
相对包内容的位置 | 长度(字节) | 名称 | 描述 |
---|---|---|---|
0 | 不定 | length | 这个字段的数据长度 |
不定 | 不定 | text | 这个字段的值 |
Go代码实现:
func NewRowPacket() *Row {
return &Row{RowList: nil}
}
type Row struct {
RowList []string
}
func (r *Row) RowPacket(mysql *server.Mysql) {
for {
packetData := mysql.Payload()
packetType := hex.EncodeToString(packetData[:1])
if packetType == "fe" { //需要做一个eof判断是否结束
return
}
rowIdx = 0 //初始化一下
r.row(packetData)
}
}
var rowIdx uint16
func (r *Row) row(packetData []byte) {
if int(rowIdx) >= len(packetData) {
return
}
lengthBytes := make([]byte, 2)
copy(lengthBytes, packetData[rowIdx:rowIdx+1])
length := binary.LittleEndian.Uint16(lengthBytes)
if int(rowIdx+1+length) > len(packetData) {
return
}
text := packetData[rowIdx+1 : rowIdx+1+length]
rowIdx = rowIdx + 1 + length
r.RowList = append(r.RowList, fmt.Sprintf("%s", text))
r.row(packetData)
}