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 ...
随机推荐
- Codeforces Gym101502 J-取数博弈
还有J题,J题自己并不是,套的板子,大家写的都一样,因为大家都是套板子过的,贴一下代码,等学会了写一篇博客... J.Boxes Game 代码: 1 //J. Boxes Game-取数博弈-不会, ...
- sort、dirname、添加环境变量、修改主机名、别名IP、静态路由
1.split-按照指定行数或大小分割文件 -l:指定行数 -a:指定文件后缀长度 -d:使用数字作为后缀 -b:指定大小 # 以10行为单位对文件进行分割 split -l 10 /etc/init ...
- UvaLive 4287 Proving Equivalences 强连通缩点
原题链接:https://icpcarchive.ecs.baylor.edu/index.php?option=com_onlinejudge&Itemid=8&page=show_ ...
- servlet跳转页面后图片不显示
我是用图片的相对路径,原先直接打开jsp的话图片是可以正常显示的,通过servlet跳转之后图片就显示不出来了 后来发现是图片路径的问题, 我是将图片放在WebRoot里面自己创建的img中,原先图片 ...
- JavaSwing仿QQ登录界面,注释完善,适合新手学习
使用说明: 这是一个java做的仿制QQ登录界面,界面仅使用一个类, JDK版本为jdk-11 素材包的名字为:素材(下载)请在项目中新建一个名字为“素材”的文件夹. 素材: https://pan. ...
- Akka简介与Actor模型
Akka是一个构建在JVM上,基于Actor模型的的并发框架,为构建伸缩性强,有弹性的响应式并发应用提高更好的平台.本文主要是个人对Akka的学习和应用中的一些理解. Actor模型 Akka的核心就 ...
- 计算广告、推荐系统论文以及DSP综述
http://www.huxmarket.com/detail/2966 DSP场景假定前提: 以CTR预估为例,向广告主以CPC(OCPC)方式收费,向ADX以CPM方式付费.投放计划受预算限制,在 ...
- Android 适配器教程 (六)
我们的适配器学习已经接近尾声了.尽管这不是一个大问题,可是确实是值得学习的一块知识,回忆一下之前五讲的知识.我们已经学到了非常多东西了. 在之前五讲中.我们已经由浅入深的认识了适配器,从最简单的Lis ...
- 【每日Scrum】第二天(4.12) TD学生助手Sprint1站立会议
TD学生助手Sprint1站立会议(4.12) 任务看板 站立会议内容 组员 昨天 今天 困难 签到 刘铸辉 (组长) 做了几个Sqlite编辑事件导入数据库没成功,就编辑图片滑动显示功能 今天学习了 ...
- android中的MD5、Base64、DES/3DES/ADES加解密
MD5摘要算法: <span style="font-size:18px;">主要代码: String s = edit.getText().toString(); i ...