当前位置: 首页 > news >正文

GO网络编程(四):海量用户通信系统2:登录功能核心【重难点】

目录

    • 一、C/S详细通信流程图
    • 二、消息类型定义与json标签
      • 1. 消息类型定义
      • 2. JSON标签
      • 3.结构体示例及其 JSON 表示:
      • 4.完整代码与使用说明
    • 三、客户端发送消息
      • 1. 连接到服务器
      • 2. 准备发送消息
      • 3. 创建 LoginMes 并序列化
      • 4. 将序列化后的数据嵌入消息结构
      • 5. 序列化整个 Message 结构体
      • 6. 发送消息长度
      • 7. 发送消息内容与完整代码
      • 8.细节解释与总结
    • 四、服务端处理消息
      • 1. 创建缓冲区
      • 2. 循环读取客户端发送的数据
      • 3. 读取前 4 个字节
      • 4. 并发处理
      • 5. 总结与完整代码
    • 五、测试与注意事项
      • 1.取消安全提示
      • 2.命令行中的ctrl+c
      • 3.执行.exe与测试效果

一、C/S详细通信流程图

上一讲我们实现了最基本的登录功能,其实根本没涉及网络编程,这节开始才是网络编程,而且复杂,重要,有一定难度。
C/S通信可用以下两张图表示:
在这里插入图片描述
在这里插入图片描述
这节不涉及全部流程,但涉及三块重要的内容:类型定义与json标签,客户端发送消息,服务端处理消息。

二、消息类型定义与json标签

网络编程中通过消息(Message)结构体传递和处理不同类型的数据,例如登录请求和登录响应。通常,这些类型和消息结构体用于客户端与服务器之间的通信,消息被序列化为 JSON 格式,然后通过网络传输。在服务器端或客户端接收到消息后,会对其进行反序列化,以便处理不同类型的消息。
首先在根目录下建立message包,即建立文件夹message,在其中建立文件message.go,然后定义消息结构体类型:

package messageconst (LoginMesType    = "LoginMes"LoginResMesType = "LoginResMes"
)type Message struct {Type string `json:"type"` //消息类型Data string `json:"data"` //消息
}

1. 消息类型定义

消息封装:消息传输时,包含不同类型的信息(登录、登出、注册等),需要通过一个统一的结构来表示这些消息。这就是 Message 结构体的用途。
消息分类:通过 Message 中的 Type 字段,可以区分消息的类型,例如是登录消息(LoginMes)还是登录响应消息(LoginResMes),这样服务器和客户端可以根据 Type 做出不同的处理。
扩展性:未来如果需要添加更多的消息类型(例如注册、注销等),可以很方便地在已有的结构上扩展新的类型,而不需要重写消息传递的逻辑。

2. JSON标签

JSON 标签用于指定在序列化(将 Go 的结构体转换为 JSON 格式)和反序列化(将 JSON 格式的数据转换为 Go 结构体)时,结构体字段对应的 JSON 字段名。Go 中的字段名通常是驼峰命名,而 JSON 中的字段名习惯上是小写或下划线分隔,通过 json:“字段名” 来指定这个映射。
具体例子如下:
Type string \json:"type":在消息序列化为 JSON 时,这个字段会被转换为 “type”。例如,如果 Type 是 “LoginMes”,JSON 结果会包含:“type”: “LoginMes”。
Data string \json:"data":这个字段包含实际的消息数据,在 JSON 中也会被表示为 “data”。例如:
"data": "{\"userID\":123,\"userPwd\":\"password\"}",也就是具体的消息数据会序列化为字符串。

3.结构体示例及其 JSON 表示:

1.Message 结构体:

type Message struct {Type string `json:"type"` // 消息类型Data string `json:"data"` // 消息数据
}

JSON 格式:

{"type": "LoginMes","data": "{\"userID\":123,\"userPwd\":\"password\",\"userName\":\"John\"}"
}

