go语言Socket Server 自定义协议的简单实现
2017-09-19 04:32
537 查看
在 Server 和 Client 通讯中,由于网络等原因很有可能会发生数据丢包的现象。如果数据确实,服务端接收的信息不完整,就会造成混乱。
我们就需要在 Server 和 Client 之间建立一个通讯协议,通过协议中的规则,判断当前接收到的信息是否完整。根据信息的完整情况,采取不同的处理方法。
通讯协议 protocol 的核心就是设计一个头部。如果传来的信息不包含这个头部,就说明当前信息和之前的信息是同一条。那么就把当前信息和之前的那条信息合并成一条。
而协议主要包含的功能是封装(Enpack)和解析(Depack)。
Enpack 是客户端对信息进行数据封装。封装之后可以传递给服务器。
Depack 是服务端对信息进行数据解封。
其中有个 Const 部分,用于定义头部、头部长度、客户端传入信息长度。
在代码中,我们这样定义
头部的内容为 “Headers”,长度为 7 ,所以 ConstHeaderLength = 7
而信息传递中,我们会把 int 类型转换成 byte 类型。一个 int 的长度等于 4 个 byte 的长度。因此,我们设置 ConstMLength = 4。代表客户端传来的信息大小。
自定义协议 protocol 的代码示例如下:
Server 端主要是通过协议来解析客户端发送来的信息。
建立一个函数,用来完成连接对接收信息的处理。其中建立了通道readerChannel,并把接收来的信息放在通道里。在放入通道之前,使用 protocol 的 Depack 对信息进行解析。
如果信息读取发生错误(包括读取到信息结束符 EOF),都会打印错误信息,并挑出循环。
由于通道内的数据是 []byte 型的。需要转换成 string。这个工作有专门的获取通道数据的 reader(readerChannel chan []byte) 来完成。
Server 端代码示例:
客户端使用 Enpack 封装要发送到服务端的信息后,写入连接 conn 中。
我们就需要在 Server 和 Client 之间建立一个通讯协议,通过协议中的规则,判断当前接收到的信息是否完整。根据信息的完整情况,采取不同的处理方法。
通讯协议 protocol 的核心就是设计一个头部。如果传来的信息不包含这个头部,就说明当前信息和之前的信息是同一条。那么就把当前信息和之前的那条信息合并成一条。
而协议主要包含的功能是封装(Enpack)和解析(Depack)。
Enpack 是客户端对信息进行数据封装。封装之后可以传递给服务器。
Depack 是服务端对信息进行数据解封。
其中有个 Const 部分,用于定义头部、头部长度、客户端传入信息长度。
在代码中,我们这样定义
const ( ConstHeader = "Headers" ConstHeaderLength = 7 ConstMLength = 4 )
头部的内容为 “Headers”,长度为 7 ,所以 ConstHeaderLength = 7
而信息传递中,我们会把 int 类型转换成 byte 类型。一个 int 的长度等于 4 个 byte 的长度。因此,我们设置 ConstMLength = 4。代表客户端传来的信息大小。
自定义协议 protocol 的代码示例如下:
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++ {
if length < i + ConstHeaderLength + ConstMLength{
break
}
if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
messageLength := ByteToInt(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 ByteToInt(n []byte) int {
bytesbuffer := bytes.NewBuffer(n)
var x int32
binary.Read(bytesbuffer, binary.BigEndian, &x)
return int(x)
}
//整数转换成字节
func IntToBytes(n int) []byte {
x := int32(n)
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
return bytesBuffer.Bytes()
}
Server 端主要是通过协议来解析客户端发送来的信息。
建立一个函数,用来完成连接对接收信息的处理。其中建立了通道readerChannel,并把接收来的信息放在通道里。在放入通道之前,使用 protocol 的 Depack 对信息进行解析。
//连接处理 func handleConnection(conn net.Conn) { //缓冲区,存储被截断的数据 tmpBuffer := make([]byte, 0) //接收解包 readerChannel := make(chan []byte, 10000) 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 <- tmpBuffer //接收的信息写入通道 } defer conn.Close() }
如果信息读取发生错误(包括读取到信息结束符 EOF),都会打印错误信息,并挑出循环。
Log(conn.RemoteAddr().String(), "connection error: ", err) return
由于通道内的数据是 []byte 型的。需要转换成 string。这个工作有专门的获取通道数据的 reader(readerChannel chan []byte) 来完成。
//获取通道数据 func reader(readerchannel chan []byte) { for{ select { case data := <-readerchannel: Log(string(data)) //打印通道内的信息 } } }
Server 端代码示例:
package main
import (
"net"
"fmt"
"os"
"log"
"protocol"
)
func main() {
netListen, err := net.Listen("tcp", "localhost:7373")
CheckErr(err)
defer netListen.Close()
Log("Waiting for client ...") //启动后,等待客户端访问。
for{
conn, err := netListen.Accept() //监听客户端
if err != nil {
Log(conn.RemoteAddr().String(), "发了了错误:", err)
continue
}
Log(conn.RemoteAddr().String(), "tcp connection success")
go handleConnection(conn)
}
}
//连接处理 func handleConnection(conn net.Conn) { //缓冲区,存储被截断的数据 tmpBuffer := make([]byte, 0) //接收解包 readerChannel := make(chan []byte, 10000) 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 <- tmpBuffer //接收的信息写入通道 } defer conn.Close() }
//获取通道数据 func reader(readerchannel chan []byte) { for{ select { case data := <-readerchannel: Log(string(data)) //打印通道内的信息 } } }
//日志处理
func Log(v ...interface{}) {
log.Println(v...)
}
//错误处理
func CheckErr(err error) {
if err != nil {
fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
os.Exit(1)
}
}
客户端使用 Enpack 封装要发送到服务端的信息后,写入连接 conn 中。
package main import ( "net" "time" "strconv" "protocol" "fmt" "os" ) //发送100次请求 func send(conn net.Conn) { for i := 0; i < 100; i++ { session := GetSession() words := "{\"ID\":\""+strconv.Itoa(i)+"\",\"Session\":\""+session+"20170914165908\",\"Meta\":\"golang\",\"Content\":\"message\"}" conn.Write(protocol.Enpack([]byte(words))) fmt.Println(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:7373" 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) }
相关文章推荐
- Golang Socket Server自定义协议的简单实现
- 用socket自定义简单协议实现文件上传与接受
- Go语言简单实现FTP协议.
- 基于socket简单通信协议实现
- 套接字(socket)编程简单实现server-client聊天程序
- GO语言实现简单的目录复制功能
- 实践,用tornado实现自定义协议server
- 一个Go语言接口和多操作系统实现的简单例子
- [001]socket通信--server和client实现迭代的简单例子
- 一个简单的自定义通信协议(socket)
- socket下server端支持多客户端并发访问简单实现
- 面向连接的Socket Server的简单实现(简明易懂)
- 基于socket简单通信协议实现(c/c++)
- 简单通过java的socket&serversocket以及多线程技术实现多客户端的数据的传输,并将数据写入hbase中
- 一个简单的自定义通信协议(socket)
- 基于socket简单通信协议实现(c/c++)
- Python实现同时兼容老版和新版Socket协议的一个简单WebSocket服务器
- 使用socket实现简单的客户端和服务端通信(C#语言)
- 实现一个go语言的简单爬虫来爬取CSDN博文(一)
- 自行控制loadrunner的socket协议性能测试 (转) 一前言 二任务的提出 三实现方案讨论 四技术要点讲解 如何开始录制一个最简单的收发数据包脚本 写日志文件 一行一行读数据包文件 字