内容简介:C/S构架中,客户端与服务端一般通过TCP通信。建立连接后即验证身份验证,若账户密码正确,TCP连接保持,然后client和server全双工通信。在B/S构架下,若希望用户通过浏览器也能实现客户端相同的功能,我们可以开发一个中间层为webserver,用户浏览器与webserver交互,webserver再通过tcp连接与真正的server交互。首先需要明确client与server通信格式。包括如何登陆,如何实现对资源的CURD。通过Wireshark可以抓取明文消息,对于加密消息,需要查阅源代码了解加
C/S构架中,客户端与服务端一般通过TCP通信。建立连接后即验证身份验证,若账户密码正确,TCP连接保持,然后client和server全双工通信。
在B/S构架下,若希望用户通过浏览器也能实现客户端相同的功能,我们可以开发一个中间层为webserver,用户浏览器与webserver交互,webserver再通过tcp连接与真正的server交互。
首先需要明确client与server通信格式。包括如何登陆,如何实现对资源的CURD。通过Wireshark可以抓取明文消息,对于加密消息,需要查阅源代码了解加密方式。
消息体格式
业务消息采用明文,敏感登录信息采用非对称加密RSA结合对称加密DES。
TCP连接登陆需要四个字段,分别为
用户名
密码
desKey
desIV
针对以上字段内容,使用EncryptPKCS1v15(C#系统默认加密方式)加密,再使用base64编码,构建xml包。在包头使用binary.Write写入msgLength和msgType,完成封装发送给远端服务器。对于返回值,使用刚发送的desKey结合desIV进行DES解密,获得登陆结果。
功能流程
浏览器->web service->TCPServer
image.png
在以上通信流程中,浏览器端使用人数较多,频繁建立连接。web service和TCPServer保持一个长连接,多用户共享此TCP长连接。
对于webservice与TCP server通信,利用Routine结合Channel方式协同工作。实现TCP全双工的关键Routine如下:
SendEventProcessor
监听waiting process queue,若有则构建pakage,然后通过tcp发送到服务端,然后将此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,提取出完整有效的数据包。
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 低字节先发 buf.Read(buf4bytes) frame.Type = int(binary.LittleEndian.Uint32(buf4bytes)) //little endian 低字节先发 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,解析其中数据为,从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 //向监听runtine发送完成信号 EventPendingMutex.Lock() EventPending = append(EventPending[0:i], EventPending[i+1:]...) // 将任务从工作表中清除 EventPendingMutex.Unlock() break } } break } }
完成tcp连接后,发起登录事件,阻塞等待登陆反馈。同时为避免TCP连接掉线问题,新开routine,每隔数秒发送HeartBeat。
故初始化流程如下
image.png
调用接口
为便于HTTP调用,编写CRUD接口函数,函数中构建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()在触发前,即便父函数返回也不会被garbage collector回收。仅触发后或stop状态的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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- (译)通过WebChannel/WebSockets与QML中的HTML交互
- iOS 12 人机交互指南:交互(User Interaction)
- 生活NLP云服务“玩秘”站稳人机交互2.0语音交互场景
- asyncio之子进程交互
- 以太坊交互工具
- 学习 PixiJS — 交互工具
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTML 编码/解码
HTML 编码/解码
Base64 编码/解码
Base64 编码/解码