2.LoginMes 结构体:

type LoginMes struct {UserID   int    `json:"userID"`   // 用户IDUserPwd  string `json:"userPwd"`  // 用户密码UserName string `json:"userName"` // 用户名
}

JSON 格式:

{"userID": 123,"userPwd": "password","userName": "John"
}

3.LoginResMes 结构体:

type LoginResMes struct {Code  int    `json:"code"`  // 状态码Error string `json:"error"` // 错误信息
}
{"code": 200,"error": ""
}

4.完整代码与使用说明

message.go的完整代码如下:

package messageconst (LoginMesType    = "LoginMes"LoginResMesType = "LoginResMes"
)type Message struct {Type string `json:"type"` //消息类型Data string `json:"data"` //消息
}// 定义两个消息..后面需要再增加
type LoginMes struct {UserID   int    `json:"userID"`   //用户idUserPwd  string `json:"userPwd"`  //用户密码UserName string `json:"userName"` //用户名
}type LoginResMes struct {Code  int    `json:"code"`  //返回状态码 500表示该用户未注册 200表示登录成功Error string `json:"error"` //返回错误信息
}

定义这些类型是为了处理客户端和服务器之间的消息传递,不同类型的消息可以通过 Message 结构体中的 Type 来识别。JSON 标签用于控制 Go 结构体字段与 JSON 字段的映射,使得消息在序列化和反序列化时能够正确转换。
在通信时,客户端发送登录请求,会将 LoginMes 转换为 JSON 字符串,并将其存放在 Message 结构体的 Data 字段中。服务器收到后,会先根据 Message 的 Type 来判断消息类型(例如 LoginMes),然后再将 Data 字符串反序列化为 LoginMes 结构体,并进行处理。服务器处理完登录请求后,可以通过 LoginResMes 结构体将结果返回给客户端。

三、客户端发送消息

接下来我们转到client包,完善login代码。
在登录操作中,客户端与服务器通过TCP协议通信,发送的是一个序列化后的消息。这是一个典型的网络编程操作流程,具体包括连接服务器、序列化数据、发送消息等步骤。

1. 连接到服务器

conn, err := net.Dial("tcp", "localhost:8889")
if err != nil {fmt.Println("net.Dial err=", err)return err
}
defer conn.Close()

这里使用 net.Dial 函数建立一个 TCP 连接,目标地址是localhost:8889,即本地的 8889 端口。
conn 是代表连接的对象,通过这个连接对象,客户端可以向服务器发送和接收数据。
defer conn.Close() 表示函数结束时自动关闭连接,避免资源泄漏。

2. 准备发送消息

var mes message.Message
mes.Type = message.LoginMesType

这里创建了一个 Message 结构体变量 mes,用于封装将要发送的消息。mes.Type 被设置为 message.LoginMesType,这表示消息类型是登录消息(LoginMes),后续服务器会根据消息类型来识别消息的具体用途。

3. 创建 LoginMes 并序列化

var loginMes message.LoginMes
loginMes.UserID = userID
loginMes.UserPwd = userPwd
data, err := json.Marshal(loginMes)
if err != nil {fmt.Println("json.Marshal err=", err)return err
}

loginMes 是一个 LoginMes 结构体,包含用户ID和用户密码的信息。
使用 json.Marshal 将 loginMes 转换为 JSON 字符串,便于传输。这是因为网络通信通常使用文本格式(如 JSON)来传输复杂数据结构。

4. 将序列化后的数据嵌入消息结构

mes.Data = string(data)

这里将序列化后的 loginMes 作为 JSON 字符串赋值给 mes.Data,即将登录信息封装到消息结构体 mes 中的 Data 字段。

5. 序列化整个 Message 结构体

data, err = json.Marshal(mes)
if err != nil {fmt.Println("json.Marshal err=", err)return err
}

