在上一章我们做出来一个最基础的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): 自己定义通讯协议的更多相关文章

  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. Windows 7中安装SQL2005提示IIS未安装 解决办法 .(转载)

    在Windows 7系统中安装SQL Server 2005时,可能会收到一个警告:提示IIS未安装或者未启用.在通过“控制面板”的“打开或关闭Windows功能”按默认设置安装IIS后,发现仍有这个 ...

  2. nmon系统监控

    nmon系统监控篇 本文目录 1 nmon的安装 2 nmon控制台指令 3 输出监控文件 一 下载nmon 本次使用的是nmon_linux_14i.tar.gz 二 放入linux中后解压 gzi ...

  3. sass和postcss

    sass是css预处理器 需要安装node-sass支持 核心是c++编写 集成 sass-loader 把scss装换成css css-loader 找出@import和url()导入的语法,告诉w ...

  4. 【bzoj2161】布娃娃 权值线段树

    题目描述 小时候的雨荨非常听话,是父母眼中的好孩子.在学校是老师的左右手,同学的好榜样.后来她成为艾利斯顿第二代考神,这和小时候培养的良好素质是分不开的.雨荨的妈妈也为有这么一个懂事的女儿感到高兴.一 ...

  5. POJ 2728 Desert King(最优比例生成树 二分 | Dinkelbach迭代法)

    Desert King Time Limit: 3000MS   Memory Limit: 65536K Total Submissions: 25310   Accepted: 7022 Desc ...

  6. 论文笔记《ImageNet Classification with Deep Convolutional Neural Network》

    一.摘要 了解CNN必读的一篇论文,有些东西还是可以了解的. 二.结构 1. Relu的好处: 1.在训练时间上,比tanh和sigmod快,而且BP的时候求导也很容易 2.因为是非饱和函数,所以基本 ...

  7. [USACO Section 5.3]量取牛奶 Milk Measuring (动态规划,背包$dp$)

    题目链接 Solution 完全背包 \(dp\) , 同时再加一个数组 \(v[i][j]\) 记录当总和为\(j\) 时第 \(i\) 种物品是否被选. 为保证从小到大和字典序,先将瓶子按大小排序 ...

  8. (...)ES6三点扩展运算符

    扩展运算符将一个数组转为用逗号分隔的参数序列 console.log(...[a, b, c]) // a b c 用于: 1 将一个数组,变为参数序列 let add = (x, y) => ...

  9. T-SQL还有个内置方法NULLIF()

    declare @cypic varchar if (NULLIF(@cypic, '') IS NOT NULL) begin print 1 end else begin print 2 end ...

  10. 用c#语言通过修改注册表改IE网页首页

    原文发布时间为:2009-04-19 -- 来源于本人的百度文章 [由搬家工具导入] string key = @"HKEY_CURRENT_USER\Software\Microsoft\ ...