分享
  1. 首页
  2. 文章

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

ahlxt123 · · 7321 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

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


//通讯协议处理
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, readerChannel chan []byte) []byte {
	length := len(buffer)
	var i int
	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+ConstLength+messageLength {
				break
			}
			data := buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength]
			readerChannel <- data
		}
	}
	if i == length {
		return make([]byte, 0)
	}
	return buffer[i:]
}
//整形转换成字节
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]...), readerChannel) 
 } 
 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粘包问题和处理

版权声明:本文为博主原创文章,未经博主允许不得转载。


有疑问加站长微信联系(非本文作者)

本文来自:CSDN博客

感谢作者:ahlxt123

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

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

关注微信
7321 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