frp中的json模块
预备知识
Go中的接口的数据结构可以分为两部分:
- 其中一部分指向或者存储了原始数据的值
- 另一部分指向或者存储了原始数据的类型描述符(其中包含类型,以及对应于接口中的方法)
所以大体上我们可以粗略的认为接口内部存储了原始数据的值和类型。
更详细的可以看一下Go数据结构-接口
正文
json模块一共三个文件,分别是 msg.go pack.go process.go,总共300行左右的代码量,虽然不多,但确实有许多较为深的点的。
三个文件一起看,总共就一个接口一个结构体。
接口是Message,一个空接口没啥好看的。
再来看结构体MsgCtl以及其生成函数:
type MsgCtl struct {
typeMap map[byte]reflect.Type
typeByteMap map[reflect.Type]byte
maxMsgLength int64
}
func NewMsgCtl() *MsgCtl {
return &MsgCtl{
typeMap: make(map[byte]reflect.Type),
typeByteMap: make(map[reflect.Type]byte),
maxMsgLength: defaultMaxMsgLength,
}
}
感觉似乎也很简单,两个map一个整型。MsgCtl有很多方法,比较简单的像:
// 注册,注意这里看出typeMap和typeByteMap是相互对应的。而且容量只有256个
func (msgCtl *MsgCtl) RegisterMsg(typeByte byte, msg interface{}) {
msgCtl.typeMap[typeByte] = reflect.TypeOf(msg)
msgCtl.typeByteMap[reflect.TypeOf(msg)] = typeByte
}
func (msgCtl *MsgCtl) SetMaxMsgLength(length int64) {
msgCtl.maxMsgLength = length
}
分别是向map中填充数据以及设置唯一的整型字段的值。
剩余的几个方法最重要的就是Pack和readMsg和unpack这三个方法,其余的都是添头了。
先来看一下Pack方法:
func (msgCtl *MsgCtl) Pack(msg Message) ([]byte, error) {
// 1
typeByte, ok := msgCtl.typeByteMap[reflect.TypeOf(msg).Elem()]
if !ok {
return nil, ErrMsgType
}
// 2
content, err := json.Marshal(msg)
if err != nil {
return nil, err
}
// 3
buffer := bytes.NewBuffer(nil)
buffer.WriteByte(typeByte)
binary.Write(buffer, binary.BigEndian, int64(len(content)))
buffer.Write(content)
return buffer.Bytes(), nil
}
- 获取msg的结构体类型以及其对应'标示字节(就是typeByteMap键值对中的值)',首先一般来说:msg参数中的类型一般是一个结构体实例的指针类型,所以
reflect.TypeOf(msg).Elem()返回的是这个结构体类型 - 解析为json
- 先将标示字节写入,然后将json的长度按大端写入,最后将json写入
来看一下readMsg方法:
func (msgCtl *MsgCtl) readMsg(c io.Reader) (typeByte byte, buffer []byte, err error) {
// 1
buffer = make([]byte, 1)
_, err = c.Read(buffer)
if err != nil {
return
}
typeByte = buffer[0]
if _, ok := msgCtl.typeMap[typeByte]; !ok {
err = ErrMsgType
return
}
// 2
var length int64
err = binary.Read(c, binary.BigEndian, &length)
if err != nil {
return
}
if length > msgCtl.maxMsgLength {
err = ErrMaxMsgLength
return
} else if length < 0 {
err = ErrMsgLength
return
}
// 3
buffer = make([]byte, length)
n, err := io.ReadFull(c, buffer)
if err != nil {
return
}
if int64(n) != length {
err = ErrMsgFormat
}
return
}
看完Pack方法后,再看这个就不难理解了。这个方法基本上就是三步走:
- 消息的第一个字节表示消息类型,读取后检测这个消息类型是否是合法的(是否被注册过)
- 消息的第二个和第三个字节表示数据长度,读出来后检测该长度是否有效
- 知道了长度后,就把对应长度的数据读出来放到buffer中
所以Pack后的数据一般需要readMsg来读取。
接下来再看unpack方法:
func (msgCtl *MsgCtl) unpack(typeByte byte, buffer []byte, msgIn Message) (msg Message, err error) {
if msgIn == nil {
t, ok := msgCtl.typeMap[typeByte]
if !ok {
err = ErrMsgType
return
}
msg = reflect.New(t).Interface().(Message)
} else {
msg = msgIn
}
err = json.Unmarshal(buffer, &msg)
return
}
unpack一般是将readMsg读取的数据加以处理得到其对应的结构。这个方法有些东西,一开始看的我一脸懵逼,主要是对Go中的反射reflect不熟,后来看了看这个Go 语言反射三定律,我才了解了这些东西。首先msgIn肯定是一个Message接口类型的对象,假如其是nil的话,那我们根据typeByte找出对应的类型,然后就是复杂的这一句了:
msg = reflect.New(t).Interface().(Message),t是一个reflect.Type类型的接口实例,reflect.New(t)则会返回一个reflect.Value类型的结构体实例,但注意:这个Value的类型是t的原始类型的指针类型,值则是该类型的零值,reflect.New(t).Interface()会将reflect.Value这个实例中真正对应的值以及其指针类型转换为空接口然后返回,紧接着后面又跟了.(Message)将空接口转换为Message空接口。绕了这么一大圈,我们知道:现在msg接口中两部分中值是t原始类型的零值,类型是t原始类型的指针类型。
最后,将buffer中的数据解析出来赋给msg,并返回。
其余的方法基本上都是调用了这三个方法中的某个或者某几个
func (msgCtl *MsgCtl) UnPack(typeByte byte, buffer []byte) (msg Message, err error) {
return msgCtl.unpack(typeByte, buffer, nil)
}
func (msgCtl *MsgCtl) ReadMsg(c io.Reader) (msg Message, err error) {
typeByte, buffer, err := msgCtl.readMsg(c)
if err != nil {
return
}
return msgCtl.UnPack(typeByte, buffer)
}
func (msgCtl *MsgCtl) WriteMsg(c io.Writer, msg interface{}) (err error) {
buffer, err := msgCtl.Pack(msg)
if err != nil {
return
}
if _, err = c.Write(buffer); err != nil {
return
}
return nil
}
在外层我们基本上只用ReadMsg和WriteMsg来读取数据就可以了。
用法
package main
import (
"fmt"
jsonMsg "github.com/fatedier/golib/msg/json"
)
const (
TypeMsgOne = '1'
TypeMsgTwo = '2'
)
var msgTypeMap = map[byte]interface{}{
TypeMsgOne: MsgOne{},
TypeMsgTwo: MsgTwo{},
}
var msgCtl *jsonMsg.MsgCtl
type MsgOne struct {}
type MsgTwo struct {}
type EchoWriter struct {}
func (EchoWriter)Write(p []byte) (n int, err error) {
fmt.Println(p)
fmt.Println(string(p))
return len(p), nil
}
func init() {
msgCtl = jsonMsg.NewMsgCtl()
for typeByte, msg := range msgTypeMap {
msgCtl.RegisterMsg(typeByte, msg)
}
}
func main() {
msgCtl.WriteMsg(EchoWriter{}, &MsgOne{})
}
运行后结果是
[49 0 0 0 0 0 0 0 2 123 125]
1{}
首先是字节49:表示字符串1;然后是占了8个字节的0 0 0 0 0 0 0 2:表示长度2;最后是字节123和125:对应花括号{}。
总结
- 整体来看json模块就是对结构体编解码处理,本质上和go官方的json模块无区别。详细的说该json模块提供了对特定的(被注册的)结构体(一般是结构体,当然其他的也可以。)的存储或者传输(可以理解为读取写入buffer)的处理。
- 接上一点:其写处理方式则是将该结构体类型对应的byte、该结构体json序列化后的长度、以及该结构体编码后的字节序列按照顺序写入
- 接上一点:其读处理方式则是将读取到的字节序列,按照写入的顺序读取并解析出来,返回给上层调用的代码。
frp中的json模块的更多相关文章
- Python 3 中的json模块使用
1. 概述 JSON (JavaScript Object Notation)是一种使用广泛的轻量数据格式. Python标准库中的json模块提供了JSON数据的处理功能. Python中一种非常常 ...
- frp源码剖析-frp中的log模块
前言&引入 一个好的log模块可以帮助我们排错,分析,统计 一般来说log中需要有时间.栈信息(比如说文件名行号等),这些东西一般某些底层log模块已经帮我们做好了.但在业务中还有很多我们需要 ...
- openresty开发系列25--openresty中使用json模块
openresty开发系列25--openresty中使用json模块 web开发过程中,经常用的数据结构为json,openresty中封装了json模块,我们看如何使用 一)如何引入cjson模块 ...
- Python中的json模块
在Python内置函数中,有一个eval()函数可以将字符串内容转换成Python对象,比如我现在将一个字典 dic = {"name":"pengfy"}写到 ...
- python中序列化json模块和pickle模块
内置模块和第三方模块 json模块和pickle 模块(序列化模块) 什么是序列化? 序列化就是将内粗这种的数据类型转成另一种格式 序列化:字典类型——>序列化——>其他格式——>存 ...
- Python中的Json模块dumps、loads、dump、load函数介绍
Json模块dumps.loads.dump.load函数介绍 1.json.dumps() json.dumps() 用于将dict类型的数据转成str,因为如果直接将dict类型的数据写入json ...
- python中的 json 模块使用
(1)python 中生成 json 字符串: import json data = dict(ret=0, msg="Welcome, Login success!") json ...
- frp源码剖析-frp中的mux模块
前言 frp几乎所有的连接处理都是构建在mux模块之上的,重要性不必多说,来看一下这是个啥吧 ps: 安装方法 go get "github.com/fatedier/golib/net/m ...
- [ Python入门教程 ] Python中JSON模块基本使用方法
JSON (JavaScript Object Notation)是一种使用广泛的轻量数据格式,Python标准库中的json模块提供了一种简单的方法来编码和解码JSON格式的数据.用于完成字符串和p ...
随机推荐
- ml-模型评估与选择
1.基本概念 错误率E=分类错误的样本数a/总样本数m:精度=1-a/m 经验误差/训练误差:在训练集上产生的 泛化误差:在测试集上产生的=====>要把这个泛化误差降到最小化. 2.评估方法 ...
- ExtJS Tab里放Grid高度自适应问题,官方Perfect方案。
出处:http://docs.sencha.com/extjs/4.2.1/extjs-build/examples/layout-browser/layouts/combination.js // ...
- 数组操作方法(包括es5)
//push(); 定义:可以可向数组的末尾添加一个或更多元素,并返回新的长度. 方法:push(); 语法:数组.push(新元素1,新元素2,....,新元素x) 返回值:把指定的值添加到数组后的 ...
- 好文章之——PHP系列(一)
注:最近实习的公司是一家做电商企业,后台主要是php开发,好久不怎么接触php的我看了几篇相关文章,提高下对它的认识与理解,发现里面的学习思路还是非常好的,当然也会重新拾一下基础知识啦! 其实自己心中 ...
- C/C++的内存泄漏检测工具Valgrind memcheck的使用经历
Linux下的Valgrind真是利器啊(不知道Valgrind的请自觉查看参考文献(1)(2)),帮我找出了不少C++中的内存管理错误,前一阵子还在纠结为什么VS 2013下运行良好的程序到了Lin ...
- spring学习总结(一)_Ioc基础(中)
本篇文章继续上篇文章讲解Ioc基础,这篇文章主要介绍使用spring注解配置Ioc 上篇文章主要是通过xml配置文件进行Ioc的配置.这次进行改造下,通过注解进行配置 首先先看一个简单的demo 简单 ...
- 线性代数的本质与几何意义 03. 矩阵与线性变换 (3blue1brown 咪博士 图文注解版)
首先,恭喜你读到了咪博士的这篇文章.本文可以说是该系列最重要.最核心的文章.你对线性代数的一切困惑,根源就在于没有真正理解矩阵到底是什么.读完咪博士的这篇文章,你一定会有一种醍醐灌顶.豁然开朗的感觉! ...
- python之工作举例:通过复制NC文件来造数据
# 通过对NC文件复制来造数据 import os, shutil # 遍历的根目录 root_dir = "D:\\test_data\\DISASTER\\" # 获取NC文件 ...
- python---面对对象的组合
组合 给一个类的对象封装一个属性,这个属性是另一个类的对象,这样我们在调用这个属性时就指向了另一个类的对象,这样我们就可以调用另一个类的方法. 模拟英雄联盟写一个游戏人物的类# 要求:# (1)创建一 ...
- Luogu3676 小清新数据结构题(树链剖分+线段树)
先不考虑换根.考虑修改某个点权值对答案的影响.显然这只会改变其祖先的子树权值和,设某祖先原子树权值和为s,修改后权值增加了x,则对答案的影响为(s+x)2-s2=2sx+x2.可以发现只要维护每个点到 ...