从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router、抽取全局配置文件】
从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router】
本期主要完成对Server的搭建、封装连接与业务绑定、实现基础Router(处理业务的部分)、抽取框架的全局配置文件
- 从配置文件中读取数据(服务器监听端口、监听IP等),通过自定义Router完成具体业务操作
第一版最终项目结构:

1 搭建基础server[V1.0]
1.1 编写server端
- 编写iserver.go,用于定义server的接口
- 编写server.go,定义server结构体,并实现接口
①/zinx/ziface/iserver.go:
package zifacetype IServer interface {Start()Stop()Serve()
}
②/zinx/znet/server.go
package znetimport ("fmt""net"
)type Server struct {Name stringIPVersion stringIP stringPort int
}func NewServer(name string) *Server {s := &Server{Name: name,IPVersion: "tcp4",IP: "0.0.0.0",Port: 8090,}return s
}func (s *Server) Start() {//启动服务监听端口fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)go func() {addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))if err != nil {fmt.Printf("resolve tcp addr error %v\n", err)return}listener, err := net.ListenTCP(s.IPVersion, addr)if err != nil {fmt.Println("listen ", s.IPVersion, " err ", err)return}fmt.Println("[start] Zinx server success ", s.Name, "Listening...")//阻塞连接,处理业务for {conn, err := listener.AcceptTCP()if err != nil {fmt.Println("Accept err ", err)continue}//处理业务:回显消息go func() {for {buf := make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("read buf err ", err)continue}fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)//回显读取到的字节数if _, err := conn.Write(buf[:cnt]); err != nil {fmt.Println("write buf err ", err)continue}}}()}}()
}func (s *Server) Stop() {}func (s *Server) Serve() {s.Start()//阻塞,一直读取客户端所发送过来的消息select {}
}
1.2 测试server端功能
①创建Server.go和Client.go
- 编写myDemo/zinxV1.0/Server.go
package mainimport "myTest/zinx/znet"func main() {s := znet.NewServer("[Zinx v1.0]")s.Serve()
}
- 编写myDemo/zinxV1.0/Client.go
package mainimport ("fmt""net""time"
)/*
模拟客户端
*/
func main() {fmt.Println("client start...")time.Sleep(time.Second * 1)//1 创建服务器连接conn, err := net.Dial("tcp", "127.0.0.1:8090")if err != nil {fmt.Println("client start err ", err)return}for {//2 调用连接向服务器发数据_, err := conn.Write([]byte("Hello Zinx v0.1"))if err != nil {fmt.Println("write conn err ", err)return}// 3 读取服务器返回的数据buf := make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("client read buf err ", err)return}fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片time.Sleep(time.Second * 1)}
}
②测试结果

