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
在以上通信流程中,瀏覽器端使用人數(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连茧。
故初始化流程如下
調用接口
為便于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