golang手把手实现tcp内网穿透代理(2)

iamdev · · 287 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

上一篇文章明确了我们需要做的东西,接下来就是实现具体功能

定义数据模型

首先是协议的定义,服务端和客户端需要一套协议来交互

协议的内容包括了,livecheck,以及发起tcp连接建立的请求,和响应的对象

假设内网里面需要暴露到外网的端口是5050,那么客户端需要请求服务端在外网建立一个端口,同时服务端需要知道内网被代理的端口是多少。

是因为当外网端口接收到tcp请求的时候,服务端也是需要下发请求到客户端要求客户端建立到被代理端口的连接的。

那么使用什么文本格式呢?首先考虑使用大家都知道的比较简单的,那就是json格式了。

既然使用的文本格式是json,那么需要了解golang下怎么使用json的序列化和反序列化一个对象。

下面开始结构体的定义:

新建一个model.go文件,用来放客户端和服务端交互的数据模型

建立一个base的结构体,里面存放一个变量是type,类型是string,用来表明这个结构体是什么类型

还有一个id,类型是string,表示消息的id

消息id是用来响应请求的,可以明确的知道是响应的哪一条请求消息。

LiveCheck直接继承Base,无须特殊的变量

Response继承Base,注意Response的id是和请求的id是一致的,这样客户端可以知道是对应哪条请求的影响消息。

response包含了error信息,result信息,statusCode信息

type LiveCheck struct {
    Base
}
type Response struct {
    Base
    Error string `json:"error"`
    Result string `json:"result"`
    StatusCode int `json:"statusCode"`
}
type Base struct {
    Type   string `json:"type"`
  Id string `json:"id"`
}

增加两个工厂方法,用来构造liveCheck和response对象,暂定使用时间戳来表示消息的id

response的id需要传递,无法默认构造,因为这个是和请求相匹配的

func NewLiveCheck() *LiveCheck {
    liveCheck := LiveCheck{}
    liveCheck.Type = "lib.LiveCheck"
    liveCheck.Id = string(time.Now().Unix())
    return &liveCheck
}

func NewResponse(error string, result string, statusCode int,id string) *Response {
    response := Response{Error: error, Result: result, StatusCode: statusCode}
    response.Type = "lib.Response"
    response.Id = id
    return &response
}

增加一个TcpRequest对象,其中LocalPort表示client端需要被代理出去的端口,ServerPort表示服务器上需要开放什么端口来代理client的端口。增加对应的工厂方法

type TcpRequest struct {
    ClientPort  int32
    ServerPort int32
    Base
}
func NewTcpRequest(clientPort int32, serverPort int32) *TcpRequest {
    tcpRequest := TcpRequest{ClientPort: clientPort, ServerPort: serverPort}
    tcpRequest.Type = "lib.TcpRequest"
    tcpRequest.Id = string(time.Now().Unix())
    return &tcpRequest
}

定义好数据模型之后,后面我们可以再回过头来补充需要的信息,暂停就是这些。

定义数据的发送和接收

接下来定义数据怎么传递和发送

tcp协议可以保证我们的数据是按照顺序发送的,那么我们只需要处理粘包的情况。

报文构造使用两端数据,第一段是报文的长度,占用4个字节,第二段是报文的内容

报文长度 报文内容
使用int32,占用4个字节 json数据,占用报文长度的字节数

这样的话,我们先读取前4个字节,获取到报文长度,然后按照顺序,把剩余长度的字节读取到报文长度为止,然后再反序列化成结构体对象即可。

新增io.go文件,存放io相关的代码,

增加ReadInt32方法,从io的reader流中读取int32

func ReadInt32(reader io.Reader) (int32, error) {
    IntBytes := make([]byte, 4)  //构造4个字节的数组
    n, err := reader.Read(IntBytes) //读取到数组中
    if err != nil {
        return 0, err
    }
    if n != 4 {  
        return 0, errors.New(fmt.Sprintf("cannot read enough data to convert int32 , data bytes is %d ", n))
    }
    buf := bytes.NewBuffer(IntBytes)
    var IntValue int32
    err = binary.Read(buf, binary.LittleEndian, &IntValue) //使用小端,从byte转换int
    if err != nil {
        return 0, err
    }
    return IntValue, nil
}

增加WriteInt32方法,写入int32到流中

func WriteInt32(intValue int32, writer io.Writer) error {
    cache := make([]byte, 0)
    buf := bytes.NewBuffer(cache)
    _ = binary.Write(buf, binary.LittleEndian, intValue)
    _, err := writer.Write(buf.Bytes())
    if err != nil {
        return err
    }
    return nil
}

增加写入结构体的方法,在写入结构体之前,需要先json序列化,然后获取json序列化之后的内容的字节数

把这个字节数先写入到输出流中,然后再写入结构体内容。

func (base Base) WriteBaseModel(writer io.Writer) error {
    data, err := json.Marshal(&base)
    if err != nil {
        return err
    }
    dataSize := len(data)
    err = WriteInt32(int32(dataSize), writer)
    if err != nil {
        return err
    }
    _, err = writer.Write(data)
    if err != nil {
        return err
    }
    return nil
}

同样的读取也是这个逻辑,但是读取的话,需要先反序列化json到Base结构体,然后再读取type类型

然后再反序列化到正确的结构体对象

func ReadBaseModel(reader io.Reader) (base *Base, err error) {
    intValue, err := ReadInt32(reader)
    bytesData := make([]byte, intValue)
    n, err := reader.Read(bytesData)
    if err != nil {
        return (*Base)(nil), err
    }
    if int32(n) != intValue {
        return (*Base)(nil), errors.New(fmt.Sprintf("cannot read enough data to convert json , data bytes is %d ,not %d ", n, intValue))
    }
    var _base Base
    err = json.Unmarshal(bytesData, &_base)
    if err != nil {
        fmt.Println("Unmarshal failed, ", err)
        return (*Base)(nil), err
    }
    var common interface{}
    common = ModelMap[_base.Type]()
    err = json.Unmarshal(bytesData, &common)
    return common.(*Base), err
}

增加结构体type映射反射字典,字典可以根据结构体的名字来动态创建结构体对象

字典的key是一个字符串对象,value是func() interface{} 对象,也就是value是一个方法,通过调用方法

可以直接返回结构体变量!

func registerType(elem interface{}, modelMap map[string]func() interface{}) {
    typeName := reflect.TypeOf(elem).Elem()
    modelMap[typeName.Name()] = newStruct(typeName)
}

func newStruct(elem reflect.Type) func() interface{} {
    return func() interface{} {
        return reflect.New(elem).Elem().Interface()
    }
}

var ModelMap = func() map[string]func() interface{} {
    modelMap := make(map[string]func() interface{})
    registerType((*LiveCheck)(nil), modelMap)
    registerType((*Response)(nil), modelMap)
    registerType((*TcpRequest)(nil), modelMap)
    return modelMap
}()

未完待续。。。

本文来自:简书

感谢作者:iamdev

查看原文:golang手把手实现tcp内网穿透代理(2)

入群交流(和以上内容无关):Go中文网 QQ 交流群:798786647 或加微信入微信群:274768166 备注:入群;关注公众号:Go语言中文网

287 次点击  
加入收藏 微博
上一篇:ARTS 第11周
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传