关闭网络连接步骤:
1. 直接关闭
2. 赋值 nil
对与网络连接相关的操作(客户端网络连接、服务器网络连接)都应该: 加锁、解锁
```bash
connect *net.Conn // TCP网络连接
connectMutex sync.RWMutex // TCP网络连接读写锁
使用时:
connectMutex.RLock()
defer connectMutex.RUnlock()
```
协程的使用:
```bash
for {
select {
case <-c.ctx.Done():
// 增加退出协程相关操作
return
default:
}
// 增加逻辑代码
// 匿名函数的使用
func(){
// ...
}()
}
```
- 注意逻辑代码和业务代码的分层处理,可以抽象的尽量抽象。
不要把底层数据直接暴露给业务代码,采用中间变量进行抽象封装
- 回调函数的使用:
1. 声明一个回调函数: type HandleCommandCallback func([]byte) error
2. 在函数中直接调用回调函数(参数、返回值要一直)
3. 在业务层具体实现回调函数的功能
- 接口的使用:
1. 声明一个接口(含有多个方法)
2. 结构体地址赋值给接口变量
3. 在结构体变量中实现接口的所有方法 func (s *structA) 接口方法 {}
## 服务器+客户端
客户端服务器代码理解:
服务器接口 + 服务器接口实例
1. 创建一个服务器实例
a. 定义服务器结构体
b. 初始化服务器结构体
c. 初始化服务器实例,初始化服务器实例时开启监听客户端的协程(新增客户端,创建客户端实例)
2. 客户端实例化
a. 定义客户端结构体
b. 初始化客户端结构体
c. 初始化客户端实例(在init()中进行命令和函数的对应注册)
- 运行过程
1. 在NewDataServer()中进行服务器初始化 server.init()
2. 在服务器初始化 server.init()中进行客户端接入监听 go s.accept()
3. 在客户端接入监听中增加客户端 s.addClient(connect)
4. 增加客户端函数中增加数据客户端 NewDataClient(...)
5. NewDataClient(...)中进行客户端初始化 client.init()
6. 客户端初始化 client.init()中进行 “命令-函数” 注册,以及开启消息接收 go c.receive()
- 在服务器Receive()函数中,通过协程来调用客户端的Receive()
go func(client DataClient, contentChan chan CommandContent, errChan chan error) {
clientContent, err := client.Receive(timeoutCtx)
...
}(client, s.receiveClientChannel, errChan)
--------------------------------------------
初始化全局变量通道的写法???
var a sync.Once
a.DO()...
2. 服务器接收时,进行Receive(),接收到的是客户端利用通道传递过来的数据(或者错误信息)
map的高级用法:
判断一个map[i]值是否存在?
var abc map[int]string
a, b := abc[2]
fmt.Printf("a:%v \n", a)
fmt.Printf("b:%v \n", b)
输出结果:
a:
b:false
通过判断b值可以判断map[i]的值是否存在
----------------------------------
for {
select {
case <-c.ctx.Done(): // 客户端提前退出情况
return content, fmt.Errorf("客户端提前退出")
case <-timeoutCtx.Done(): // 定时时间到情况(一般会有一个定时变量:timeoutCtx context.Context,赋值2~4秒)
return content, ErrReceiveTimeout
case content = <-c.receiveCommandChannel: // 接收到通道数据情况(没有接收到,会一直阻塞,所以需要for循环)
return content, nil
default:
// TODO: 记录信息等待图片超时处理
time.Sleep(20 * time.Millisecond) // 每隔20毫秒执行一次for循环
}
}
带超时控制:
1. ctx context.Context
2. timeoutCtx, timeoutCancel := context.WithTimeout(ctx, captureImageMaxWaitTime)
defer timeoutCancel()
3. timeoutCtx.Done()
--------------------------
网络正常错误???
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return
}
------------------------
如何关闭协程?
<https://www.jianshu.com/p/79d27f200bcf>
<https://blog.csdn.net/m0_37579159/article/details/79257397>
1. 当协程数量较少时:
在协程函数(go fun()) func()中,加入 for-select 函数
(在协程函数中加入for-select格式是为了及时的关闭协程)
2. 当协程数量较多时,使用WaitGroup
---------------------------
##多包数据解析:
1. 根据索引值i,遍历查找包头(注意包头的判定条件),找到了包头,将数据开始(dataStartIndex)和数据结束(dataEndIndex)位置标识出来。
2. 判断包中的命令是否是多包指令?
- 不是多包指令
- 直接存储相关数据
将数据内容 dataEndIndex-dataStartIndex 保存到 data[:] 中,
将首包找到的位置置位
将索引 i 值 添加这个包的长度( i = i + 包头+包体+包尾)
- 是多包指令:
- 找多包尾(注意多包尾判断条件)
- 找到了多包尾:
解析完整的多包数据(函数调用),解析完成,将索引值 i 添加整个多包数据长度,接着继续查找
- 没有找到多包尾
直接将索引值 i++,继续查找
-----
## 常用软件
ThinkCamWorkstation
secureCRT
FileZilla
------
从二进制流中获取数据:
content []byte
var header CommandHeader
headerReader := bytes.NewReader(content[0:binary.Size(header)])
err := binary.Read(headerReader, binary.BigEndian, &header)
if err != nil {
return headerInfo, fmt.Errorf("解析命令信息头二进制数据失败(%s)", err.Error())
}
写数据到二进制流中:
强制将二进制数据写入:
header.DataLength = uint16(binary.BigEndian.Uint16(content[i+10 : i+12]))
var responseContent = new(bytes.Buffer)
// 参考蓝卡通信协议文档,计算机收到完整的识别结果文件数据包后反馈"流水号+128"
binary.Write(responseContent, binary.LittleEndian, packet.SequenceNumber+128)
response, err := BuildCommandContent(RecognitionResultFile, packet.DeviceID, responseContent.Bytes())
if err != nil {
return fmt.Errorf("构建命令反馈指令内容失败(%s)", err.Error())
}
-----
单例模式
var captureImageBuffer map[string]chan bool // 抓拍图片缓存
var initCaptureImageBuffer sync.Once // 初始化抓拍缓存
initCaptureImageBuffer.Do(func() {
captureImageBuffer = make(map[string]chan bool) // 创建抓拍图片缓存
})
-------
map + 通道的用法
------
### 类型断言
类型断言的使用?
[go语言的类型断言(Type Assertion)](https://www.jianshu.com/p/6a46fc7b6e5b)
格式: x.(T) 检查x的动态类型是否是T,其中x必须是接口值。
1. T是具体类型
2. T是接口类型
----
// 忽略正常的读取超时错误
if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
return nil
}
//上面代码功能:判断err是否是net.Error接口类型中的Timeout()类型
[go语言的错误处理模式](https://ethancai.github.io/2017/12/29/Error-Handling-in-Go/)
---
// 设置读取超时时间
err := s.connect.SetReadDeadline(time.Now().Add(200 * time.Millisecond))
if err != nil {
logger.Error("设置设备搜索反馈超时时间失败(%s)", err.Error())
return nil
}
---
代码质量的评判标准:
“高内聚、低耦合”是衡量公共库质量的一个重要方面,如果某种代码实现方式,增加了公共库和调用代码的耦合性,让模块之间产生了依赖,则比较糟糕
---
-----
// 几个常用 golang 包
1. net
2. strings
3. strconv
4. encode/json
5. convert
-------
[Go的json解析:Marshal与Unmarshal](https://www.jianshu.com/p/3534532e06ed)
-------
go 语言自定义错误的用法 ???
go 语言高级用法???
----
为什么要传结构体指针变量,不传结构体变量???
理由: 两者实现的效果一致,但是指针更快,且能节省内存空间,所以能传结构体变量的地方,尽量用结构体指针变量代替
------
### 概念理解
什么是穿透???
什么是实时透传?
有疑问加站长微信联系(非本文作者)