可以看到每隔1秒服务器就从客户端接受到数据并回显
2 封装连接conn、业务绑定[V2.0]
V0.1版本我们已经实现了了⼀一个基础的Server框架,现在我们需要对客户端链接和不不同的客户端链接所处 理理的不不同业务再做⼀一层接⼝口封装,当然我们先是把架构搭建起来。
现在在 ziface 下创建⼀一个属于链接的接⼝口⽂文件 iconnection.go ,当然他的实现⽂文件我们放在 znet 下的 connection.go 中。
需要的方法:
- 启动连接
- 停止连接
- 得到连接的conn对象
- 得到连接的id
- 得到客户端连接的地址和端口
- 发送数据的方法
- 连接所绑定的处理业务的函数
2.1 封装Conn
- 定义iconnection接口
- 创建connection结构体并实现iconnection
- 创建/zinx/ziface/iconnection.go:
package zifaceimport "net"type IConnection interface {//启动连接Start()//停止连接Stop()//获取当前连接的Conn对象GetTCPConnection() *net.TCPConn//获取当前连接模块的idGetConnectionID() uint32//获取远程客户端的TCP状态 IP:PortRemoteAddr() net.Addr//发送数据Send()
}//定义一个处理连接业务的方法
type HandleFunc func(*net.TCPConn, []byte, int) error
- 创建/zinx/znet/connection.go
package znetimport ("fmt""myTest/zinx/ziface""net"
)type Connection struct {Conn *net.TCPConnConnID uint32isClosed boolhandleAPI ziface.HandleFunc//告知当前的连接已经退出ExitChan chan bool
}func NewConnection(conn *net.TCPConn, connID uint32, callback_api ziface.HandleFunc) *Connection {c := &Connection{Conn: conn,ConnID: connID,handleAPI: callback_api,isClosed: false,ExitChan: make(chan bool, 1),}return c
}func (c *Connection) StartReader() {fmt.Println("reader goroutine is running...")defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())defer c.Stop()//读取数据for {buf := make([]byte, 512)cnt, err := c.Conn.Read(buf)if err != nil {fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)continue}//调用当前所绑定的处理业务的方法HandleAPIif err := c.handleAPI(c.Conn, buf, cnt); err != nil {fmt.Println("ConnID", c.ConnID, " handle is err ", err)break}}
}//启动连接
func (c *Connection) Start() {fmt.Printf("ConnID %d is Start...", c.ConnID)go c.StartReader()
}//停止连接
func (c *Connection) Stop() {fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)if c.isClosed {return}c.isClosed = truec.Conn.Close()close(c.ExitChan)
}//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {return c.Conn
}//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {return c.ConnID
}//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {return c.Conn.RemoteAddr()
}//发送数据
func (c *Connection) Send() {}
2.2 修改server.go(通过封装的conn实现处理业务)
将修改server.go,添加CallBackToClient方法,用于实现具体业务
将ZinxV1.0版本中的server.go的处理业务逻辑部分更换为封装后的Conn来调用

全部代码:
/zinx/znet/server.go:
package znetimport ("fmt""github.com/kataras/iris/v12/x/errors""net"
)type Server struct {Name stringIPVersion stringIP stringPort int
}func NewServer(name string) *Server {s := &Server{Name: name,IPVersion: "tcp4",IP: "0.0.0.0",Port: 8090,}return s
}//定义当前客户端连接所绑定的handleAPI(暂时写死处理业务逻辑:数据回显)
func CallBackToClient(conn *net.TCPConn, data []byte, cnt int) error {fmt.Println("[Conn handle] CallBackToClient....")if _, err := conn.Write(data[:cnt]); err != nil {fmt.Println("write buf err ", err)return errors.New("CallBackToClient error")}return nil
}func (s *Server) Start() {//启动服务监听端口fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)go func() {addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))if err != nil {fmt.Printf("resolve tcp addr error %v\n", err)return}listener, err := net.ListenTCP(s.IPVersion, addr)if err != nil {fmt.Println("listen ", s.IPVersion, " err ", err)return}fmt.Println("[start] Zinx server success ", s.Name, "Listening...")//阻塞连接,处理业务for {conn, err := listener.AcceptTCP()if err != nil {fmt.Println("Accept err ", err)continue}var cid uint32 = 0dealConn := NewConnection(conn, cid, CallBackToClient)cid++//开启goroutine处理启动当前conngo dealConn.Start()处理业务:回显消息//go func() {// for {// buf := make([]byte, 512)// cnt, err := conn.Read(buf)// if err != nil {// fmt.Println("read buf err ", err)// continue// }// fmt.Printf("receive client buf %s, cnt %d \n", buf, cnt)// //回显读取到的字节数// if _, err := conn.Write(buf[:cnt]); err != nil {// fmt.Println("write buf err ", err)// continue// }// }////}()}}()
}func (s *Server) Stop() {}func (s *Server) Serve() {s.Start()//阻塞,一直读取客户端所发送过来的消息select {}
}
2.3 测试ZinxV2.0功能
①修改Server.go和Client.go的日志打印
创建/myDemo/ZinxV2.0/Client.go和/myDemo/ZinxV2.0/Server.go,这部分测试代码和V1.0没有区别,将打印日志换成Zinx2.0即可
- Client.go
package mainimport ("fmt""net""time"
)/*
模拟客户端
*/
func main() {fmt.Println("client start...")time.Sleep(time.Second * 1)//1 创建服务器连接conn, err := net.Dial("tcp", "127.0.0.1:8090")if err != nil {fmt.Println("client start err ", err)return}for {//2 调用连接向服务器发数据_, err := conn.Write([]byte("Hello Zinx v0.2"))if err != nil {fmt.Println("write conn err ", err)return}// 3 读取服务器返回的数据buf := make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("client read buf err ", err)return}fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片time.Sleep(time.Second * 1)}
}
- Server.go
package mainimport "myTest/zinx/znet"func main() {s := znet.NewServer("[Zinx v2.0]")s.Serve()
}
②测试结果