再次使用 json.Marshal 将整个 Message 结构体序列化为 JSON 字符串,准备发送给服务器。这是因为 Message 结构体不仅包含数据,还包含类型信息。

6. 发送消息长度

var pkgLen = uint32(len(data))
var buf [4]byte
binary.BigEndian.PutUint32(buf[0:4], pkgLen)
n, err := conn.Write(buf[:])
if n != 4 || err != nil {fmt.Println("conn.Write(bytes[:]) fail err=", err)return err
}

网络通信中,为了保证数据的完整性,常常需要先发送消息的长度,这里通过 len(data) 获取序列化后的消息的长度,并通过 binary.BigEndian.PutUint32 函数将消息长度(pkgLen)转换为 4 个字节(大端序),存放在 buf 中。最后使用 conn.Write(buf[:]) 将消息长度发送给服务器。

7. 发送消息内容与完整代码

接下来会通过 conn.Write(data) 把实际的消息数据发送给服务器,这个操作将在下节讲。
login函数的完整代码如下:

func login(userID int, userPwd string) error {//下一个就要开始定协议// fmt.Printf("userId=%d pwd=%s\n", userId, pwd)// return nil//1.连接到服务器conn, err := net.Dial("tcp", "localhost:8889")if err != nil {fmt.Println("net.Dial err=", err)return err}//延时关闭defer conn.Close()//2.准备通过conn发送消息给服务器var mes message.Messagemes.Type = message.LoginMesType//3.创建一个LoginMes 结构体var loginMes message.LoginMesloginMes.UserID = userIDloginMes.UserPwd = userPwd//4.将loginMes 序列化data, err := json.Marshal(loginMes)if err != nil {fmt.Println("json.Marshal err=", err)return err}//5.把data赋给mes.Data字段mes.Data = string(data)//6.将mes进行序列化data, err = json.Marshal(mes)if err != nil {fmt.Println("json.Marshal err=", err)return err}//7.到这个时候,data就是我们要发送的消息//7.1 先把data的长度发送给服务器// 先获取到data的长度->转成一个表示长度的byte切片var pkgLen = uint32(len(data))var buf [4]bytebinary.BigEndian.PutUint32(buf[0:4], pkgLen)//发送长度n, err := conn.Write(buf[:4])if n != 4 || err != nil {fmt.Println("conn.Write(bytes[:]) fail err=", err)return err}fmt.Printf("客户端,发送消息的长度=%d\n内容:%s\n", len(data), string(data))return nil
}

8.细节解释与总结

(1)为什么使用大端序
在网络传输中,为了保证跨平台数据的一致性,通常会采用大端序(Big Endian)来传输数据。因为大端序的字节排列方式与人类阅读数字的顺序一致。高位字节在前,低位字节在后,和我们书写数值的方式相同。
举个例子,假设 pkgLen = 123456 (十六进制为 0x0001E240),则 binary.BigEndian.PutUint32(buf[0:4], pkgLen) 将执行以下操作:
buf[0] = 0x00
buf[1] = 0x01
buf[2] = 0xE2
buf[3] = 0x40
最终 buf[0:4] 就包含了 pkgLen 的大端序表示。
(2)为什么需要发送长度
在网络通信中,消息的长度是动态的,接收方通常不知道需要读取多少数据。如果直接发送数据,接收方可能无法正确判断消息的结束。
因此,发送方先发送一个固定长度的消息(如4个字节的消息长度),让接收方根据这个长度来接收完整的消息。
(3)客户端发消息其实就为四步
1.连接服务器
2.初始化并序列化消息变种
3.用消息变种初始化并序列化消息
4.发送消息的长度与内容

四、服务端处理消息

在server包下创建main.go,自定义process函数,其核心内容如下:

1. 创建缓冲区

buf := make([]byte, 1024*4) //准备缓冲区以读取数据

这里使用 make 函数创建了一个大小为 4KB(1024 * 4 字节)的缓冲区 buf,用于存放从客户端接收到的数据。

