看这篇文章的时候不要怕代码,重要的核心都注释掉了,原理很简单!!祝您阅读顺利。在学习一门新的语言时,我总是喜欢先搭建一个demo工程,通过搭建一个基础工程来了解语言的特性。对于web交互,以往常用的技术主要是Ajax和Form表单提交。如果要建立长连接,可以使用Websocket。Websocket和socket其实是两个东西。如果要比较,应该用websocket和http。比较。Websocket发送json。Websocket发送json。这是一种常规方法。值得一提的是,在Vue框架中使用axios发送POST请求时,默认的Content-Type为application/json,所以后端接收时需要进行流处理。.比如对于PHP,使用php://input,如果是go,则使用如下代码获取请求体的所有内容,然后使用json.Unmarshal解析funcReceive(whttp.ResponseWriter,r*http.Request){b,_:=ioutil.ReadAll(r.Body)}我们继续看Vue的websocket部分exportdefault{data(){return{username:'',email:'',content:'',message_list:[],ws:null}},方法:{handleRecv:function(data){varjsonData=JSON.parse(data)this.message_list.unshift(jsonData.data)},wsOpen:function(){varthat=thisvarws=newWebSocket("ws://localhost:9000/ws")ws.onopen=function(){console.info("wsopen")}ws.onmessage=function(evt){那。handleRecv(evt.data)}ws.onclose=function(){console.info("wsclose")}this.ws=ws},wsSend:function(){if(this.ws==null){console.info("连接未打开")}this.ws.send(JSON.stringify({email:this.email,content:this.content}))}},mounted(){this.wsOpen();}}上面的代码是在Vue组件中定义的。其实核心就是在挂载的组件挂载后调用newWebSocket创建连接,并注册onOpen、onMessage、onClose事件。通过websocket发送json其实就是传递一个json字符串。对于基于golang的后端,我们还需要搭建一个websocket服务器来接收消息。Golang的websocket服务websocket是http协议的升级。这里我们使用的是websocket包“github.com/gorilla/websocket”对于main函数,我们构造如下//定义flag参数varaddr=flag.String("addr",":9000","http服务address")varupgrader=websocket.Upgrader{}funcmain(){flag.Parse()http.HandleFunc("/ws",echo)err:=http.ListenAndServe(*addr,nil)iferr!=nil{日志。Fatalf("%v",err)}}回显函数定义如下。不要害怕这么长的一段代码。其实核心很简单。我们首先创建一个结构体//messagereplytype,includingcodeanddatabodytypeMessageRetstruct{Codeint`json:"code"`Datainterface{}`json:"data"`}//cardcontainsparameterstostoretheparsedjsonobjecttypeCardstruct{Emailstring`json:"email"`Contentstring`json:"content"`}下面是主要的echo函数。做的就是升级http,监听某个链接的请求,回复。这里使用的方法主要是upgrager.CheckOrigin解决跨域问题upgrade.Upgrade()c.ReadMessage()c.WriteMessage()funcecho(whttp.ResponseWriter,r*http.Request){//跨域upgrader.CheckOrigin=func(r*http.Request)bool{returntrue}//升级httpc,err:=upgrader.Upgrade(w,r,nil)iferr!=nil{log.Fatalf("%v",呃)}deferc.Close()//结构指针varcard=&Card{}for{//块,等待读取消息mt,message,err:=c.ReadMessage()iferr!=nil{log.Println("read",nil)break}//解析参数err=json.Unmarshal(message,card)iferr!=nil{log.Fatalf("jsonparseerror%v",err)break}//这里可以自定义句柄处理card.Email="server-"+card.Emailcard.Content="server-"+card.Content//重新打包,返回~~~~varret=MessageRet{Code:http.StatusOK,Data:*card}b,_:=json.Marshal(ret)log.Printf("recv:%s",b)err=c.WriteMessage(mt,b)如果错误!=nil{log.Fatalf("write",nil)break}}}注意上面c.WriteMessage(mt,b)的第一个参数是MessageType类型,有两个值分别代表binary和textwebsockets结合protobuf。长连接,与go的websocket服务通信使用websocket避免轮询已经是减轻服务器请求压力的一种方式,那么我们可不可以在传输协议上做一些改变,要减小传输数据包的大小,请使用protobuf。关于protobuf,它是一种编码协议。你可以想象json和xml。protoc是proto文件的编译器,proto-gen-go是protoc的插件,可以根据proto文件编译成go文件。google-protobuf现在也支持生成js文件。使用protobuf还有一个好处就是如果我在goserver端定义了Request的包体内容和Response的包体内容,那么在生成go文件之后,也可以同时生成js文件这样双方可以按照相同的参数方式开发,相当于一个proto文件,相当于一个接口文件。那么我们首先生成一个proto文件。比如websocket需要发送请求,goserver需要返回内容,这就涉及到两个消息结构体的定义syntax="proto3";//请求消息ChatRequest{stringemail=1;stringcontent=2;}//响应消息ChatResponse{int32code=1;ChatRequestdata=2;}然后使用protoc生成文件go/jsprotoc--go_out=plugins=grpc:.*.protoprotoc--js_out=import_style=commonjs,binary:.*.protoprotogo文件的使用先介绍下goproto文件导入(pb"message_board_api/proto/chat")然后在代码中获取收到websocket消息后,进行proto解析就很简单了~~~~//使用protobuf解析pbr:=&pb.ChatRequest{}err=proto.Unmarshal(message,pbr)iferr!=nil{log.Fatalf("protoparsingfailed%v",err)break}protojs文件使用protojs文件,需要配合使用使用google-protobuf.js。根据官网文档,如果要在浏览器中使用,需要用browserify打包。在vue组件中导入包import"google-protobuf"importprotofrom"../proto/chat_pb.js"来看看websocket结合protobuf和传统的json有何不同,这么大的数据也不怕paragraph代码,我们主要看方法部分,ws:null}},methods:{//处理protobuf内容handleRecv:function(data){//这里解码接收到的二进制消息varrep=proto.ChatResponse.deserializeBinary(data)//可以得到数据和代码console.info(rep.getData())console.info(rep.getCode())//这里拼接消息,message_list是vue的列表,不用管this.message_list.unshift({email:rep.getData().getEmail(),content:rep.getData().getContent()})},wsOpen:function(){varthat=thisvarws=newWebSocket("ws://localhost:9000/ws")//这个地方很重要,websocket默认是Uint8arrayws.binaryType='arraybuffer';ws.onopen=function(){console.info("wsopen")}ws.onmessage=function(evt){console.info(evt)console.info("Receivedmessage:"+evt.data)that.handleRecv(evt.data)}ws.onclose=function(){console.info("wsclose")}this.ws=ws},wsSend:function(){if(this.ws==null){console.info("Theconnectionhasnotbeenopened")}//发送消息也很简单,我们不需要关心格式varchat=newproto.ChatRequest()chat.setEmail(this.email)chat.setContent(this.content)this.ws.send(chat.serializeBinary())}},mounted(){this.wsOpen();}}看看上面的注释部分。其实就是按照约定的消息结构分为两部分,设置数据遵循约定的消息结构,获取数据注意:proto.ChatResponse.deserializeBinary是静态方法,不需要new,必须改成arraybuffer,二进制数组ws.binaryType='arraybuffer';通过上面的过程,我们基本了解了protobuf在websocket中的使用,另外还有一个protobuf.js也很流行,但是没有专门研究过。我更喜欢官方广播,但这里有一个细节问题。如果我们要建立通信,一般来说,我们不会直接返回信息。因为websocket是全双工通信,不像http,请求一次,返回一次,关闭。如果我们要使用websocket,第一反应就是长连接,建立聊天室进行实时聊天。因此,让我们实现聊天功能。那么聊天能力,其实就是有一个广播的功能。一个人说话,每个人都能收到。这一点特别有趣。如果早就用ajax来做,还是需要保存数据,然后每次轮询读取输出到前端。现在不需要将其保存在数据库中。核心:广播其实就是向所有与服务器建立连接的客户端广播消息。看实施。首先,创建一个映射来存储客户端连接,然后创建一个消息缓冲通道。//Clientsetvarclients=make(map[*websocket.Conn]string)//消息缓冲区通道varmessages=make(chan*pb.ChatRequest,100)每次新建连接后,链接会保存在用于缓存的客户端。c,err:=upgrader.Upgrade(w,r,nil)iferr!=nil{log.Fatalf("%v",err)}deferc.Close()//将链??接的地址写给客户端:=c.RemoteAddr().String()log.Println(who)clients[c]=whoproto解析的部分前面已经提到了,这里将解析后的内容写入messages通道。//使用protobuf解析pbr:=&pb.ChatRequest{}err=proto.Unmarshal(message,pbr)iferr!=nil{log.Fatalf("protoparsingfailed%v",err)break}//解析后的内容被写入消息广播pbr.Email=pbr.Email+"<"+who+">"messages<-pbr下面是核心的boardcast方法funcboardcast(){//始终读取消息formsg:=rangemessages{//读取后广播并启动协程立即处理下一条消息Email:msg.Email,Content:msg.Content}}b,err:=proto.Marshal(pbrp)iferr!=nil{log.Fatalf("protomarshalerror%v",err)}//二进制发送客户端.WriteMessage(websocket.BinaryMessage,b)}}()}}上面的boardcast方法要交给协程goroutine处理,否则为rangemessages会阻塞,所以使用协程funcmain(){flag.Parse()http.HandleFunc("/ws",echo)//broadcastgoboardcast()//pprof这个是状态监听,可以忽略,也可以研究一下gofunc(){log.Println(http.ListenAndServe("localhost:6060",nil))}()//这里的ListenAndServe已经启动了goroutine协程err:=http.ListenAndServe(*addr,nil)iferr!=nil{log.Fatalf("%v",err)}}总结如下创建websocket和protobuf聊天能力的过程双方都需要实现websocket通信协议protomessage协议使用goroutine协程广播生成go和js两个版本。上面的例子并没有在客户端退出时从客户端删除链接。其实可以用下面的形式来删除,conn.close关闭连接delete(clients,cli)希望以上内容对大家有所帮助。详细问题可以留言讨论