3 实现基础Router[V3.0]
3.1 Request请求封装
将连接和数据绑定在一起
zinx/ziface/irequest.go:
package zifaceimport "net"type IRequest interface {GetConnection() *net.TCPConnGetData() []byte
}
zinx/znet/request.go:
package znetimport "net"type Request struct {conn *net.TCPConndata []byte
}func (r *Request) GetConnection() *net.TCPConn {return r.conn
}func (r *Request) GetData() []byte {return r.data
}
3.2 Router模块
zinx/ziface/irouter.go
package zifacetype IRouter interface {//处理请求之前的方法PreHandle(request IRequest)Handler(request IRequest)//处理请求之后的方法PostHandler(request IRequest)
}
zinx/znet/router.go
package znetimport "myTest/zinx/ziface"type BaseRouter struct {
}//这里做了空实现,直接让后续Router继承BaseRouter,然后根据需要重写对应方法即可
func (br *BaseRouter) PreHandle(request ziface.IRequest) {}
func (br *BaseRouter) Handler(request ziface.IRequest) {}func (br *BaseRouter) PostHandler(request ziface.IRequest) {}
3.3 框架集成router模块
- 取消znet/server.go中的HandlerFunc模块,改为Router。server.go中添加Router属性
- 将znet/connection.go中的callback_api ziface.HandleFunc参数改为Router
zinx/znet/connection.go
package znetimport ("fmt""myTest/zinx/ziface""net"
)type Connection struct {Conn *net.TCPConnConnID uint32isClosed bool//告知当前的连接已经退出ExitChan chan boolRouter ziface.IRouter
}func NewConnection(conn *net.TCPConn, connID uint32, router ziface.IRouter) *Connection {c := &Connection{Conn: conn,ConnID: connID,Router: router,isClosed: false,ExitChan: make(chan bool, 1),}return c
}func (c *Connection) StartReader() {fmt.Println("reader goroutine is running...")defer fmt.Println("connID=", c.ConnID, "Reader is exit, remote addr is ", c.RemoteAddr().String())defer c.Stop()//读取数据for {buf := make([]byte, 512)_, err := c.Conn.Read(buf)if err != nil {fmt.Printf("connID %d receive buf err %s\n", c.ConnID, err)continue}//封装请求,改为router处理r := Request{conn: c.Conn,data: buf,}go func(request ziface.IRequest) {c.Router.PreHandle(request)c.Router.Handler(request)c.Router.PostHandler(request)}(&r)}
}//启动连接
func (c *Connection) Start() {fmt.Printf("ConnID %d is Start...", c.ConnID)go c.StartReader()
}//停止连接
func (c *Connection) Stop() {fmt.Println("Connection Stop()...ConnectionID = ", c.ConnID)if c.isClosed {return}c.isClosed = truec.Conn.Close()close(c.ExitChan)
}//获取当前连接的Conn对象
func (c *Connection) GetTCPConnection() *net.TCPConn {return c.Conn
}//获取当前连接模块的id
func (c *Connection) GetConnectionID() uint32 {return c.ConnID
}//获取远程客户端的TCP状态 IP:Port
func (c *Connection) RemoteAddr() net.Addr {return c.Conn.RemoteAddr()
}//发送数据
func (c *Connection) Send() {}
zinx/znet/server.go
package znetimport ("fmt""myTest/zinx/ziface""net"
)type Server struct {Name stringIPVersion stringIP stringPort intRouter ziface.IRouter
}func NewServer(name string) *Server {s := &Server{Name: name,IPVersion: "tcp4",IP: "0.0.0.0",Port: 8090,Router: nil,}return s
}func (s *Server) Start() {//启动服务监听端口fmt.Printf("[start] Server listener at IP:%s, Port %d is starting\n", s.IP, s.Port)go func() {addr, err := net.ResolveTCPAddr(s.IPVersion, fmt.Sprintf("%s:%d", s.IP, s.Port))if err != nil {fmt.Printf("resolve tcp addr error %v\n", err)return}listener, err := net.ListenTCP(s.IPVersion, addr)if err != nil {fmt.Println("listen ", s.IPVersion, " err ", err)return}fmt.Println("[start] Zinx server success ", s.Name, "Listening...")//阻塞连接,处理业务for {conn, err := listener.AcceptTCP()if err != nil {fmt.Println("Accept err ", err)continue}var cid uint32 = 0dealConn := NewConnection(conn, cid, s.Router)cid++//开启goroutine处理启动当前conngo dealConn.Start()}}()
}func (s *Server) Stop() {}func (s *Server) Serve() {s.Start()//阻塞,一直读取客户端所发送过来的消息select {}
}func (s *Server) AddRouter(router ziface.IRouter) {s.Router = router
}
测试框架集成router效果
myDemo/ZinxV3.0/client.go
package mainimport ("fmt""net""time"
)/*
模拟客户端
*/
func main() {fmt.Println("client start...")time.Sleep(time.Second * 1)//1 创建服务器连接conn, err := net.Dial("tcp", "127.0.0.1:8090")if err != nil {fmt.Println("client start err ", err)return}for {//2 调用连接向服务器发数据_, err := conn.Write([]byte("Hello Zinx v0.3"))if err != nil {fmt.Println("write conn err ", err)return}// 3 读取服务器返回的数据buf := make([]byte, 512)cnt, err := conn.Read(buf)if err != nil {fmt.Println("client read buf err ", err)return}fmt.Printf("server call back:%s, cnt=%d\n", buf, cnt)//cpu阻塞,让出cpu时间片,避免无限for循环导致其他程序无法获取cpu时间片time.Sleep(time.Second * 1)}
}
myDemo/ZinxV3.0/server.go
package mainimport ("fmt""myTest/zinx/ziface""myTest/zinx/znet"
)//自定义一个Router,测试路由功能
type PingRouter struct {znet.BaseRouter
}func (pr *PingRouter) PreHandle(request ziface.IRequest) {_, err := request.GetConnection().Write([]byte("pre handle success..."))if err != nil {fmt.Println("server call pre handle err ", err)return}fmt.Println("server call pre handle...")
}func (pr *PingRouter) Handler(request ziface.IRequest) {_, err := request.GetConnection().Write([]byte("handle success..."))if err != nil {fmt.Println("server call handle err ", err)return}fmt.Println("server call handler....")
}func (pr *PingRouter) PostHandler(request ziface.IRequest) {_, err := request.GetConnection().Write([]byte("post handle success..."))if err != nil {fmt.Println("server call post handle err ", err)return}fmt.Println("server call post handler...")
}func main() {s := znet.NewServer("[Zinx v3.0]")//添加自定义路由router := &PingRouter{}s.AddRouter(router)s.Serve()
}
最终效果:

