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

前端之家收集整理的这篇文章主要介绍了[Golang] 从零开始写Socket Server(2): 自定义通讯协议前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

在上一章我们做出来一个最基础的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进行解析和校验。


  1. //通讯协议处理
  2. package protocol
  3.  
  4. import (
  5. "bytes"
  6. "encoding/binary"
  7. )
  8. const (
  9. ConstHeader = "Headers"
  10. ConstHeaderLength = 7
  11. ConstMLength = 4
  12. )
  13.  
  14. //封包
  15. func Enpack(message []byte) []byte {
  16. return append(append([]byte(ConstHeader),IntToBytes(len(message))...),message...)
  17. }
  18.  
  19. //解包
  20. func Depack(buffer []byte,readerChannel chan []byte) []byte {
  21. length := len(buffer)
  22.  
  23. var i int
  24. for i = 0; i < length; i = i + 1 {
  25. if length < i+ConstHeaderLength+ConstMLength {
  26. break
  27. }
  28. if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
  29. messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
  30. if length < i+ConstHeaderLength+ConstLength+messageLength {
  31. break
  32. }
  33. data := buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
  34. readerChannel <- data
  35.  
  36. }
  37. }
  38.  
  39. if i == length {
  40. return make([]byte,0)
  41. }
  42. return buffer[i:]
  43. }
  44.  
  45. //整形转换成字节
  46. func IntToBytes(n int) []byte {
  47. x := int32(n)
  48.  
  49. bytesBuffer := bytes.NewBuffer([]byte{})
  50. binary.Write(bytesBuffer,binary.BigEndian,x)
  51. return bytesBuffer.Bytes()
  52. }
  53.  
  54. //字节转换成整形
  55. func BytesToInt(b []byte) int {
  56. bytesBuffer := bytes.NewBuffer(b)
  57.  
  58. var x int32
  59. binary.Read(bytesBuffer,&x)
  60.  
  61. return int(x)
  62. }


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


  1. package main
  2. import (
  3. "protocol"
  4. "fmt"
  5. "net"
  6. "os"
  7. )
  8. func main() {
  9. netListen,err := net.Listen("tcp","localhost:6060")
  10. CheckError(err)
  11. defer netListen.Close()
  12. Log("Waiting for clients")
  13. for {
  14. conn,err := netListen.Accept()
  15. if err != nil {
  16. continue
  17. }
  18. //timeouSec :=10
  19. //conn.
  20. Log(conn.RemoteAddr().String()," tcp connect success")
  21. go handleConnection(conn)
  22. }
  23. }
  24. func handleConnection(conn net.Conn) {
  25. // 缓冲区,存储被截断的数据
  26. tmpBuffer := make([]byte,0)
  27. //接收解包
  28. readerChannel := make(chan []byte,16)
  29. go reader(readerChannel)
  30. buffer := make([]byte,1024)
  31. for {
  32. n,err := conn.Read(buffer)
  33. if err != nil {
  34. Log(conn.RemoteAddr().String()," connection error: ",err)
  35. return
  36. }
  37. tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:n]...),readerChannel)
  38. }
  39. defer conn.Close()
  40. }
  41. func reader(readerChannel chan []byte) {
  42. for {
  43. select {
  44. case data := <-readerChannel:
  45. Log(string(data))
  46. }
  47. }
  48. }
  49. func Log(v ...interface{}) {
  50. fmt.Println(v...)
  51. }
  52. func CheckError(err error) {
  53. if err != nil {
  54. fmt.Fprintf(os.Stderr,"Fatal error: %s",err.Error())
  55. os.Exit(1)
  56. }
  57. }



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


  1. package main
  2. import (
  3. "protocol"
  4. "fmt"
  5. "net"
  6. "os"
  7. "time"
  8. "strconv"
  9. )
  10. func send(conn net.Conn) {
  11. for i := 0; i < 100; i++ {
  12. session:=GetSession()
  13. words := "{\"ID\":"+ strconv.Itoa(i) +"\",\"Session\":"+session +"2015073109532345\",\"Meta\":\"golang\",\"Content\":\"message\"}"
  14. conn.Write(protocol.Enpacket([]byte(words)))
  15. }
  16. fmt.Println("send over")
  17. defer conn.Close()
  18. }
  19. func GetSession() string{
  20. gs1:=time.Now().Unix()
  21. gs2:=strconv.FormatInt(gs1,10)
  22. return gs2
  23. }
  24. func main() {
  25. server := "localhost:6060"
  26. tcpAddr,err := net.ResolveTCPAddr("tcp4",server)
  27. if err != nil {
  28. fmt.Fprintf(os.Stderr,err.Error())
  29. os.Exit(1)
  30. }
  31. conn,err := net.DialTCP("tcp",nil,tcpAddr)
  32. if err != nil {
  33. fmt.Fprintf(os.Stderr,err.Error())
  34. os.Exit(1)
  35. }
  36. fmt.Println("connect success")
  37. send(conn)
  38. }



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




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

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

猜你在找的Go相关文章