[Golang] 从零开始写Socket Server(2): 自定义通讯协议

[Golang] 从零开始写Socket Server(2): 自定义通讯协议,第1张

概述        在上一章我们做出来一个最基础的demo后,已经可以初步实现Server和Client之间的信息交流了~ 这一章我会介绍一下怎么在Server和Client之间实现一个简单的通讯协议,从而增强整个信息交流过程的稳定性。         在Server和client的交互过程中,有时候很难避免出现网络波动,而在通讯质量较差的时候,Client有可能无法将信息流一次性完整发送,最终传到S

在上一章我们做出来一个最基础的demo后,已经可以初步实现Server和ClIEnt之间的信息交流了~ 这一章我会介绍一下怎么在Server和ClIEnt之间实现一个简单的通讯协议,从而增强整个信息交流过程的稳定性。

在Server和clIEnt的交互过程中,有时候很难避免出现网络波动,而在通讯质量较差的时候,ClIEnt有可能无法将信息流一次性完整发送,最终传到Server上的信息很可能变为很多段。

如下图所示,本来应该是分条传输的Json,结果因为一些原因连接在了一起,这时候就会出现问题啦,Server端要怎么判断收到的消息是否完整呢?~




唔,答案就是这篇文章的主题啦:在Server和ClIEnt交互的时候,加入一个通讯协议(protocol),让二者的交互通过这个协议进行封装,从而使Server能够判断收到的信息是否为完整的一段。(也就是解决分包的问题)

因为主要目的是为了让Server能判断客户端发来的信息是否完整,因此整个协议的核心思路并不是很复杂:

协议的核心就是设计一个头部(headers),在ClIEnt每次发送信息的时候将header封装进去,再让Server在每次收到信息的时候按照预定格式将消息进行解析,这样根据ClIEnt传来的数据中是否包含headers,就可以很轻松的判断收到的信息是否完整了~

如果信息完整,那么就将该信息发送给下一个逻辑进行处理,如果信息不完整(缺少headers),那么Server就会把这条信息与前一条信息合并继续处理。


下面是协议部分的代码,主要分为数据的封装(Enpack)和解析(Depack)两个部分,其中Enpack用于ClIEnt端将传给服务器的数据封装,而Depack是Server用来解析数据,其中Const部分用于定义headers,headerLength则是headers的长度,用于后面Server端的解析。这里要说一下ConstMLength,这里代表ClIEnt传入信息的长度,因为在golang中,int转为byte后会占4长度的空间,因此设定为4。每次ClIEnt向Server发送信息的时候,除了将headers封装进去意以外,还会将传入信息的长度也封装进去,这样可以方便Server进行解析和校验。


//通讯协议处理package protocolimport (	"bytes"	"enCoding/binary")const (	Constheader         = "headers"	ConstheaderLength   = 7	ConstMLength = 4)//封包func Enpack(message []byte) []byte {	return append(append([]byte(Constheader),IntToBytes(len(message))...),message...)}//解包func Depack(buffer []byte,readerChannel chan []byte) []byte {	length := len(buffer)	var i int	for i = 0; i < length; i = i + 1 {		if length < i+ConstheaderLength+ConstMLength {			break		}		if string(buffer[i:i+ConstheaderLength]) == Constheader {			messageLength := BytesToInt(buffer[i+ConstheaderLength : i+ConstheaderLength+ConstMLength])			if length < i+ConstheaderLength+ConstLength+messageLength {				break			}			data := buffer[i+ConstheaderLength+ConstMLength : i+ConstheaderLength+ConstMLength+messageLength]			readerChannel <- data		}	}	if i == length {		return make([]byte,0)	}	return buffer[i:]}//整形转换成字节func IntToBytes(n int) []byte {	x := int32(n)	bytesBuffer := bytes.NewBuffer([]byte{})	binary.Write(bytesBuffer,binary.BigEndian,x)	return bytesBuffer.Bytes()}//字节转换成整形func BytesToInt(b []byte) int {	bytesBuffer := bytes.NewBuffer(b)	var x int32	binary.Read(bytesBuffer,&x)	return int(x)}


协议写好之后,接下来就是在Server和ClIEnt的代码中应用协议啦,下面是Server端的代码,主要负责解析ClIEnt通过协议发来的信息流:


package main    import (      "protocol"      "fmt"      "net"      "os"  )    func main() {      netListen,err := net.Listen("tcp","localhost:6060")      Checkerror(err)        defer netListen.Close()        Log("Waiting for clIEnts")      for {          conn,err := netListen.Accept()          if err != nil {              continue          }            //timeouSec :=10          //conn.          Log(conn.RemoteAddr().String()," tcp connect success")          go handleConnection(conn)        }  }    func handleConnection(conn net.Conn) {          // 缓冲区,存储被截断的数据      tmpBuffer := make([]byte,0)        //接收解包      readerChannel := make(chan []byte,16)      go reader(readerChannel)        buffer := make([]byte,1024)      for {      n,err := conn.Read(buffer)      if err != nil {      Log(conn.RemoteAddr().String()," connection error: ",err)      return      }        tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:n]...),readerChannel)      }      defer conn.Close()  }    func reader(readerChannel chan []byte) {      for {          select {          case data := <-readerChannel:              Log(string(data))          }      }  }    func Log(v ...interface{}) {      fmt.Println(v...)  }    func Checkerror(err error) {      if err != nil {          fmt.Fprintf(os.Stderr,"Fatal error: %s",err.Error())          os.Exit(1)      }  }  



然后是ClIEnt端的代码,这个简单多了,只要给信息封装一下就可以了~:


package main  import (  "protocol"  "fmt"  "net"  "os"  "time"  "strconv"    )    func send(conn net.Conn) {      for i := 0; i < 100; i++ {          session:=GetSession()          words := "{\"ID\":"+ strconv.Itoa(i) +"\",\"Session\":"+session +"2015073109532345\",\"Meta\":\"golang\",\"Content\":\"message\"}"          conn.Write(protocol.Enpacket([]byte(words)))      }      fmt.Println("send over")      defer conn.Close()  }    func GetSession() string{      gs1:=time.Now().Unix()      gs2:=strconv.FormatInt(gs1,10)      return gs2  }    func main() {      server := "localhost:6060"      tcpAddr,err := net.ResolveTCPAddr("tcp4",server)      if err != nil {          fmt.Fprintf(os.Stderr,err.Error())          os.Exit(1)      }        conn,err := net.DialTCP("tcp",nil,tcpAddr)      if err != nil {          fmt.Fprintf(os.Stderr,err.Error())          os.Exit(1)      }          fmt.Println("connect success")      send(conn)        }  



这样我们就成功实现在Server和ClIEnt之间建立一套自定义的基础通讯协议啦,让我们运行一下看下效果:




成功识别每一条ClIEnt发来的信息啦~~

更多详细信息可以参考这篇文章:golang中tcp socket粘包问题和处理

总结

以上是内存溢出为你收集整理的[Golang] 从零开始写Socket Server(2): 自定义通讯协议全部内容,希望文章能够帮你解决[Golang] 从零开始写Socket Server(2): 自定义通讯协议所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1287039.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-09
下一篇 2022-06-09

发表评论

登录后才能评论

评论列表(0条)

保存