按照模板方法设计模式,完成了调用
4 抽取全局配置文件[V4.0]
4.1 编写/zinx/util/globalobj.go
主要用于读取zinx配置文件的信息
package utilimport ("encoding/json""io/ioutil""myTest/zinx/ziface"
)type GlobalObj struct {TCPServer ziface.IServer //当前全局Zinx的server对象Host string //当前服务器主机监听的ipTcpPort int //当前服务器主机监听的端口号Name string //当前服务器的名称Version string //当前Zinx的版本号MaxConn int //当前服务器所允许的最大连接数MaxPackageSize uint32 //当前Zinx框架数据包的最大值
}var GlobalObject *GlobalObj//从配置文件中重新加载GlobalObject的信息
func (g *GlobalObj) Reload() {data, err := ioutil.ReadFile("conf/zinx.json")if err != nil {panic(err)}//将json文件数据解析到struct中err = json.Unmarshal(data, &GlobalObject)if err != nil {panic(err)}
}//在其他文件导入该util包的时候会加载init
func init() {GlobalObject = &GlobalObj{Name: "ZinxServerApp",Version: "V0.4",TcpPort: 8090,Host: "0.0.0.0",MaxConn: 120,MaxPackageSize: 4096,}//尝试从conf/zinx.json中去加载用户自定义的参数GlobalObject.Reload()
}
4.2 替换之前server.go中的硬编码
包括/zinx/znet/server.go和/zinx/znet/connection.go部分
- server:
- connection:
4.3 测试
编写myDemo/ZinxV4.0
- 并且编写对应的.json配置文件(Client.go与Server.go都与V3.0一样)