2. 循环读取客户端发送的数据

for {fmt.Println("读取客户端发送的数据")//...
}

使用 for 循环,持续监听并读取来自客户端的数据,直到出现错误或者连接断开。循环不断地执行读取操作,保证服务器可以持续处理客户端发送的消息。

3. 读取前 4 个字节

n, err := conn.Read(buf[:4])
if n != 4 || err != nil {fmt.Println("conn.Read err=", err)return
}

conn.Read(buf[:4]):从连接 conn 中读取最多 4 个字节,读取的数据存储在 buf 数组的前 4 个字节位置。
返回值 n:表示读取了多少个字节。理想情况下,n 应该为 4,表示完整读取了 4 个字节的数据。
接下来要判断连接是否正常:如果读取的字节数不是 4,即读取不完整,或发生了错误,则输出错误信息并退出 process 函数,终止该连接的处理。

4. 并发处理

在第二节,我讲了客户端与服务端的基本通信操作,在服务端,每个客户端连接都通过新的协程来处理,这使得服务器能够同时与多个客户端进行通信,而不会因为一个客户端的阻塞操作(如读取数据)而影响其他客户端。其中listen.Accept()是关键操作,它的作用是阻塞主协程并等待客户端连接,连接成功后返回一个用于与客户端通信的conn对象

5. 总结与完整代码

server包下的main.go要实现一个简单的并发 TCP 服务器,即
监听 TCP 连接,并发处理,错误处理,以及资源释放,即通过 defer 机制,服务器会在程序结束时正确释放资源(关闭监听器)。完整代码如下:

package mainimport ("fmt""net"
)// 处理和客户端的通信
func process(conn net.Conn) {//这里需要延时关闭conndefer conn.Close()buf := make([]byte, 1024*4) //准备缓冲区以读取数据//循环读取客户端发送的信息for {fmt.Println("读取客户端发送的数据")n, err := conn.Read(buf[:4])if n != 4 || err != nil {fmt.Println("conn.Read err=", err)return}fmt.Println("读到的buf=", buf[:4])}
}
func main() {//提示信息fmt.Println("服务器在8889端口监听")listen, err := net.Listen("tcp", "0.0.0.0:8889")if err != nil {fmt.Println("net.Listen err=", err)return}defer listen.Close()//一旦监听成功,就等待客户端来连接服务器for {fmt.Println("等待客户端来连接服务器......")conn, err := listen.Accept()if err != nil {fmt.Println("listen.Accept err=", err)}//一旦连接成功,则启动一个协程和客户端保持通信go process(conn)}
}

五、测试与注意事项

1.取消安全提示

如果是windows操作系统,在执行server包的程序时,可能会弹出“是否允许公共网络访问”这样的提示,最直接的办法就是关闭防火墙,当然如果你网络编程的时间较长并且一直在上网,这是有风险的。还有种就是将server包下的服务器程序,比如server.exe加入信任列表。当然,前提是编译server包,即在项目根目录下执行如下命令:

go build ./server

然后在win底部菜单栏中(win10/11适用)输入“允许应用通过”,此时会弹出下图:
在这里插入图片描述
点击进入设置界面,再按下图操作:
在这里插入图片描述
然后浏览并添加即可。

2.命令行中的ctrl+c

这里的ctrl+c可不是复制,而是用于命令行中结束程序,一般的命令行比如windows cmd,一旦开启server.exe,只能通过右上角的关闭按钮结束,但如果用powershell执行程序,则用ctrl+c就能结束,这样就能保留命令行记录与操作(按上方向键能查看旧命令)。注意:如果你用的IDE是vscode,则内置终端默认就是powershell,而且可以启动多个终端并分屏查看。至于快捷键调出的外部命令行是无法使用ctrl+c的。

3.执行.exe与测试效果

打开两个powershell命令行,在当前根目录下执行两个编译好的程序client.exe和server.exe:

