Golang通過TCP與遠端服務器交互

C/S構架中搀菩,客戶端與服務端一般通過TCP通信。建立連接后即驗證身份驗證破托,若賬戶密碼正確肪跋,TCP連接保持,然后client和server全雙工通信土砂。
在B/S構架下州既,若希望用戶通過瀏覽器也能實現(xiàn)客戶端相同的功能谜洽,我們可以開發(fā)一個中間層為webserver,用戶瀏覽器與webserver交互吴叶,webserver再通過tcp連接與真正的server交互阐虚。

首先需要明確client與server通信格式。包括如何登陸蚌卤,如何實現(xiàn)對資源的CURD实束。通過Wireshark可以抓取明文消息,對于加密消息逊彭,需要查閱源代碼了解加密方式咸灿。

消息體格式

業(yè)務消息采用明文,敏感登錄信息采用非對稱加密RSA結合對稱加密DES侮叮。
TCP連接登陸需要四個字段避矢,分別為
用戶名
密碼
desKey
desIV

針對以上字段內容,使用EncryptPKCS1v15(C#系統(tǒng)默認加密方式)加密签赃,再使用base64編碼谷异,構建xml包。在包頭使用binary.Write寫入msgLength和msgType锦聊,完成封裝發(fā)送給遠端服務器歹嘹。對于返回值,使用剛發(fā)送的desKey結合desIV進行DES解密孔庭,獲得登陸結果尺上。

功能流程

瀏覽器->web service->TCPServer


image.png

在以上通信流程中,瀏覽器端使用人數(shù)較多圆到,頻繁建立連接怎抛。web service和TCPServer保持一個長連接,多用戶共享此TCP長連接芽淡。

對于webservice與TCP server通信马绝,利用Routine結合Channel方式協(xié)同工作。實現(xiàn)TCP全雙工的關鍵Routine如下:
SendEventProcessor
監(jiān)聽waiting process queue挣菲,若有則構建pakage富稻,然后通過tcp發(fā)送到服務端,然后將此Event轉移的pending response隊列白胀,考慮到允許刪除無響應event椭赋,pending response隊列采用slice結構。

// work as runtine
//
func SendEventProcessor() { 

    var task *Event
    for {

        Event= <-EventWaiting

        sendMessage(Event.action, Event.data)
        if Event.action != "HeartBeat" {
            log.Printf("Event message was sent...%s", Event.action)
            EventPendingMutex.Lock()
            EventPending = append(EventPending,Event)
            EventPendingMutex.Unlock()
        }
    }

}

FrameDetector:由于TCP read buffer可能存在粘包或杠、拆包哪怔、廢棄包。此routine用于實時過濾tcp read buffer,提取出完整有效的數(shù)據(jù)包认境。

func FrameDetector() {
    buf := new(bytes.Buffer)      //滑動窗
    buf4bytes := make([]byte, 4)  //tmp var
    cRdr := bufio.NewReader(conn) //reader from connection
    frame := Frame{}
    for {
        b, err := cRdr.ReadByte()
        if err == io.EOF {
            log.Println("readFrame:connection closed,connecting...")
            RemoteConnect(targetServer)
        }
        buf.WriteByte(b)
        if buf.Len() == 8 {
            buf.Read(buf4bytes)
            frame.Length = int(binary.LittleEndian.Uint32(buf4bytes)) //little endian 低字節(jié)先發(fā)
            buf.Read(buf4bytes)
            frame.Type = int(binary.LittleEndian.Uint32(buf4bytes)) //little endian 低字節(jié)先發(fā)
            switch frame.Type {
            case 3://msgType, login response
                                ... /create frame from bytes
                FrameChan <- &frame
                buf.Reset()//clear bytes buffer after saving the frame
                break

            case 4, 5, 411: // response

                frame.Message = make([]byte, frame.Length-4)
                n, _ := cRdr.Read(frame.Message)
                if n < frame.Length-4 { //continue waiting and reading
                    for n != frame.Length-4 {
                        b, _ := cRdr.ReadByte()
                        frame.Message = append(frame.Message, b)
                        n++
                    }
                }
                FrameChan <- &frame
                buf.Reset()
                break

            default:
                buf.ReadByte()
            }
        }
    }

}

RecieveMessageProcessor:
讀服務端返回的有效Frame胚委,解析其中數(shù)據(jù)為,從pending response隊列尋找目標Event元暴,把返回結果寫進去篷扩,同時標記done,從pending中移除茉盏。

// should be work as runtine
func RecieveMessageProcessor() { 
    var frame *Frame

    for {
        frame = <-FrameChan 
        log.Printf("receiveMessage:length:%d,type:%d", frame.Length, frame.Type)
        switch frame.Type {
        case 3: // 提醒事件
                         //parse xml string and save as a map
            m := parseXML(frame.Message)
            for i, event := range EventPending { //查找到工作中的任務鉴未,標記為完成
                if event.action == "Login" {
                    event.response = m
                    event.done <- true //向監(jiān)聽runtine發(fā)送完成信號
                    EventPendingMutex.Lock()
                    EventPending = append(EventPending[0:i], EventPending[i+1:]...) // 將任務從工作表中清除
                    EventPendingMutex.Unlock()
                    break
                }
            }
            break

    }

}

完成tcp連接后,發(fā)起登錄事件鸠姨,阻塞等待登陸反饋铜秆。同時為避免TCP連接掉線問題,新開routine讶迁,每隔數(shù)秒發(fā)送HeartBeat连茧。

故初始化流程如下


image.png

調用接口

為便于HTTP調用,編寫CRUD接口函數(shù)巍糯,函數(shù)中構建Event事件啸驯、構建NewTimer定時事件,使用select channel方式判斷超時祟峦。若超時罚斗,則從queue中刪除此event,同時返回超時信息給http handler宅楞。

func Delete(id,name, formula string) map[string]string {

    data := map[string]string{"ID": id, "name": name, "content": formula}
    event := Event{action: "Delete", data: data, done: make(chan bool, 1)}
    EventWaiting <- &event
    timer := time.NewTimer(timeout)
    defer timer.Stop()
    select {
    case <-timer.C:
        dropTask(&event)
        event.response = map[string]string{"status": "error", "message": "time out"}
    case <-task.done:
                //do something
                break;
    }
    return task.response
}

注意點:
time.After()在觸發(fā)前针姿,即便父函數(shù)退出定時器對象也不會被garbage collector回收。僅觸發(fā)后或stop狀態(tài)的timer厌衙,會被gc回收距淫。
使用正則從xml字符串提取有用信息,需要先剔除字符串中特殊字符(ascii<32)
關于TCP/ip報文婶希,以及各控制字功能榕暇,握手流程、揮手流程喻杈,CLOSE_WAIT和TIME_WAIT參考此文https://www.cnblogs.com/myd620/p/6252135.html
關于RSA拐揭,有多種加密模式,C#默認的是EncryptPKCS1v15
關于DES padding模式奕塑,C#默認的是PKCS7Padding

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市家肯,隨后出現(xiàn)的幾起案子龄砰,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,997評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件换棚,死亡現(xiàn)場離奇詭異式镐,居然都是意外死亡,警方通過查閱死者的電腦和手機固蚤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,603評論 3 392
  • 文/潘曉璐 我一進店門娘汞,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人夕玩,你說我怎么就攤上這事你弦。” “怎么了燎孟?”我有些...
    開封第一講書人閱讀 163,359評論 0 353
  • 文/不壞的土叔 我叫張陵禽作,是天一觀的道長。 經(jīng)常有香客問我揩页,道長旷偿,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,309評論 1 292
  • 正文 為了忘掉前任爆侣,我火速辦了婚禮萍程,結果婚禮上,老公的妹妹穿的比我還像新娘兔仰。我一直安慰自己茫负,他們只是感情好,可當我...
    茶點故事閱讀 67,346評論 6 390
  • 文/花漫 我一把揭開白布斋陪。 她就那樣靜靜地躺著朽褪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪无虚。 梳的紋絲不亂的頭發(fā)上缔赠,一...
    開封第一講書人閱讀 51,258評論 1 300
  • 那天,我揣著相機與錄音友题,去河邊找鬼嗤堰。 笑死,一個胖子當著我的面吹牛度宦,可吹牛的內容都是我干的踢匣。 我是一名探鬼主播,決...
    沈念sama閱讀 40,122評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼戈抄,長吁一口氣:“原來是場噩夢啊……” “哼离唬!你這毒婦竟也來了?” 一聲冷哼從身側響起划鸽,我...
    開封第一講書人閱讀 38,970評論 0 275
  • 序言:老撾萬榮一對情侶失蹤输莺,失蹤者是張志新(化名)和其女友劉穎戚哎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嫂用,經(jīng)...
    沈念sama閱讀 45,403評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡型凳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,596評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了嘱函。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片甘畅。...
    茶點故事閱讀 39,769評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖往弓,靈堂內的尸體忽然破棺而出疏唾,到底是詐尸還是另有隱情,我是刑警寧澤亮航,帶...
    沈念sama閱讀 35,464評論 5 344
  • 正文 年R本政府宣布荸实,位于F島的核電站,受9級特大地震影響缴淋,放射性物質發(fā)生泄漏准给。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,075評論 3 327
  • 文/蒙蒙 一重抖、第九天 我趴在偏房一處隱蔽的房頂上張望露氮。 院中可真熱鬧,春花似錦钟沛、人聲如沸畔规。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,705評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽叁扫。三九已至,卻和暖如春畜埋,著一層夾襖步出監(jiān)牢的瞬間莫绣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,848評論 1 269
  • 我被黑心中介騙來泰國打工悠鞍, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留对室,地道東北人。 一個月前我還...
    沈念sama閱讀 47,831評論 2 370
  • 正文 我出身青樓咖祭,卻偏偏與公主長得像掩宜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子么翰,可洞房花燭夜當晚...
    茶點故事閱讀 44,678評論 2 354

推薦閱讀更多精彩內容