zinx.json
{"Name": "Zinx Server Application","Version": "V0.4","Host": "0.0.0.0","TcpPort": 8091,"MaxConn": 30,"MaxPackageSize": 1024
}
最后效果:

参考:https://www.yuque.com/aceld/npyr8s/bgftov
相关文章:
从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router、抽取全局配置文件】
从0到1开发go-tcp框架【1-搭建server、封装连接与业务绑定、实现基础Router】 本期主要完成对Server的搭建、封装连接与业务绑定、实现基础Router(处理业务的部分)、抽取框架的全局配置文件 从配置文件中读取数据(服务器监听端口、监听IP等&a…...
建设银行秋招指南,备考技巧和考试内容详解
建设银行秋招简介 银行作为非常吃香的岗位,每年都有不少同学通过投递简历,进入笔试,再到面试成功,成功到银行就职,也有相当一部分同学因为信息差,符合条件却没有报名。无法进入银行工作。 建设银行的秋招…...
Cilium 系列-7-Cilium 的 NodePort 实现从 SNAT 改为 DSR
系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能。但是通过对 Cilium 不同模式的切换/功能的启用,可以进一步提升 Cilium 的网络性能。具体调优项包括不限于: 启用本地路由 (Native Rou…...
React的hooks---useReducer
useReducer 作为 useState 的代替方案,在某些场景下使用更加适合,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。 使用 useReducer 还能给那些会触发深更新的组件做性能优化,因为父组件可以向自…...
自然语言处理从入门到应用——LangChain:模型(Models)-[文本嵌入模型Ⅱ]
分类目录:《自然语言处理从入门到应用》总目录 本文将介绍如何在LangChain中使用Embedding类。Embedding类是一种与嵌入交互的类。有很多嵌入提供商,如:OpenAI、Cohere、Hugging Face等,这个类旨在为所有这些提供一个标准接口。 …...
Olap BI工具对比
背景 目前公司主要使用数据存储有MySQL、ES、Hive、HBase、TiDB等 MySQL用于存储应用的基本支撑数据,数据量少;ES和Hbase用于存储和查询调用记录,数据量多;Hive和TiDB用于DC上使用,数据量多。主要使用的数据分析平台…...
【iOS】Cocoapods的安装以及使用
文章目录 前言一、Cocoapods的作用二、安装Cocoapods三、使用Cocoapods总结 前言 最近笔者在仿写天气预报App时用到了api调用数据,一般的基本数据类型我们用Xcode中自带的框架就可以转换得到。但是在和风天气api中的图标的格式为svg格式。 似乎iOS13之后Xcode中可…...
OpenCvSharp (C# OpenCV) 二维码畸变矫正--基于透视变换(附源码)
导读 本文主要介绍如何使用OpenCvSharp中的透视变换来实现二维码的畸变矫正。 由于CSDN文章中贴二维码会导致显示失败,大家可以直接点下面链接查看图片: C# OpenCV实现二维码畸变矫正--基于透视变换 (详细步骤 + 代码) 实现步骤 讲解实现步骤之前先看下效果(左边是原图,右边…...
下级平台级联视频汇聚融合平台EasyCVR,层级显示不正确的原因排查
视频汇聚平台安防监控EasyCVR可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有GB28181、RTSP/Onvif、RTMP等,以及厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等,能对外分发RTSP、RTMP、FLV、HLS、WebRTC等…...
Android程序CPU使用大的异常分析
程序出现CPU使用过高的问题,如果能够重现,就比较好办了,可以top命令查看各线程的cpu使用,定位到线程。 以下是问国内某AI的答案 在Android应用中,如果某个应用消耗了大量的CPU资源,可以采取以下方法进行分…...
[数学建模] 0、关于数学建模的一点看法付费专栏食用说明
文章目录 1、前言2、数学建模学习索引2.1、建模知识点 3、实战建模论文索引3.1、国赛真题索引3.1.1、[数学建模] [2001年国赛模拟] 1. 血管的三维重建3.1.2、[数学建模] [2011年B国赛模拟] 2. 交巡警服务平台的设置与调度3.1.3、[数学建模][2012年A国赛模拟] 3. 葡萄酒的评价 3…...
2.oracle数据库自增主键
不同于mysql,oracle主键自增不能在建表时直接设置,其实也很简单 1.建表 CREATE TABLE test(id NUMBER NOT NULL,key1 VARCHAR2(40) NULL,key2 VARCHAR2(40) NULL);2.设置主键 alter table test add constraint test_pk primary key (id);3.新建序列tes…...
算法通关村第二关——链表加法的问题解析
题目类型 链表反转、栈 题目描述 * 题目: * 给你两个非空链表来表示两个非负整数,数字最高位位于链表的开始位置。 * 它们的每个节点都只存储一个数字。将这两个数相加会返回一个新的链表。 * 你可以假设除了数字0外,这两个数字都不会以0开头…...
mapboxGL中楼层与室内地图的结合展示
概述 质量不够,数量来凑,没错,本文就是来凑数的。前面的几篇文章实现了楼栋与楼层单体化的展示、室内地图的展示,本文结合前面的几篇文章,做一个综合的展示效果。 实现效果 实现 1. 数据处理 要实现上图所示的效果…...
使用Anaconda3创建pytorch虚拟环境
一、Conda配置Pytorch环境 1.conda安装Pytorch环境 打开Anaconda Prompt,输入命令行: conda create -n pytorch python3.6 输入y,再回车。 稍等,便完成了Pytorch的环境安装。我们可以利用以下命令激活pytorch环境。 conda…...
QT 常用数据结构整理
目录 QString篇 QString篇 //初始化bool bOk false;QString str "sd";QString strTemp(str);str QString("%1,%2").arg("11").arg("-gg");qDebug()<<str;str.sprintf("%s %d","ni",1);qDebug()<<…...
Fiddler使用教程|渗透测试工具使用方法Fiddler
提示:如有问题可联系我,24小时在线 文章目录 前言一、Fiddler界面介绍二、菜单栏1.菜单Fiddler工具栏介绍Fiddler命令行工具详解 前言 网络渗透测试工具: Fiddler是目前最常用的http抓包工具之一。 Fiddler是功能非常强大,是web…...
网站密码忘记了怎么办?chrome浏览器,谷歌浏览器。
有时候忘记了网站的密码,又不想“忘记密码”去一番折腾。如果你正好用的是 chrome 浏览器。 那么根本就没必要折腾,直接就能看到网站密码。 操作如下 1.在浏览器右上角点击三个小点: 2.点这三个点: 3.选择“显示密码”&#x…...
23款奔驰GLS450加装原厂香氛负离子系统,清香宜人,久闻不腻
奔驰原厂香氛合理性可通过车内空气调节组件营造芳香四溢的怡人氛围。通过更换手套箱内香氛喷雾发生器所用的香水瓶,可轻松选择其他香氛。香氛的浓度和持续时间可调。淡雅的香氛缓缓喷出,并且在关闭后能够立刻散去。车内气味不会永久改变,香氛…...
流数据湖平台Apache Paimon(一)概述
文章目录 第1章 概述1.1 简介1.2 核心特性1.3 基本概念1.3.1 Snapshot1.3.2 Partition1.3.3 Bucket1.3.4 Consistency Guarantees一致性保证 1.4 文件布局1.4.1 Snapshot Files1.4.2 Manifest Files1.4.3 Data Files1.4.4 LSM Trees 第1章 概述 1.1 简介 Flink 社区希望能够将…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
Zustand 状态管理库:极简而强大的解决方案
Zustand 是一个轻量级、快速和可扩展的状态管理库,特别适合 React 应用。它以简洁的 API 和高效的性能解决了 Redux 等状态管理方案中的繁琐问题。 核心优势对比 基本使用指南 1. 创建 Store // store.js import create from zustandconst useStore create((set)…...
Redis相关知识总结(缓存雪崩,缓存穿透,缓存击穿,Redis实现分布式锁,如何保持数据库和缓存一致)
文章目录 1.什么是Redis?2.为什么要使用redis作为mysql的缓存?3.什么是缓存雪崩、缓存穿透、缓存击穿?3.1缓存雪崩3.1.1 大量缓存同时过期3.1.2 Redis宕机 3.2 缓存击穿3.3 缓存穿透3.4 总结 4. 数据库和缓存如何保持一致性5. Redis实现分布式…...
SCAU期末笔记 - 数据分析与数据挖掘题库解析
这门怎么题库答案不全啊日 来简单学一下子来 一、选择题(可多选) 将原始数据进行集成、变换、维度规约、数值规约是在以下哪个步骤的任务?(C) A. 频繁模式挖掘 B.分类和预测 C.数据预处理 D.数据流挖掘 A. 频繁模式挖掘:专注于发现数据中…...
12.找到字符串中所有字母异位词
🧠 题目解析 题目描述: 给定两个字符串 s 和 p,找出 s 中所有 p 的字母异位词的起始索引。 返回的答案以数组形式表示。 字母异位词定义: 若两个字符串包含的字符种类和出现次数完全相同,顺序无所谓,则互为…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
MySQL 部分重点知识篇
一、数据库对象 1. 主键 定义 :主键是用于唯一标识表中每一行记录的字段或字段组合。它具有唯一性和非空性特点。 作用 :确保数据的完整性,便于数据的查询和管理。 示例 :在学生信息表中,学号可以作为主键ÿ…...
C语言中提供的第三方库之哈希表实现
一. 简介 前面一篇文章简单学习了C语言中第三方库(uthash库)提供对哈希表的操作,文章如下: C语言中提供的第三方库uthash常用接口-CSDN博客 本文简单学习一下第三方库 uthash库对哈希表的操作。 二. uthash库哈希表操作示例 u…...
git: early EOF
macOS报错: Initialized empty Git repository in /usr/local/Homebrew/Library/Taps/homebrew/homebrew-core/.git/ remote: Enumerating objects: 2691797, done. remote: Counting objects: 100% (1760/1760), done. remote: Compressing objects: 100% (636/636…...