./server.exe
./client.exe

效果如图:
在这里插入图片描述

OK,以上就是登录模块的核心功能了,这也是网络编程的基础与重点,可以说,不掌握本节内容,是无法入门的。本节内容多,不简单,读者需要多次阅读和编程才能掌握。

相关文章:

GO网络编程(四):海量用户通信系统2:登录功能核心【重难点】

目录 一、C/S详细通信流程图二、消息类型定义与json标签1. 消息类型定义2. JSON标签3.结构体示例及其 JSON 表示:4.完整代码与使用说明 三、客户端发送消息1. 连接到服务器2. 准备发送消息3. 创建 LoginMes 并序列化4. 将序列化后的数据嵌入消息结构5. 序列化整个 M…...

某项目实战分析代码二

某项目实战分析代码二 此次分析的是protobuf的使用操作流程具体实现 3. 业务数据分析3.1 客户端3.2 服务器端简单案例 此次分析的是protobuf的使用 Protocol Buffer( 简称 Protobuf) 是Google公司内部的混合语言数据标准,它是一种轻便高效的结构化数据存储格式&…...

全面指南:探索并实施解决Windows系统中“mfc140u.dll丢失”的解决方法

当你的电脑出现mfc140u.dll丢失的问题是什么情况呢?mfc140u.dll文件依赖了什么?mfc140u.dll丢失会导致电脑出现什么情况?今天这篇文章就和大家聊聊mfc140u.dll丢失的解决办法。希望能够有效的帮助你解决这问题。 哪些程序依赖mfc140u.dll文件…...

QT学习笔记1(QT和QT creator介绍)

QT学习笔记1(QT和QT creator介绍) Qt 是一个跨平台的应用开发框架,主要用于图形用户界面(GUI)应用的开发,但也支持非GUI程序的开发。Qt 支持多种平台,如Windows、macOS、Linux、iOS和Android&a…...

存储电话号码的数据类型,用 int 还是用 string?

在 Java 编程中,存储电话号码的选择可以通过两种常见方式进行:使用 int 类型或 String 类型。这种选择看似简单,但实际上涉及到 JVM 内部的字节码实现、内存优化、数据表示、以及潜在的可扩展性问题。 Java 基本数据类型与引用数据类型的差异…...

【目标检测】工程机械车辆数据集2690张4类VOC+YOLO格式

数据集格式:Pascal VOC格式YOLO格式(不包含分割路径的txt文件,仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数):2694 标注数量(xml文件个数):2694 标注数量(txt文件个数):2694 标注…...

target_link_libraries()

target_link_libraries() 是 CMake 中的一个命令,用于指定目标(如可执行文件或库)所依赖的其他库。其主要作用包括: 链接库:将指定的库链接到目标上,使目标能够调用这些库中的函数和使用其功能。 管理依赖…...

Javascript数组研究09_Array.prototype[Symbol.unscopables]

Symbol.unscopables 是 JavaScript 中一个相对较新的符号(Symbol),用于控制对象属性在 with 语句中的可见性。它主要用于内置对象,如 Array.prototype,以防止某些方法被引入到 with 语句的作用域中,避免潜在…...

SkyWalking 自定义链路追踪

对项目中的业务方法&#xff0c;实现链路追踪&#xff0c;方便我们排查问题 引入依赖 <!‐‐ SkyWalking 工具类 ‐‐> <dependency> <groupId>org.apache.skywalking</groupId> <artifactId>apm‐toolkit‐trace</artifactId> <vers…...

Linux驱动开发(速记版)--设备模型

第八十章 设备模型基本框架-kobject 和 kset 80.1 什么是设备模型 设备模型使Linux内核处理复杂设备更高效。 字符设备驱动适用于简单设备&#xff0c;但对于电源管理和热插拔&#xff0c;不够灵活。 设备模型允许开发人员以高级方式描述硬件及关系&#xff0c;提供API处理设备…...

