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

  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) []byte {
  21. length := len(buffer)
  22.  
  23. var i int
  24. data := make([]byte, 32)
  25. for i = 0; i < length; i = i + 1 {
  26. if length < i+ConstHeaderLength+ConstMLength {
  27. break
  28. }
  29. if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
  30. messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
  31. if length < i+ConstHeaderLength+ConstMLength+messageLength {
  32. break
  33. }
  34. data = buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
  35.  
  36. }
  37. }
  38.  
  39. if i == length {
  40. return make([]byte, 0)
  41. }
  42. return data
  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, binary.BigEndian, &x)
  60.  
  61. return int(x)
  62. }

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

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

然后是Client端的代码,这个简单多了。仅仅要给信息封装一下就能够了~:

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

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

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

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

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

[Golang] 从零開始写Socket Server(2): 自己定义通讯协议的更多相关文章

  1. [Golang] 从零開始写Socket Server(3): 对长、短连接的处理策略(模拟心跳)

    通过前两章,我们成功是写出了一套凑合能用的Server和Client,并在二者之间实现了通过协议交流.这么一来,一个简易的socket通讯框架已经初具雏形了,那么我们接下来做的.就是想办法让这个框架更 ...

  2. [Golang] 从零開始写Socket Server(4):将执行參数放入配置文件(XML/YAML)

    为了将我们写好的Server公布到server上.就要将我们的代码进行build打包.这样假设以后想要改动一些代码的话.须要又一次给代码进行编译打包并上传到server上.     显然,这么做过于繁 ...

  3. 《从零開始搭建游戏server》项目公布到Linux环境

    前言: 之前我们提及了怎样使用Maven来创建.管理和打包项目,也简单过了一遍Linux中搭建Java开发环境的步骤,如今我们就開始将我们之前开发的项目demo公布到Linux环境下.并让它正常执行起 ...

  4. 从零開始写游戏引擎(一) - project创建以及文件夹设置还有版本号控制

    一句话提要 好的開始等于成功了一半. 创建文件夹结构 project文件夹下最好分为以下几个文件夹 Docs - 开发文档,设计文档 Assets - 角色,动作,模型和音效等 Source - 代码 ...

  5. 从零開始怎么写android native service?

    从零開始怎么写android native service Android service对于从事android开发的人都不是一个陌生的东西,非常多人可能会认为服务非常easy. 服务是简单,由于复杂 ...

  6. 从零開始学习OpenCL开发(一)架构

    多谢大家关注 转载本文请注明:http://blog.csdn.net/leonwei/article/details/8880012 本文将作为我<从零開始做OpenCL开发>系列文章的 ...

  7. # 从零開始搭建Hadoop2.7.1的分布式集群

    Hadoop 2.7.1 (2015-7-6更新),Hadoop的环境配置不是特别的复杂,可是确实有非常多细节须要注意.不然会造成很多配置错误的情况.尽量保证一次配置正确防止重复改动. 网上教程有非常 ...

  8. Bmob移动后端云服务平台--Android从零開始--(二)android高速入门

    Bmob移动后端云服务平台--Android从零開始--(二)android高速入门 上一篇博文我们简介何为Bmob移动后端服务平台,以及其相关功能和优势. 本文将利用Bmob高速实现简单样例,进一步 ...

  9. 《PHP 5.5从零開始学(视频教学版)》内容简单介绍、文件夹

    <PHP 5.5从零開始学(视频教学版)>当当网购买地址: http://product.dangdang.com/23586810.html <PHP 5.5从零開始学(视频教学版 ...

随机推荐

  1. 用例UML图

    用例图主要用来描述“用户.需求.系统功能单元”之间的关系.它展示了一个外部用户能够观察到的系统功能模型图. [用途]:帮助开发团队以一种可视化的方式理解系统的功能需求. 用例图中涉及的关系有:关联.泛 ...

  2. 团队Alpha版本冲刺(三)

    目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员11:何宇恒 展示组内最 ...

  3. Vagrant Tip: Virtualbox Guest Additions

    Vagrant Tip: Virtualbox Guest Additions 12 February 2016 Tired of seeing this message when you run v ...

  4. Google Code Jam 2008 Round 1A C Numbers(矩阵快速幂+化简方程,好题)

    Problem C. Numbers This contest is open for practice. You can try every problem as many times as you ...

  5. [SDOI2011][bzoj2245] 工作分配 [费用流]

    题面 传送门 思路 数据范围n,m<=250 分配任务问题 这是典型的"看到数据范围就知道算法"类型 而且我们发现我们要保证一定产出的情况下最小化花费 这句话等价于保证一定流 ...

  6. 关于jena-fuseki SPARQL query版本问题的解决方案

    在做“Apache jena SPARQL endpoint及推理”时,遇到了不少问题,主要原因是jena-fuseki版本更新了.以下对问题解决方案做下笔记.想了解更多,请查阅底部参考文献. Que ...

  7. 关于ContextImp的源码分析

      关于ContextImp的源码分析 来源: http://blog.csdn.net/qinjuning/article/details/7310620 Context概述:   Android ...

  8. tomcat镜像构建

    1.目录结构与配置文件如下 [root@centos05 java]# tree . ├── apache-tomcat-.tar.gz ├── Dockerfile ├── jdk-8u45-lin ...

  9. 在razor中使用递归,巧用递归

    原文发布时间为:2011-04-20 -- 来源于本人的百度文章 [由搬家工具导入] Learning Razor–Writing an Inline Recursive HTML Helper Wr ...

  10. js,add script async? loaded ok.

    function loadScript(url, callback){ var script = document.createElement_x("script") script ...