zoukankan      html  css  js  c++  java
  • [Golang] 从零開始写Socket Server(2): 自己定义通讯协议

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

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

            例如以下图所看到的。本来应该是分条传输的json。结果由于一些原因连接在了一起,这时候就会出现故障啦。Server端要怎么推断收到的消息是否完整呢?~




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

            由于主要目的是为了让Server能推断client发来的信息是否完整,因此整个协议的核心思路并非非常复杂:

    协议的核心就是设计一个头部(headers),在Client每次发送信息的时候将header封装进去。再让Server在每次收到信息的时候依照预定格式将消息进行解析,这样依据Client传来的数据中是否包括headers,就能够非常轻松的推断收到的信息是否完整了~

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


            以下是协议部分的代码,主要分为数据的封装(Enpack)和解析(Depack)两个部分,当中Enpack用于Client端将传给server的数据封装。而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) []byte {
    	length := len(buffer)
    
    	var i int
    	data := make([]byte, 32)
    	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+ConstMLength+messageLength {
    				break
    			}
    			data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
    
    		}
    	}
    
    	if i == length {
    		return make([]byte, 0)
    	}
    	return data
    }
    
    //整形转换成字节
    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, binary.BigEndian, &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]...))  
        }  
        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, "Fatal error: %s", err.Error())  
            os.Exit(1)  
        }  
      
        conn, err := net.DialTCP("tcp", nil, tcpAddr)  
        if err != nil {  
            fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())  
            os.Exit(1)  
        }  
      
      
        fmt.Println("connect success")  
        send(conn)  
      
      
      
    }  



            这样我们就成功实如今Server和Client之间建立一套自己定义的基础通讯协议啦,让我们执行一下看下效果:




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

    很多其它具体信息能够參考这篇文章: golang中tcp socket粘包问题和处理



    我已经把SocketServer系列的代码整合到了一起,公布到了我个人的github上:点击链接, 希望大家有兴趣的能够学习star一下~

  • 相关阅读:
    网络编程-python实现-UDP(1.1.2)
    网络编程-python实现-socket(1.1.1)
    1-浮动布局
    1-解决java Scanner出现 java.util.NoSuchElementException
    1.激活函数
    A-交叉熵的使用
    matplotlib的学习16-animation动画
    matplotlib的学习15-次坐标轴
    matplotlib的学习14-图中图
    07-爬虫验证码破解实战
  • 原文地址:https://www.cnblogs.com/gccbuaa/p/6785443.html
Copyright © 2011-2022 走看看