动手学深度学习(李沐)PyTorch 第 6 章 卷积神经网络

李宏毅-卷积神经网络CNN 如果使用全连接层&#xff1a;第一层的weight就有3*10^7个 观察 1&#xff1a;检测模式不需要整张图像 很多重要的pattern只要看小范围即可 简化1&#xff1a;感受野 根据观察1 可以做第1个简化&#xff0c;卷积神经网络会设定一个区域&#xff0c…...

新编英语语法教程

新编英语语法教程 1. 新编英语语法教程 (第 6 版) 学生用书1.1. 目录1.2. 电子课件 References A New English Grammar Coursebook 新编英语语法教程 (第 6 版) 学生用书新编英语语法教程 (第 6 版) 教师用书 1. 新编英语语法教程 (第 6 版) 学生用书 https://erp.sflep.cn/…...

Golang 服务器虚拟化应用案例

推荐学习文档 golang应用级os框架&#xff0c;欢迎stargolang应用级os框架使用案例&#xff0c;欢迎star案例&#xff1a;基于golang开发的一款超有个性的旅游计划app经历golang实战大纲golang优秀开发常用开源库汇总想学习更多golang知识&#xff0c;这里有免费的golang学习笔…...

Elasticsearch基础_4.ES搜索功能

文章目录 一、搜索辅助功能1.1、指定返回的字段1.2、结果计数1.3、结果分页 二、搜索匹配功能2.1、查询所有文档2.2、term级别查询2.2.1、term查询2.2.2、terms查询2.2.3、range查询2.2.4、exists查询 2.3、布尔查询2.3.1、must&#xff0c;should&#xff0c;must_not2.3.2、f…...

Elasticsearch要点简记

Elasticsearch要点简记 1、ES概述2、基础概念&#xff08;1&#xff09;索引、文档、字段&#xff08;2&#xff09;映射&#xff08;3&#xff09;DSL 3、架构原理4、索引字段的数据类型5、ES的三种分页方式&#xff08;1&#xff09;深度分页&#xff08;fromsize&#xff09…...

【通信协议】IIC通信协议详解

IIC&#xff08;Inter-Integrated Circuit&#xff09;通信协议&#xff0c;又称为I2C&#xff08;Inter-Integrated Circuit 2&#xff09;协议&#xff0c;是一种广泛使用的串行通信协议。它由Philips Semiconductor&#xff08;现NXP Semiconductors&#xff09;开发&#x…...

2024年中国科技核心期刊目录(社会科学卷)

2024年中国科技核心期刊目录 &#xff08;社会科学卷&#xff09; 序号 期刊代码 期刊名称 1 SC02 JOURNAL OF S…...

用Python集成免费IP归属地查询API

IP查询的优势是什么&#xff1f; IP查询是一种强大的工具&#xff0c;能够快速提供关于IP地址的信息&#xff0c;如地理位置、互联网服务提供商&#xff08;ISP&#xff09;、连接类型等。这些数据在多种场景下都非常有用&#xff0c;帮助用户理解网络环境和用户行为。 首先&…...

C 数组

C 数组 数组是C语言中的一种基本数据结构&#xff0c;用于存储一系列相同类型的数据。它是连续的内存分配&#xff0c;允许通过索引快速访问元素。本文将详细介绍C数组的概念、使用方法、以及注意事项。 1. 数组的概念 数组是一个集合&#xff0c;可以存储一定数量的元素。在…...

【Unity】unity安卓打包参数(个人复习向/有不足之处欢迎指出/侵删)

1.Texture Compression 纹理压缩 设置发布后的纹理压缩格式 Use Player Settings:使用在播放器设置中设置的纹理压缩格式 ETC&#xff1a;使用ETC格式&#xff08;兼容&#xff09; ETC2&#xff1a;使用ETC2格式&#xff08;很多设备不支持&#xff09; ASTC&#xff1a;使用…...

