[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一下~
[Golang] 从零開始写Socket Server(2): 自己定义通讯协议的更多相关文章
- [Golang] 从零開始写Socket Server(3): 对长、短连接的处理策略(模拟心跳)
通过前两章,我们成功是写出了一套凑合能用的Server和Client,并在二者之间实现了通过协议交流.这么一来,一个简易的socket通讯框架已经初具雏形了,那么我们接下来做的.就是想办法让这个框架更 ...
- [Golang] 从零開始写Socket Server(4):将执行參数放入配置文件(XML/YAML)
为了将我们写好的Server公布到server上.就要将我们的代码进行build打包.这样假设以后想要改动一些代码的话.须要又一次给代码进行编译打包并上传到server上. 显然,这么做过于繁 ...
- 《从零開始搭建游戏server》项目公布到Linux环境
前言: 之前我们提及了怎样使用Maven来创建.管理和打包项目,也简单过了一遍Linux中搭建Java开发环境的步骤,如今我们就開始将我们之前开发的项目demo公布到Linux环境下.并让它正常执行起 ...
- 从零開始写游戏引擎(一) - project创建以及文件夹设置还有版本号控制
一句话提要 好的開始等于成功了一半. 创建文件夹结构 project文件夹下最好分为以下几个文件夹 Docs - 开发文档,设计文档 Assets - 角色,动作,模型和音效等 Source - 代码 ...
- 从零開始怎么写android native service?
从零開始怎么写android native service Android service对于从事android开发的人都不是一个陌生的东西,非常多人可能会认为服务非常easy. 服务是简单,由于复杂 ...
- 从零開始学习OpenCL开发(一)架构
多谢大家关注 转载本文请注明:http://blog.csdn.net/leonwei/article/details/8880012 本文将作为我<从零開始做OpenCL开发>系列文章的 ...
- # 从零開始搭建Hadoop2.7.1的分布式集群
Hadoop 2.7.1 (2015-7-6更新),Hadoop的环境配置不是特别的复杂,可是确实有非常多细节须要注意.不然会造成很多配置错误的情况.尽量保证一次配置正确防止重复改动. 网上教程有非常 ...
- Bmob移动后端云服务平台--Android从零開始--(二)android高速入门
Bmob移动后端云服务平台--Android从零開始--(二)android高速入门 上一篇博文我们简介何为Bmob移动后端服务平台,以及其相关功能和优势. 本文将利用Bmob高速实现简单样例,进一步 ...
- 《PHP 5.5从零開始学(视频教学版)》内容简单介绍、文件夹
<PHP 5.5从零開始学(视频教学版)>当当网购买地址: http://product.dangdang.com/23586810.html <PHP 5.5从零開始学(视频教学版 ...
随机推荐
- 用例UML图
用例图主要用来描述“用户.需求.系统功能单元”之间的关系.它展示了一个外部用户能够观察到的系统功能模型图. [用途]:帮助开发团队以一种可视化的方式理解系统的功能需求. 用例图中涉及的关系有:关联.泛 ...
- 团队Alpha版本冲刺(三)
目录 组员情况 组员1(组长):胡绪佩 组员2:胡青元 组员3:庄卉 组员4:家灿 组员5:凯琳 组员6:丹丹 组员7:家伟 组员8:政演 组员9:鸿杰 组员10:刘一好 组员11:何宇恒 展示组内最 ...
- Vagrant Tip: Virtualbox Guest Additions
Vagrant Tip: Virtualbox Guest Additions 12 February 2016 Tired of seeing this message when you run v ...
- 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 ...
- [SDOI2011][bzoj2245] 工作分配 [费用流]
题面 传送门 思路 数据范围n,m<=250 分配任务问题 这是典型的"看到数据范围就知道算法"类型 而且我们发现我们要保证一定产出的情况下最小化花费 这句话等价于保证一定流 ...
- 关于jena-fuseki SPARQL query版本问题的解决方案
在做“Apache jena SPARQL endpoint及推理”时,遇到了不少问题,主要原因是jena-fuseki版本更新了.以下对问题解决方案做下笔记.想了解更多,请查阅底部参考文献. Que ...
- 关于ContextImp的源码分析
关于ContextImp的源码分析 来源: http://blog.csdn.net/qinjuning/article/details/7310620 Context概述: Android ...
- tomcat镜像构建
1.目录结构与配置文件如下 [root@centos05 java]# tree . ├── apache-tomcat-.tar.gz ├── Dockerfile ├── jdk-8u45-lin ...
- 在razor中使用递归,巧用递归
原文发布时间为:2011-04-20 -- 来源于本人的百度文章 [由搬家工具导入] Learning Razor–Writing an Inline Recursive HTML Helper Wr ...
- js,add script async? loaded ok.
function loadScript(url, callback){ var script = document.createElement_x("script") script ...