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 ...
随机推荐
- jQuery时间验证和转换为标准格式的时间
var TimeObjectUtil; /** * @title 时间工具类 * @note 本类一律违规验证返回false * @author {boonyachengdu@gmail.com} * ...
- NGINX白名单功能,ngx_http_limit_conn_module和ngx_http_limit_req_module值设置多少才合适呀?
要根据不同的应用慢慢学习测试? 我现在设置的10左右,看看再说吧... #增加限制规则,如果不能正常访问,则需要调节这两个值 -- #增加ip白名单功能 geo $whiteiplist { defa ...
- AC日记——太空飞行计划 洛谷 P2762
题目背景 题目描述 W 教授正在为国家航天中心计划一系列的太空飞行.每次太空飞行可进行一系列商业性实验而获取利润.现已确定了一个可供选择的实验集合E={E1,E2,…,Em},和进行这些实验需要使用的 ...
- IE使用多彩文档上传数据库报错
使用多彩文档,用IE浏览器提交表单,双引号里面包含单引号,导致数据库插入不了,而用chrome浏览器不会报错,自动过滤单引号, 解决:content.replace("'", &q ...
- Linux下批量替换文件内容和文件名(转)
1.批量替换指定多个文件的文件内容 在指定目录/your/path里,查找包含old_string字符串的所有文件,并用new_string字符串替换old_string字符串. sed -i &qu ...
- js转换金额为中文大写
function changeMoneyToChinese(money){ var cnNums = new Array("零","壹","贰&quo ...
- Android 源码编译记录
问题1:Can't locate Switch.pm in @INC (you may need to install the Switch module) (@INC contains: /etc/ ...
- cocos2d-x-3.x bringToFront & sendToBack实现
void Node::bringToFront(void) { auto parent = this->getParent(); if (parent != nullptr && ...
- word中更改图片和标题之间的垂直距离
word中插入图片后.往往须要给图片加上标题. 你插入图片和给图片插入标题时,word用的是默认的格式给你插入的图片和标题. 假如原来的paragraph是2倍行距.你的图片和标题之间的距离也是2倍行 ...
- mysql freeing items 状态
http://blog.sina.com.cn/s/blog_6128a8f00100wsdd.html数据库出现大量的freeing items状态 表更新慢 而且大量锁表查看mysql官方free ...