C0016.Clion中qDebug()打印输出中文时,都是问号??????的解决办法

问题描述 在clion中使用qDebug打印输出中文内容时&#xff0c;都是&#xff1f;&#xff1f;&#xff1f;&#xff1f;&#xff1f;如下图&#xff1a; 注意&#xff1a;修改该文件的编码格式就行&#xff0c;该文件名为apr.cpp&#xff1b; 解决办法...

C++ priority_queue 优先队列构造大根堆和小根堆

priority_queue的三个参数 template <class T, class Container std::vector<T>, class Compare std::less<typename Container::value_type>> class priority_queue;1、元素类型 2、底层容器类型&#xff0c;默认vector 3、比较函数&#xff08;传入的是…...

音视频入门基础:FLV专题(9)——Script Tag简介

一、SCRIPTDATA 根据《video_file_format_spec_v10_1.pdf》第75页到76页&#xff0c;如果某个Tag的Tag header中的TagType值为18&#xff0c;表示该Tag为Script Tag&#xff08;脚本Tag&#xff0c;又称Data Tag、SCRIPTDATA tag&#xff09;。这时如果Filter的值不为1表示未加…...

Vue页面,基础配置

最简单页面 日期范围及字符搜索&#xff0c;监听器处理日期范围搜索控件清空重置问题导出、导出文件文件名称带日期时间表格日期指定格式显示。。。 <template><div class"app-container"><el-form :model"queryParams" ref"queryForm…...

(杨辉三角) 攻防世界--->notsequence

学习笔记。(不想看可以直接跳正文。) 前言&#xff1a;仿佛又回到高中 - - 只不过&#xff0c;是以另一种形式再次出现。 学习思维为主&#xff0c;做题位次。&#xff08;后面再补。&#xff09; 前置知识&#xff1a; 什么是杨辉三角&#xff1a; 杨辉三角_百度百科 (bai…...

【CTF Web】Pikachu CSRF(get) Writeup(CSRF+GET请求+社会工程学)

CSRF(跨站请求伪造)概述 Cross-site request forgery 简称为“CSRF”&#xff0c;在CSRF的攻击场景中攻击者会伪造一个请求&#xff08;这个请求一般是一个链接&#xff09;&#xff0c;然后欺骗目标用户进行点击&#xff0c;用户一旦点击了这个请求&#xff0c;整个攻击就完成…...

智能工厂的软件设计 作为“程序Program”的中台 之2

Q11、现在&#xff0c;我们再重新理解一下前面所说&#xff1a;三种中台&#xff08;数据中台、技术中台和业务中台&#xff09;作为这个整体的三个整子Holon&#xff0c;合起来是一个 融合了三分法&#xff08; 三分部 的三个中台构成三段式 程序框架&#xff0c;分别用 <h…...

GB28181信令交互流程及Android端设备对接探讨

GB28181规范必要性 好多开发者在做比如执法记录仪、智能安全帽、智能监控等设备端视频回传技术方案选型的时候&#xff0c;不清楚到底是用RTSP、RTMP还是GB28181&#xff0c;对GB28181相对比较陌生&#xff0c;我们就GB28181规范的必要性&#xff0c;做个探讨&#xff1a; 实现…...

OpenCV视频I/O(14)创建和写入视频文件的类:VideoWriter介绍

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 视频写入器类。 该类提供了用于写入视频文件或图像序列的 C API。 cv::VideoWriter 类是 OpenCV 库中用于创建和写入视频文件的一个类。它提供了…...

c语言学习(书本目录)

关键字 变量 3.typedef的使用&#xff1a; 定义一个结构体&#xff0c;名字是Person&#xff1a; struct Person { char name[50]; int age; }; 给结构体取一个别名&#xff1a;struct Person---》Person typedef struct Person Person; 上面带标签的结构体等效于 …...