在上一章我们做出来一个最基础的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 protocol
- import (
- "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)
- }
- }
- 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粘包问题和处理