上一篇文章明確了我們需要做的東西,接下來就是一步步實現具體功能

定義數據模型

首先是協議的定義,服務端和客戶端需要一套協議來交互

協議的內容包括了,ping和pong,以及發起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
}()

未完待續。。。


推薦閱讀:
相關文章