go 客户端服务端通信
client.go
package main import (
"bufio"
"encoding/json"
"fmt"
"hash/crc32"
"math/rand"
"net"
"os"
// "sync"
"time"
) //数据包类型
const (
HEART_BEAT_PACKET = 0x00
REPORT_PACKET = 0x01
) //默认的服务器地址
var (
server = "127.0.0.1:8080"
) //数据包
type Packet struct {
PacketType byte
PacketContent []byte
} //心跳包
type HeartPacket struct {
Version string `json:"version"`
Timestamp int64 `json:"timestamp"`
} //数据包
type ReportPacket struct {
Content string `json:"content"`
Rand int `json:"rand"`
Timestamp int64 `json:"timestamp"`
} //注册
type RegisterReq struct {
PERAESKey string `json:"PERAESKey"`
VIN string `json:"VIN"`
T_Box_SN string `json:"T_Box_SN"`
IMSI string `json:"IMSI"`
rollNumber string `json:"rollNumber"`
} //客户端对象
type TcpClient struct {
connection *net.TCPConn
hawkServer *net.TCPAddr
stopChan chan struct{}
} func main() {
//拿到服务器地址信息
hawkServer, err := net.ResolveTCPAddr("tcp", server)
if err != nil {
fmt.Printf("hawk server [%s] resolve error: [%s]", server, err.Error())
os.Exit()
}
//连接服务器
connection, err := net.DialTCP("tcp", nil, hawkServer)
if err != nil {
fmt.Printf("connect to hawk server error: [%s]", err.Error())
os.Exit()
}
client := &TcpClient{
connection: connection,
hawkServer: hawkServer,
stopChan: make(chan struct{}),
}
//启动接收
go client.receivePackets() //发送心跳的goroutine
/*go func() {
heartBeatTick := time.Tick(2 * time.Second)
for {
select {
case <-heartBeatTick:
client.sendHeartPacket()
case <-client.stopChan:
return
}
}
}()*/ //测试用的,开300个goroutine每秒发送一个包
// for i := 0; i < 1; i++ {
go func() {
sendTimer := time.After( * time.Second)
for {
select {
case <-sendTimer:
client.sendReportPacket()
sendTimer = time.After( * time.Second)
case <-client.stopChan:
return
}
}
}()
// }
//等待退出
<-client.stopChan
} // 接收数据包
func (client *TcpClient) receivePackets() {
reader := bufio.NewReader(client.connection)
for {
//承接上面说的服务器端的偷懒,我这里读也只是以\n为界限来读区分包
msg, err := reader.ReadString('\n')
if err != nil {
//在这里也请处理如果服务器关闭时的异常
close(client.stopChan)
break
}
fmt.Print(msg)
}
} //发送数据包
//仔细看代码其实这里做了两次json的序列化,有一次其实是不需要的
func (client *TcpClient) sendReportPacket() {
registPacket := RegisterReq{
PERAESKey: "",
VIN: "abcdef",
T_Box_SN: "abcdef123456",
IMSI: "IMSI",
rollNumber: getRandString(),
/*Content: getRandString(),
Timestamp: time.Now().Unix(),
Rand: rand.Int(),*/
}
fmt.Println("registPacket:", registPacket)
packetBytes, err := json.Marshal(registPacket) //返回值是字节数组byte[],
if err != nil {
fmt.Println(err.Error())
}
//这一次其实可以不需要,在封包的地方把类型和数据传进去即可
/*packet := Packet{
PacketType: REPORT_PACKET,
PacketContent: packetBytes,
}
sendBytes, err := json.Marshal(packet)
if err != nil {
fmt.Println(err.Error())
}*/
//发送 client.connection.Write(EnPackSendData(packetBytes))
// fmt.Println("EnPackSendData(packetBytes):%v", EnPackSendData(packetBytes))
// fmt.Println("Send metric data success!")
} //使用的协议与服务器端保持一致
func EnPackSendData(sendBytes []byte) []byte {
packetLength := len(sendBytes) +
result := make([]byte, packetLength)
result[] = 0xFF
result[] = 0xFF
result[] = byte(uint16(len(sendBytes)) >> ) //除以2的8次方,byte是0-255,
result[] = byte(uint16(len(sendBytes)) & 0xFF)
copy(result[:], sendBytes)
sendCrc := crc32.ChecksumIEEE(sendBytes)
result[packetLength-] = byte(sendCrc >> )
result[packetLength-] = byte(sendCrc >> & 0xFF)
result[packetLength-] = 0xFF
result[packetLength-] = 0xFE
fmt.Println(result)
return result
} //发送心跳包,与发送数据包一样
func (client *TcpClient) sendHeartPacket() {
heartPacket := HeartPacket{
Version: "1.0",
Timestamp: time.Now().Unix(),
}
packetBytes, err := json.Marshal(heartPacket)
if err != nil {
fmt.Println(err.Error())
}
packet := Packet{
PacketType: HEART_BEAT_PACKET,
PacketContent: packetBytes,
}
sendBytes, err := json.Marshal(packet)
if err != nil {
fmt.Println(err.Error())
}
client.connection.Write(EnPackSendData(sendBytes))
fmt.Println("Send heartbeat data success!")
} //拿一串随机字符
func getRandString() string {
// length := rand.Intn(10)
strBytes := make([]byte, )
for i := ; i < ; i++ {
strBytes[i] = byte(rand.Intn() + )
}
return string(strBytes)
} /*作者:getyouyou
链接:https://www.jianshu.com/p/dbc62a879081
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/
server.go
package main import (
"bufio"
"encoding/json"
"fmt"
"hash/crc32"
"io"
"net"
"os"
) //数据包的类型
const (
HEART_BEAT_PACKET = 0x00
REPORT_PACKET = 0x01
) var (
server = "127.0.0.1:8080"
) //这里是包的结构体,其实是可以不需要的
type Packet struct {
PacketType byte
PacketContent []byte
} //注册
type RegisterReq struct {
PERAESKey string `json:"PERAESKey"`
VIN string `json:"VIN"`
T_Box_SN string `json:"T_Box_SN"`
IMSI string `json:"IMSI"`
rollNumber string `json:"rollNumber"`
} //心跳包,这里用了json来序列化,也可以用github上的gogo/protobuf包
//具体见(https://github.com/gogo/protobuf)
type HeartPacket struct {
Version string `json:"version"`
Timestamp int64 `json:"timestamp"`
} //正式上传的数据包
type ReportPacket struct {
Content string `json:"content"`
Rand int `json:"rand"`
Timestamp int64 `json:"timestamp"`
} //与服务器相关的资源都放在这里面
type TcpServer struct {
listener *net.TCPListener
hawkServer *net.TCPAddr
} func main() {
//类似于初始化套接字,绑定端口
hawkServer, err := net.ResolveTCPAddr("tcp", server)
checkErr(err)
//侦听
listen, err := net.ListenTCP("tcp", hawkServer)
checkErr(err)
//记得关闭
defer listen.Close()
tcpServer := &TcpServer{
listener: listen,
hawkServer: hawkServer,
}
fmt.Println("start server successful......")
//开始接收请求
for {
conn, err := tcpServer.listener.Accept()
fmt.Println("accept tcp client %s", conn.RemoteAddr().String())
checkErr(err)
// 每次建立一个连接就放到单独的协程内做处理
go Handle(conn)
}
} //处理函数,这是一个状态机
//根据数据包来做解析
//数据包的格式为|0xFF|0xFF|len(高)|len(低)|Data|CRC高16位|0xFF|0xFE
//其中len为data的长度,实际长度为len(高)*256+len(低)
//CRC为32位CRC,取了最高16位共2Bytes
//0xFF|0xFF和0xFF|0xFE类似于前导码
func Handle(conn net.Conn) {
// close connection before exit
defer conn.Close()
// 状态机状态
state := 0x00
// 数据包长度
length := uint16()
// crc校验和
crc16 := uint16()
var recvBuffer []byte
// 游标
cursor := uint16()
bufferReader := bufio.NewReader(conn)
//状态机处理数据
for {
recvByte, err := bufferReader.ReadByte() //recvByte是每次读到的字节
if err != nil {
//这里因为做了心跳,所以就没有加deadline时间,如果客户端断开连接
//这里ReadByte方法返回一个io.EOF的错误,具体可考虑文档
/*Handle方法在一个死循环中使用了一个无阻塞的buff来读取套接字中的数据,
因此当客户端主动关闭连接时,如果不对这个io.EOF进行处理,会导致这个goroutine空转,
疯狂吃cpu,在这里io.EOF的处理非常重要:)*/
if err == io.EOF {
fmt.Printf("client %s is close!\n", conn.RemoteAddr().String())
}
//在这里直接退出goroutine,关闭由defer操作完成
return
}
//进入状态机,根据不同的状态来处理
switch state {
case 0x00:
if recvByte == 0xFF {
state = 0x01
//初始化状态机
recvBuffer = nil
length =
crc16 =
} else {
state = 0x00
}
break
case 0x01:
if recvByte == 0xFF {
state = 0x02
} else {
state = 0x00
}
break
case 0x02:
length += uint16(recvByte) * //length这次是发送数据的长度
fmt.Println("0x02,length:%d", length) //
state = 0x03
break
case 0x03:
length += uint16(recvByte)
fmt.Println("0x03,length:%d", length) //77
// 一次申请缓存,初始化游标,准备读数据
recvBuffer = make([]byte, length)
cursor =
state = 0x04
break
case 0x04:
//不断地在这个状态下读数据,直到满足长度为止
recvBuffer[cursor] = recvByte
cursor++
if cursor == length {
state = 0x05
}
break
case 0x05:
crc16 += uint16(recvByte) * //crc32编码
state = 0x06
break
case 0x06:
crc16 += uint16(recvByte)
state = 0x07
break
case 0x07:
if recvByte == 0xFF {
state = 0x08
} else {
state = 0x00
}
case 0x08:
if recvByte == 0xFE {
//执行数据包校验
if (crc32.ChecksumIEEE(recvBuffer)>>)&0xFFFF == uint32(crc16) {
var packet RegisterReq
//把拿到的数据反序列化出来
json.Unmarshal(recvBuffer, &packet)
//新开协程处理数据
go processRecvData(&packet, conn)
} else {
fmt.Println("丢弃数据!")
}
}
//状态机归位,接收下一个包
state = 0x00
}
}
} //在这里处理收到的包,就和一般的逻辑一样了,根据类型进行不同的处理,因人而异
//我这里处理了心跳和一个上报数据包
//服务器往客户端的数据包很简单地以\n换行结束了,偷了一个懒:),正常情况下也可根据自己的协议来封装好
//然后在客户端写一个状态来处理
func processRecvData(packet *RegisterReq, conn net.Conn) {
// switch packet.PacketType {
// case HEART_BEAT_PACKET:
// var beatPacket HeartPacket
// json.Unmarshal(packet.PacketContent, &beatPacket)
fmt.Printf("recieve heat beat from [%s] ,data is [%v]\n", conn.RemoteAddr().String(), packet)
conn.Write([]byte("heartBeat\n"))
return
// case REPORT_PACKET:
// var reportPacket ReportPacket
// json.Unmarshal(packet.PacketContent, &reportPacket)
// fmt.Printf("recieve report data from [%s] ,data is [%v]\n", conn.RemoteAddr().String(), reportPacket)
// conn.Write([]byte("Report data has recive\n"))
// return
// }
} //处理错误,根据实际情况选择这样处理,还是在函数调之后不同的地方不同处理
func checkErr(err error) {
if err != nil {
fmt.Println(err)
os.Exit(-)
}
} /*作者:getyouyou
链接:https://www.jianshu.com/p/dbc62a879081
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。*/
go 客户端服务端通信的更多相关文章
- 基于Delphi实现客户端服务端通信Demo
在开始之前我们需要了解下这个Demo功能是啥 我们可以看到这是两个小project,左边的project有服务端和客户端1,右边的project只有一个客户端2 效果就是当两个客户端各自分别输入正确的 ...
- 客户端服务端通信protocol
这个协议不知我在上面耗费了多长时间,也有人问过我咋回事,这个protocol不长,但对于我来说理解起来很费劲,今天回来看看忽然看懂了(80%),只能说不知看了多少遍 其实这些东西应该在来的一个月这样子 ...
- MVC验证10-到底用哪种方式实现客户端服务端双重异步验证
原文:MVC验证10-到底用哪种方式实现客户端服务端双重异步验证 本篇将通过一个案例来体验使用MVC的Ajax.BeginForm或jQuery来实现异步提交,并在客户端和服务端双双获得验证.希望能梳 ...
- Netty入门之客户端与服务端通信(二)
Netty入门之客户端与服务端通信(二) 一.简介 在上一篇博文中笔者写了关于Netty入门级的Hello World程序.书接上回,本博文是关于客户端与服务端的通信,感觉也没什么好说的了,直接上代码 ...
- Android BLE与终端通信(三)——客户端与服务端通信过程以及实现数据通信
Android BLE与终端通信(三)--客户端与服务端通信过程以及实现数据通信 前面的终究只是小知识点,上不了台面,也只能算是起到一个科普的作用,而同步到实际的开发上去,今天就来延续前两篇实现蓝牙主 ...
- Eureka源码探索(一)-客户端服务端的启动和负载均衡
1. Eureka源码探索(一)-客户端服务端的启动和负载均衡 1.1. 服务端 1.1.1. 找起始点 目前唯一知道的,就是启动Eureka服务需要添加注解@EnableEurekaServer,但 ...
- winsock 编程(简单客户&服务端通信实现)
winsock 编程(简单客户&服务端通信实现) 双向通信:Client send message to Server, and if Server receive the message, ...
- 基于开源SuperSocket实现客户端和服务端通信项目实战
一.课程介绍 本期带给大家分享的是基于SuperSocket的项目实战,阿笨在实际工作中遇到的真实业务场景,请跟随阿笨的视角去如何实现打通B/S与C/S网络通讯,如果您对本期的<基于开源Supe ...
- winform 客户端采用HTTP协议与服务端通信
本来从来没有仔细研究过Http协议,今天因为公司业务需求,调试了半天,终于现在会Winform用Http协议与服务端通信了,其中常用的有POST和Get方式: 仔细看了人人网和新浪等大部分都是采用GE ...
随机推荐
- AC日记——小行星 洛谷 P2711
题目背景 pid=3437 题目描述 星云中有n颗行星,每颗行星的位置是(x,y,z).每次可以消除一个面(即x,y或z坐标相等)的行星,但是由于时间有限,求消除这些行星的最少次数. 输入输出格式 输 ...
- python笔记1:python基础
1.Python模块的标准文件模板: #!/usr/bin/env python #第1行注释可以让这个 .py 文件直接在Unix/Linux/Mac上运行 # -*- coding: utf-8 ...
- 简单的makefile模板
makefile不是总用到,每次用到的时候总要重新找资料,有点麻烦(怪自己基础知识不扎实,汗).留一个通用模板放这,方便以后使用 CC = gcc CXX = g++ LINK = g++ CFLAG ...
- Java并发编程实战 读书笔记(一)
最近在看多线程经典书籍Java并发变成实战,很多概念有疑惑,虽然工作中很少用到多线程,但觉得还是自己太弱了.加油.记一些随笔.下面简单介绍一下线程. 一 线程与进程 进程与线程的解释 个人觉 ...
- Unity -- 入门教程二
为了接下来要做的小游戏,在这里我要小小的修改一下移动的代码. public class PlayerMove : MonoBehaviour { //定义移动的速度 public float Move ...
- mac安装.net core
https://www.microsoft.com/net/core#macos Install for macOS 10.11 or higher (64 bit) 1 Install pre-re ...
- 【转载】读懂IL代码就这么简单(三)完结篇
一 前言 写了两篇关于IL指令相关的文章,分别把值类型与引用类型在 堆与栈上的操作区别详细的写了一遍这第三篇也是最后一篇,之所以到第三篇就结束了,是因为以我现在的层次,能理解到的都写完了,而且个人认为 ...
- C#设计模式总结 C#设计模式(22)——访问者模式(Vistor Pattern) C#设计模式总结 .NET Core launch.json 简介 利用Bootstrap Paginator插件和knockout.js完成分页功能 图片在线裁剪和图片上传总结 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi
C#设计模式总结 一. 设计原则 使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性.并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则.开放封闭原则.里氏代替 ...
- sql time 比较
数据字段为varchar类型的,格式:20110228 151010想进行时间比较,搜索一个范围内的时间select * from table where ' 20120102' <`time ...
- 系统安全-Man in the middleattack
窃听VS加密(解决数据机密性) 加密由两部分组成:算法&秘钥(算法要够复杂,秘钥要够安全) 对称加密:(Symmetric encryption) 采用单秘钥密码系统的加密方法,同一个秘钥可以 ...